SystemTextClassifier.java revision dfd3c653cef679b05a5e4f79b2e894b5b0e55c37
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.NonNull; 20import android.annotation.Nullable; 21import android.annotation.WorkerThread; 22import android.content.Context; 23import android.os.Looper; 24import android.os.RemoteException; 25import android.os.ServiceManager; 26import android.service.textclassifier.ITextClassificationCallback; 27import android.service.textclassifier.ITextClassifierService; 28import android.service.textclassifier.ITextLinksCallback; 29import android.service.textclassifier.ITextSelectionCallback; 30 31import com.android.internal.annotations.VisibleForTesting; 32import com.android.internal.annotations.VisibleForTesting.Visibility; 33import com.android.internal.util.Preconditions; 34 35import java.util.concurrent.CountDownLatch; 36import java.util.concurrent.TimeUnit; 37 38/** 39 * Proxy to the system's default TextClassifier. 40 * @hide 41 */ 42@VisibleForTesting(visibility = Visibility.PACKAGE) 43public final class SystemTextClassifier implements TextClassifier { 44 45 private static final String LOG_TAG = "SystemTextClassifier"; 46 47 private final ITextClassifierService mManagerService; 48 private final TextClassificationConstants mSettings; 49 private final TextClassifier mFallback; 50 private final String mPackageName; 51 private TextClassificationSessionId mSessionId; 52 53 public SystemTextClassifier(Context context, TextClassificationConstants settings) 54 throws ServiceManager.ServiceNotFoundException { 55 mManagerService = ITextClassifierService.Stub.asInterface( 56 ServiceManager.getServiceOrThrow(Context.TEXT_CLASSIFICATION_SERVICE)); 57 mSettings = Preconditions.checkNotNull(settings); 58 mFallback = context.getSystemService(TextClassificationManager.class) 59 .getTextClassifier(TextClassifier.LOCAL); 60 mPackageName = Preconditions.checkNotNull(context.getPackageName()); 61 } 62 63 /** 64 * @inheritDoc 65 */ 66 @Override 67 @WorkerThread 68 public TextSelection suggestSelection(TextSelection.Request request) { 69 Preconditions.checkNotNull(request); 70 Utils.checkMainThread(); 71 try { 72 final TextSelectionCallback callback = new TextSelectionCallback(); 73 mManagerService.onSuggestSelection(mSessionId, request, callback); 74 final TextSelection selection = callback.mReceiver.get(); 75 if (selection != null) { 76 return selection; 77 } 78 } catch (RemoteException | InterruptedException e) { 79 Log.e(LOG_TAG, "Error suggesting selection for text. Using fallback.", e); 80 } 81 return mFallback.suggestSelection(request); 82 } 83 84 /** 85 * @inheritDoc 86 */ 87 @Override 88 @WorkerThread 89 public TextClassification classifyText(TextClassification.Request request) { 90 Preconditions.checkNotNull(request); 91 Utils.checkMainThread(); 92 try { 93 final TextClassificationCallback callback = new TextClassificationCallback(); 94 mManagerService.onClassifyText(mSessionId, request, callback); 95 final TextClassification classification = callback.mReceiver.get(); 96 if (classification != null) { 97 return classification; 98 } 99 } catch (RemoteException | InterruptedException e) { 100 Log.e(LOG_TAG, "Error classifying text. Using fallback.", e); 101 } 102 return mFallback.classifyText(request); 103 } 104 105 /** 106 * @inheritDoc 107 */ 108 @Override 109 @WorkerThread 110 public TextLinks generateLinks(@NonNull TextLinks.Request request) { 111 Preconditions.checkNotNull(request); 112 Utils.checkMainThread(); 113 114 if (!mSettings.isSmartLinkifyEnabled() && request.isLegacyFallback()) { 115 return Utils.generateLegacyLinks(request); 116 } 117 118 try { 119 request.setCallingPackageName(mPackageName); 120 final TextLinksCallback callback = new TextLinksCallback(); 121 mManagerService.onGenerateLinks(mSessionId, request, callback); 122 final TextLinks links = callback.mReceiver.get(); 123 if (links != null) { 124 return links; 125 } 126 } catch (RemoteException | InterruptedException e) { 127 Log.e(LOG_TAG, "Error generating links. Using fallback.", e); 128 } 129 return mFallback.generateLinks(request); 130 } 131 132 @Override 133 public void onSelectionEvent(SelectionEvent event) { 134 Preconditions.checkNotNull(event); 135 Utils.checkMainThread(); 136 137 try { 138 mManagerService.onSelectionEvent(mSessionId, event); 139 } catch (RemoteException e) { 140 Log.e(LOG_TAG, "Error reporting selection event.", e); 141 } 142 } 143 144 /** 145 * @inheritDoc 146 */ 147 @Override 148 @WorkerThread 149 public int getMaxGenerateLinksTextLength() { 150 // TODO: retrieve this from the bound service. 151 return mFallback.getMaxGenerateLinksTextLength(); 152 } 153 154 @Override 155 public void destroy() { 156 try { 157 if (mSessionId != null) { 158 mManagerService.onDestroyTextClassificationSession(mSessionId); 159 } 160 } catch (RemoteException e) { 161 Log.e(LOG_TAG, "Error destroying classification session.", e); 162 } 163 } 164 165 /** 166 * Attempts to initialize a new classification session. 167 * 168 * @param classificationContext the classification context 169 * @param sessionId the session's id 170 */ 171 void initializeRemoteSession( 172 @NonNull TextClassificationContext classificationContext, 173 @NonNull TextClassificationSessionId sessionId) { 174 mSessionId = Preconditions.checkNotNull(sessionId); 175 try { 176 mManagerService.onCreateTextClassificationSession(classificationContext, mSessionId); 177 } catch (RemoteException e) { 178 Log.e(LOG_TAG, "Error starting a new classification session.", e); 179 } 180 } 181 182 private static final class TextSelectionCallback extends ITextSelectionCallback.Stub { 183 184 final ResponseReceiver<TextSelection> mReceiver = new ResponseReceiver<>(); 185 186 @Override 187 public void onSuccess(TextSelection selection) { 188 mReceiver.onSuccess(selection); 189 } 190 191 @Override 192 public void onFailure() { 193 mReceiver.onFailure(); 194 } 195 } 196 197 private static final class TextClassificationCallback extends ITextClassificationCallback.Stub { 198 199 final ResponseReceiver<TextClassification> mReceiver = new ResponseReceiver<>(); 200 201 @Override 202 public void onSuccess(TextClassification classification) { 203 mReceiver.onSuccess(classification); 204 } 205 206 @Override 207 public void onFailure() { 208 mReceiver.onFailure(); 209 } 210 } 211 212 private static final class TextLinksCallback extends ITextLinksCallback.Stub { 213 214 final ResponseReceiver<TextLinks> mReceiver = new ResponseReceiver<>(); 215 216 @Override 217 public void onSuccess(TextLinks links) { 218 mReceiver.onSuccess(links); 219 } 220 221 @Override 222 public void onFailure() { 223 mReceiver.onFailure(); 224 } 225 } 226 227 private static final class ResponseReceiver<T> { 228 229 private final CountDownLatch mLatch = new CountDownLatch(1); 230 231 private T mResponse; 232 233 public void onSuccess(T response) { 234 mResponse = response; 235 mLatch.countDown(); 236 } 237 238 public void onFailure() { 239 Log.e(LOG_TAG, "Request failed.", null); 240 mLatch.countDown(); 241 } 242 243 @Nullable 244 public T get() throws InterruptedException { 245 // If this is running on the main thread, do not block for a response. 246 // The response will unfortunately be null and the TextClassifier should depend on its 247 // fallback. 248 // NOTE that TextClassifier calls should preferably always be called on a worker thread. 249 if (Looper.myLooper() != Looper.getMainLooper()) { 250 mLatch.await(2, TimeUnit.SECONDS); 251 } 252 return mResponse; 253 } 254 } 255} 256