SystemTextClassifier.java revision 6563833cf3c79e8cd211e32357422ae899674437
1/* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.view.textclassifier; 18 19import android.annotation.IntRange; 20import android.annotation.NonNull; 21import android.annotation.Nullable; 22import android.annotation.WorkerThread; 23import android.content.Context; 24import android.os.Looper; 25import android.os.RemoteException; 26import android.os.ServiceManager; 27import android.service.textclassifier.ITextClassificationCallback; 28import android.service.textclassifier.ITextClassifierService; 29import android.service.textclassifier.ITextLinksCallback; 30import android.service.textclassifier.ITextSelectionCallback; 31 32import com.android.internal.annotations.GuardedBy; 33import com.android.internal.annotations.VisibleForTesting; 34import com.android.internal.annotations.VisibleForTesting.Visibility; 35import com.android.internal.util.Preconditions; 36 37import java.util.concurrent.CountDownLatch; 38import java.util.concurrent.TimeUnit; 39 40/** 41 * Proxy to the system's default TextClassifier. 42 * @hide 43 */ 44@VisibleForTesting(visibility = Visibility.PACKAGE) 45public final class SystemTextClassifier implements TextClassifier { 46 47 private static final String LOG_TAG = "SystemTextClassifier"; 48 49 private final ITextClassifierService mManagerService; 50 private final TextClassificationConstants mSettings; 51 private final TextClassifier mFallback; 52 private final String mPackageName; 53 54 private final Object mLoggerLock = new Object(); 55 @GuardedBy("mLoggerLock") 56 private Logger.Config mLoggerConfig; 57 @GuardedBy("mLoggerLock") 58 private Logger mLogger; 59 60 public SystemTextClassifier(Context context, TextClassificationConstants settings) 61 throws ServiceManager.ServiceNotFoundException { 62 mManagerService = ITextClassifierService.Stub.asInterface( 63 ServiceManager.getServiceOrThrow(Context.TEXT_CLASSIFICATION_SERVICE)); 64 mSettings = Preconditions.checkNotNull(settings); 65 mFallback = new TextClassifierImpl(context, settings); 66 mPackageName = Preconditions.checkNotNull(context.getPackageName()); 67 } 68 69 /** 70 * @inheritDoc 71 */ 72 @Override 73 @WorkerThread 74 public TextSelection suggestSelection( 75 @NonNull CharSequence text, 76 @IntRange(from = 0) int selectionStartIndex, 77 @IntRange(from = 0) int selectionEndIndex, 78 @Nullable TextSelection.Options options) { 79 Utils.validate(text, selectionStartIndex, selectionEndIndex, false /* allowInMainThread */); 80 try { 81 final TextSelectionCallback callback = new TextSelectionCallback(); 82 mManagerService.onSuggestSelection( 83 text, selectionStartIndex, selectionEndIndex, options, callback); 84 final TextSelection selection = callback.mReceiver.get(); 85 if (selection != null) { 86 return selection; 87 } 88 } catch (RemoteException e) { 89 e.rethrowAsRuntimeException(); 90 } catch (InterruptedException e) { 91 Log.d(LOG_TAG, e.getMessage()); 92 } 93 return mFallback.suggestSelection(text, selectionStartIndex, selectionEndIndex, options); 94 } 95 96 /** 97 * @inheritDoc 98 */ 99 @Override 100 @WorkerThread 101 public TextClassification classifyText( 102 @NonNull CharSequence text, 103 @IntRange(from = 0) int startIndex, 104 @IntRange(from = 0) int endIndex, 105 @Nullable TextClassification.Options options) { 106 Utils.validate(text, startIndex, endIndex, false /* allowInMainThread */); 107 try { 108 final TextClassificationCallback callback = new TextClassificationCallback(); 109 mManagerService.onClassifyText(text, startIndex, endIndex, options, callback); 110 final TextClassification classification = callback.mReceiver.get(); 111 if (classification != null) { 112 return classification; 113 } 114 } catch (RemoteException e) { 115 e.rethrowAsRuntimeException(); 116 } catch (InterruptedException e) { 117 Log.d(LOG_TAG, e.getMessage()); 118 } 119 return mFallback.classifyText(text, startIndex, endIndex, options); 120 } 121 122 /** 123 * @inheritDoc 124 */ 125 @Override 126 @WorkerThread 127 public TextLinks generateLinks( 128 @NonNull CharSequence text, @Nullable TextLinks.Options options) { 129 Utils.validate(text, false /* allowInMainThread */); 130 131 final boolean legacyFallback = options != null && options.isLegacyFallback(); 132 if (!mSettings.isSmartLinkifyEnabled() && legacyFallback) { 133 return Utils.generateLegacyLinks(text, options); 134 } 135 136 try { 137 if (options == null) { 138 options = new TextLinks.Options().setCallingPackageName(mPackageName); 139 } else if (!mPackageName.equals(options.getCallingPackageName())) { 140 options.setCallingPackageName(mPackageName); 141 } 142 final TextLinksCallback callback = new TextLinksCallback(); 143 mManagerService.onGenerateLinks(text, options, callback); 144 final TextLinks links = callback.mReceiver.get(); 145 if (links != null) { 146 return links; 147 } 148 } catch (RemoteException e) { 149 e.rethrowAsRuntimeException(); 150 } catch (InterruptedException e) { 151 Log.d(LOG_TAG, e.getMessage()); 152 } 153 return mFallback.generateLinks(text, options); 154 } 155 156 /** 157 * @inheritDoc 158 */ 159 @Override 160 @WorkerThread 161 public int getMaxGenerateLinksTextLength() { 162 // TODO: retrieve this from the bound service. 163 return mFallback.getMaxGenerateLinksTextLength(); 164 } 165 166 @Override 167 public Logger getLogger(@NonNull Logger.Config config) { 168 Preconditions.checkNotNull(config); 169 synchronized (mLoggerLock) { 170 if (mLogger == null || !config.equals(mLoggerConfig)) { 171 mLoggerConfig = config; 172 mLogger = new Logger(config) { 173 @Override 174 public void writeEvent(SelectionEvent event) { 175 try { 176 mManagerService.onSelectionEvent(event); 177 } catch (RemoteException e) { 178 e.rethrowAsRuntimeException(); 179 } 180 } 181 }; 182 } 183 } 184 return mLogger; 185 } 186 187 private static final class TextSelectionCallback extends ITextSelectionCallback.Stub { 188 189 final ResponseReceiver<TextSelection> mReceiver = new ResponseReceiver<>(); 190 191 @Override 192 public void onSuccess(TextSelection selection) { 193 mReceiver.onSuccess(selection); 194 } 195 196 @Override 197 public void onFailure() { 198 mReceiver.onFailure(); 199 } 200 } 201 202 private static final class TextClassificationCallback extends ITextClassificationCallback.Stub { 203 204 final ResponseReceiver<TextClassification> mReceiver = new ResponseReceiver<>(); 205 206 @Override 207 public void onSuccess(TextClassification classification) { 208 mReceiver.onSuccess(classification); 209 } 210 211 @Override 212 public void onFailure() { 213 mReceiver.onFailure(); 214 } 215 } 216 217 private static final class TextLinksCallback extends ITextLinksCallback.Stub { 218 219 final ResponseReceiver<TextLinks> mReceiver = new ResponseReceiver<>(); 220 221 @Override 222 public void onSuccess(TextLinks links) { 223 mReceiver.onSuccess(links); 224 } 225 226 @Override 227 public void onFailure() { 228 mReceiver.onFailure(); 229 } 230 } 231 232 private static final class ResponseReceiver<T> { 233 234 private final CountDownLatch mLatch = new CountDownLatch(1); 235 236 private T mResponse; 237 238 public void onSuccess(T response) { 239 mResponse = response; 240 mLatch.countDown(); 241 } 242 243 public void onFailure() { 244 Log.e(LOG_TAG, "Request failed.", null); 245 mLatch.countDown(); 246 } 247 248 @Nullable 249 public T get() throws InterruptedException { 250 // If this is running on the main thread, do not block for a response. 251 // The response will unfortunately be null and the TextClassifier should depend on its 252 // fallback. 253 // NOTE that TextClassifier calls should preferably always be called on a worker thread. 254 if (Looper.myLooper() != Looper.getMainLooper()) { 255 mLatch.await(2, TimeUnit.SECONDS); 256 } 257 return mResponse; 258 } 259 } 260} 261