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