TextClassifierService.java revision 080c8542b68cf17a0441862c404cb49ce0e86cfe
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.service.textclassifier; 18 19import android.Manifest; 20import android.annotation.NonNull; 21import android.annotation.Nullable; 22import android.annotation.SystemApi; 23import android.app.Service; 24import android.content.ComponentName; 25import android.content.Context; 26import android.content.Intent; 27import android.content.pm.PackageManager; 28import android.content.pm.ResolveInfo; 29import android.content.pm.ServiceInfo; 30import android.os.CancellationSignal; 31import android.os.IBinder; 32import android.os.RemoteException; 33import android.text.TextUtils; 34import android.util.Slog; 35import android.view.textclassifier.SelectionEvent; 36import android.view.textclassifier.TextClassification; 37import android.view.textclassifier.TextClassificationContext; 38import android.view.textclassifier.TextClassificationManager; 39import android.view.textclassifier.TextClassificationSessionId; 40import android.view.textclassifier.TextClassifier; 41import android.view.textclassifier.TextLinks; 42import android.view.textclassifier.TextSelection; 43 44import com.android.internal.util.Preconditions; 45 46/** 47 * Abstract base class for the TextClassifier service. 48 * 49 * <p>A TextClassifier service provides text classification related features for the system. 50 * The system's default TextClassifierService is configured in 51 * {@code config_defaultTextClassifierService}. If this config has no value, a 52 * {@link android.view.textclassifier.TextClassifierImpl} is loaded in the calling app's process. 53 * 54 * <p>See: {@link TextClassifier}. 55 * See: {@link TextClassificationManager}. 56 * 57 * <p>Include the following in the manifest: 58 * 59 * <pre> 60 * {@literal 61 * <service android:name=".YourTextClassifierService" 62 * android:permission="android.permission.BIND_TEXTCLASSIFIER_SERVICE"> 63 * <intent-filter> 64 * <action android:name="android.service.textclassifier.TextClassifierService" /> 65 * </intent-filter> 66 * </service>}</pre> 67 * 68 * @see TextClassifier 69 * @hide 70 */ 71@SystemApi 72public abstract class TextClassifierService extends Service { 73 74 private static final String LOG_TAG = "TextClassifierService"; 75 76 /** 77 * The {@link Intent} that must be declared as handled by the service. 78 * To be supported, the service must also require the 79 * {@link android.Manifest.permission#BIND_TEXTCLASSIFIER_SERVICE} permission so 80 * that other applications can not abuse it. 81 */ 82 @SystemApi 83 public static final String SERVICE_INTERFACE = 84 "android.service.textclassifier.TextClassifierService"; 85 86 private final ITextClassifierService.Stub mBinder = new ITextClassifierService.Stub() { 87 88 // TODO(b/72533911): Implement cancellation signal 89 @NonNull private final CancellationSignal mCancellationSignal = new CancellationSignal(); 90 91 /** {@inheritDoc} */ 92 @Override 93 public void onSuggestSelection( 94 TextClassificationSessionId sessionId, 95 TextSelection.Request request, ITextSelectionCallback callback) 96 throws RemoteException { 97 Preconditions.checkNotNull(request); 98 Preconditions.checkNotNull(callback); 99 TextClassifierService.this.onSuggestSelection( 100 sessionId, request, mCancellationSignal, 101 new Callback<TextSelection>() { 102 @Override 103 public void onSuccess(TextSelection result) { 104 try { 105 callback.onSuccess(result); 106 } catch (RemoteException e) { 107 Slog.d(LOG_TAG, "Error calling callback"); 108 } 109 } 110 111 @Override 112 public void onFailure(CharSequence error) { 113 try { 114 if (callback.asBinder().isBinderAlive()) { 115 callback.onFailure(); 116 } 117 } catch (RemoteException e) { 118 Slog.d(LOG_TAG, "Error calling callback"); 119 } 120 } 121 }); 122 } 123 124 /** {@inheritDoc} */ 125 @Override 126 public void onClassifyText( 127 TextClassificationSessionId sessionId, 128 TextClassification.Request request, ITextClassificationCallback callback) 129 throws RemoteException { 130 Preconditions.checkNotNull(request); 131 Preconditions.checkNotNull(callback); 132 TextClassifierService.this.onClassifyText( 133 sessionId, request, mCancellationSignal, 134 new Callback<TextClassification>() { 135 @Override 136 public void onSuccess(TextClassification result) { 137 try { 138 callback.onSuccess(result); 139 } catch (RemoteException e) { 140 Slog.d(LOG_TAG, "Error calling callback"); 141 } 142 } 143 144 @Override 145 public void onFailure(CharSequence error) { 146 try { 147 callback.onFailure(); 148 } catch (RemoteException e) { 149 Slog.d(LOG_TAG, "Error calling callback"); 150 } 151 } 152 }); 153 } 154 155 /** {@inheritDoc} */ 156 @Override 157 public void onGenerateLinks( 158 TextClassificationSessionId sessionId, 159 TextLinks.Request request, ITextLinksCallback callback) 160 throws RemoteException { 161 Preconditions.checkNotNull(request); 162 Preconditions.checkNotNull(callback); 163 TextClassifierService.this.onGenerateLinks( 164 sessionId, request, mCancellationSignal, 165 new Callback<TextLinks>() { 166 @Override 167 public void onSuccess(TextLinks result) { 168 try { 169 callback.onSuccess(result); 170 } catch (RemoteException e) { 171 Slog.d(LOG_TAG, "Error calling callback"); 172 } 173 } 174 175 @Override 176 public void onFailure(CharSequence error) { 177 try { 178 callback.onFailure(); 179 } catch (RemoteException e) { 180 Slog.d(LOG_TAG, "Error calling callback"); 181 } 182 } 183 }); 184 } 185 186 /** {@inheritDoc} */ 187 @Override 188 public void onSelectionEvent( 189 TextClassificationSessionId sessionId, 190 SelectionEvent event) throws RemoteException { 191 Preconditions.checkNotNull(event); 192 TextClassifierService.this.onSelectionEvent(sessionId, event); 193 } 194 195 /** {@inheritDoc} */ 196 @Override 197 public void onCreateTextClassificationSession( 198 TextClassificationContext context, TextClassificationSessionId sessionId) 199 throws RemoteException { 200 Preconditions.checkNotNull(context); 201 Preconditions.checkNotNull(sessionId); 202 TextClassifierService.this.onCreateTextClassificationSession(context, sessionId); 203 } 204 205 /** {@inheritDoc} */ 206 @Override 207 public void onDestroyTextClassificationSession(TextClassificationSessionId sessionId) 208 throws RemoteException { 209 TextClassifierService.this.onDestroyTextClassificationSession(sessionId); 210 } 211 }; 212 213 @Nullable 214 @Override 215 public final IBinder onBind(Intent intent) { 216 if (SERVICE_INTERFACE.equals(intent.getAction())) { 217 return mBinder; 218 } 219 return null; 220 } 221 222 /** 223 * Returns suggested text selection start and end indices, recognized entity types, and their 224 * associated confidence scores. The entity types are ordered from highest to lowest scoring. 225 * 226 * @param sessionId the session id 227 * @param request the text selection request 228 * @param cancellationSignal object to watch for canceling the current operation 229 * @param callback the callback to return the result to 230 */ 231 public abstract void onSuggestSelection( 232 @Nullable TextClassificationSessionId sessionId, 233 @NonNull TextSelection.Request request, 234 @NonNull CancellationSignal cancellationSignal, 235 @NonNull Callback<TextSelection> callback); 236 237 /** 238 * Classifies the specified text and returns a {@link TextClassification} object that can be 239 * used to generate a widget for handling the classified text. 240 * 241 * @param sessionId the session id 242 * @param request the text classification request 243 * @param cancellationSignal object to watch for canceling the current operation 244 * @param callback the callback to return the result to 245 */ 246 public abstract void onClassifyText( 247 @Nullable TextClassificationSessionId sessionId, 248 @NonNull TextClassification.Request request, 249 @NonNull CancellationSignal cancellationSignal, 250 @NonNull Callback<TextClassification> callback); 251 252 /** 253 * Generates and returns a {@link TextLinks} that may be applied to the text to annotate it with 254 * links information. 255 * 256 * @param sessionId the session id 257 * @param request the text classification request 258 * @param cancellationSignal object to watch for canceling the current operation 259 * @param callback the callback to return the result to 260 */ 261 public abstract void onGenerateLinks( 262 @Nullable TextClassificationSessionId sessionId, 263 @NonNull TextLinks.Request request, 264 @NonNull CancellationSignal cancellationSignal, 265 @NonNull Callback<TextLinks> callback); 266 267 /** 268 * Writes the selection event. 269 * This is called when a selection event occurs. e.g. user changed selection; or smart selection 270 * happened. 271 * 272 * <p>The default implementation ignores the event. 273 * 274 * @param sessionId the session id 275 * @param event the selection event 276 */ 277 public void onSelectionEvent( 278 @Nullable TextClassificationSessionId sessionId, @NonNull SelectionEvent event) {} 279 280 /** 281 * Creates a new text classification session for the specified context. 282 * 283 * @param context the text classification context 284 * @param sessionId the session's Id 285 */ 286 public abstract void onCreateTextClassificationSession( 287 @NonNull TextClassificationContext context, 288 @NonNull TextClassificationSessionId sessionId); 289 290 /** 291 * Destroys the text classification session identified by the specified sessionId. 292 * 293 * @param sessionId the id of the session to destroy 294 */ 295 public abstract void onDestroyTextClassificationSession( 296 @NonNull TextClassificationSessionId sessionId); 297 298 /** 299 * Returns a TextClassifier that runs in this service's process. 300 * If the local TextClassifier is disabled, this returns {@link TextClassifier#NO_OP}. 301 */ 302 public final TextClassifier getLocalTextClassifier() { 303 final TextClassificationManager tcm = getSystemService(TextClassificationManager.class); 304 if (tcm != null) { 305 return tcm.getTextClassifier(TextClassifier.LOCAL); 306 } 307 return TextClassifier.NO_OP; 308 } 309 310 /** 311 * Callbacks for TextClassifierService results. 312 * 313 * @param <T> the type of the result 314 * @hide 315 */ 316 @SystemApi 317 public interface Callback<T> { 318 /** 319 * Returns the result. 320 */ 321 void onSuccess(T result); 322 323 /** 324 * Signals a failure. 325 */ 326 void onFailure(CharSequence error); 327 } 328 329 /** 330 * Returns the component name of the system default textclassifier service if it can be found 331 * on the system. Otherwise, returns null. 332 * @hide 333 */ 334 @Nullable 335 public static ComponentName getServiceComponentName(Context context) { 336 final String packageName = context.getPackageManager().getSystemTextClassifierPackageName(); 337 if (TextUtils.isEmpty(packageName)) { 338 Slog.d(LOG_TAG, "No configured system TextClassifierService"); 339 return null; 340 } 341 342 final Intent intent = new Intent(SERVICE_INTERFACE).setPackage(packageName); 343 344 final ResolveInfo ri = context.getPackageManager().resolveService(intent, 345 PackageManager.MATCH_SYSTEM_ONLY); 346 347 if ((ri == null) || (ri.serviceInfo == null)) { 348 Slog.w(LOG_TAG, String.format("Package or service not found in package %s", 349 packageName)); 350 return null; 351 } 352 final ServiceInfo si = ri.serviceInfo; 353 354 final String permission = si.permission; 355 if (Manifest.permission.BIND_TEXTCLASSIFIER_SERVICE.equals(permission)) { 356 return si.getComponentName(); 357 } 358 Slog.w(LOG_TAG, String.format( 359 "Service %s should require %s permission. Found %s permission", 360 si.getComponentName(), 361 Manifest.permission.BIND_TEXTCLASSIFIER_SERVICE, 362 si.permission)); 363 return null; 364 } 365} 366