SystemTextClassifier.java revision 5a03094ebc91df1c64a2232be648ac3ed26657ce
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 = new TextClassifierImpl(context, settings); 59 mPackageName = Preconditions.checkNotNull(context.getPackageName()); 60 } 61 62 /** 63 * @inheritDoc 64 */ 65 @Override 66 @WorkerThread 67 public TextSelection suggestSelection(TextSelection.Request request) { 68 Preconditions.checkNotNull(request); 69 Utils.checkMainThread(); 70 try { 71 final TextSelectionCallback callback = new TextSelectionCallback(); 72 mManagerService.onSuggestSelection(mSessionId, request, callback); 73 final TextSelection selection = callback.mReceiver.get(); 74 if (selection != null) { 75 return selection; 76 } 77 } catch (RemoteException | InterruptedException e) { 78 Log.e(LOG_TAG, "Error suggesting selection for text. Using fallback.", e); 79 } 80 return mFallback.suggestSelection(request); 81 } 82 83 /** 84 * @inheritDoc 85 */ 86 @Override 87 @WorkerThread 88 public TextClassification classifyText(TextClassification.Request request) { 89 Preconditions.checkNotNull(request); 90 Utils.checkMainThread(); 91 try { 92 final TextClassificationCallback callback = new TextClassificationCallback(); 93 mManagerService.onClassifyText(mSessionId, request, callback); 94 final TextClassification classification = callback.mReceiver.get(); 95 if (classification != null) { 96 return classification; 97 } 98 } catch (RemoteException | InterruptedException e) { 99 Log.e(LOG_TAG, "Error classifying text. Using fallback.", e); 100 } 101 return mFallback.classifyText(request); 102 } 103 104 /** 105 * @inheritDoc 106 */ 107 @Override 108 @WorkerThread 109 public TextLinks generateLinks(@NonNull TextLinks.Request request) { 110 Preconditions.checkNotNull(request); 111 Utils.checkMainThread(); 112 113 if (!mSettings.isSmartLinkifyEnabled() && request.isLegacyFallback()) { 114 return Utils.generateLegacyLinks(request); 115 } 116 117 try { 118 request.setCallingPackageName(mPackageName); 119 final TextLinksCallback callback = new TextLinksCallback(); 120 mManagerService.onGenerateLinks(mSessionId, request, callback); 121 final TextLinks links = callback.mReceiver.get(); 122 if (links != null) { 123 return links; 124 } 125 } catch (RemoteException | InterruptedException e) { 126 Log.e(LOG_TAG, "Error generating links. Using fallback.", e); 127 } 128 return mFallback.generateLinks(request); 129 } 130 131 /** 132 * @inheritDoc 133 */ 134 @Override 135 @WorkerThread 136 public int getMaxGenerateLinksTextLength() { 137 // TODO: retrieve this from the bound service. 138 return mFallback.getMaxGenerateLinksTextLength(); 139 } 140 141 @Override 142 public void destroy() { 143 try { 144 if (mSessionId != null) { 145 mManagerService.onDestroyTextClassificationSession(mSessionId); 146 } 147 } catch (RemoteException e) { 148 Log.e(LOG_TAG, "Error destroying classification session.", e); 149 } 150 } 151 152 /** 153 * Attempts to initialize a new classification session. 154 * 155 * @param classificationContext the classification context 156 * @param sessionId the session's id 157 */ 158 void initializeRemoteSession( 159 @NonNull TextClassificationContext classificationContext, 160 @NonNull TextClassificationSessionId sessionId) { 161 mSessionId = Preconditions.checkNotNull(sessionId); 162 try { 163 mManagerService.onCreateTextClassificationSession(classificationContext, mSessionId); 164 } catch (RemoteException e) { 165 Log.e(LOG_TAG, "Error starting a new classification session.", e); 166 } 167 } 168 169 private static final class TextSelectionCallback extends ITextSelectionCallback.Stub { 170 171 final ResponseReceiver<TextSelection> mReceiver = new ResponseReceiver<>(); 172 173 @Override 174 public void onSuccess(TextSelection selection) { 175 mReceiver.onSuccess(selection); 176 } 177 178 @Override 179 public void onFailure() { 180 mReceiver.onFailure(); 181 } 182 } 183 184 private static final class TextClassificationCallback extends ITextClassificationCallback.Stub { 185 186 final ResponseReceiver<TextClassification> mReceiver = new ResponseReceiver<>(); 187 188 @Override 189 public void onSuccess(TextClassification classification) { 190 mReceiver.onSuccess(classification); 191 } 192 193 @Override 194 public void onFailure() { 195 mReceiver.onFailure(); 196 } 197 } 198 199 private static final class TextLinksCallback extends ITextLinksCallback.Stub { 200 201 final ResponseReceiver<TextLinks> mReceiver = new ResponseReceiver<>(); 202 203 @Override 204 public void onSuccess(TextLinks links) { 205 mReceiver.onSuccess(links); 206 } 207 208 @Override 209 public void onFailure() { 210 mReceiver.onFailure(); 211 } 212 } 213 214 private static final class ResponseReceiver<T> { 215 216 private final CountDownLatch mLatch = new CountDownLatch(1); 217 218 private T mResponse; 219 220 public void onSuccess(T response) { 221 mResponse = response; 222 mLatch.countDown(); 223 } 224 225 public void onFailure() { 226 Log.e(LOG_TAG, "Request failed.", null); 227 mLatch.countDown(); 228 } 229 230 @Nullable 231 public T get() throws InterruptedException { 232 // If this is running on the main thread, do not block for a response. 233 // The response will unfortunately be null and the TextClassifier should depend on its 234 // fallback. 235 // NOTE that TextClassifier calls should preferably always be called on a worker thread. 236 if (Looper.myLooper() != Looper.getMainLooper()) { 237 mLatch.await(2, TimeUnit.SECONDS); 238 } 239 return mResponse; 240 } 241 } 242} 243