1 /* 2 * Copyright (C) 2017 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 17 package android.view.textclassifier; 18 19 import static android.Manifest.permission.ACCESS_TEXT_CLASSIFIER_BY_TYPE; 20 21 import android.annotation.FlaggedApi; 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.annotation.RequiresPermission; 25 import android.annotation.SystemApi; 26 import android.annotation.SystemService; 27 import android.compat.annotation.UnsupportedAppUsage; 28 import android.content.Context; 29 import android.content.pm.PackageManager; 30 import android.os.Build; 31 import android.os.ServiceManager; 32 import android.permission.flags.Flags; 33 import android.view.textclassifier.TextClassifier.TextClassifierType; 34 35 import com.android.internal.annotations.GuardedBy; 36 import com.android.internal.util.IndentingPrintWriter; 37 38 import java.util.Objects; 39 40 /** 41 * Interface to the text classification service. 42 */ 43 @SystemService(Context.TEXT_CLASSIFICATION_SERVICE) 44 public final class TextClassificationManager { 45 46 private static final String LOG_TAG = TextClassifier.LOG_TAG; 47 48 private static final TextClassificationConstants sDefaultSettings = 49 new TextClassificationConstants(); 50 51 private final Object mLock = new Object(); 52 private final TextClassificationSessionFactory mDefaultSessionFactory = 53 classificationContext -> new TextClassificationSession( 54 classificationContext, getTextClassifier()); 55 56 private final Context mContext; 57 58 @GuardedBy("mLock") 59 @Nullable 60 private TextClassifier mCustomTextClassifier; 61 @GuardedBy("mLock") 62 private TextClassificationSessionFactory mSessionFactory; 63 @GuardedBy("mLock") 64 private TextClassificationConstants mSettings; 65 66 /** @hide */ TextClassificationManager(Context context)67 public TextClassificationManager(Context context) { 68 mContext = Objects.requireNonNull(context); 69 mSessionFactory = mDefaultSessionFactory; 70 } 71 72 /** 73 * Returns the text classifier that was set via {@link #setTextClassifier(TextClassifier)}. 74 * If this is null, this method returns a default text classifier (i.e. either the system text 75 * classifier if one exists, or a local text classifier running in this process.) 76 * <p> 77 * Note that requests to the TextClassifier may be handled in an OEM-provided process rather 78 * than in the calling app's process. 79 * 80 * @see #setTextClassifier(TextClassifier) 81 */ 82 @NonNull getTextClassifier()83 public TextClassifier getTextClassifier() { 84 synchronized (mLock) { 85 if (mCustomTextClassifier != null) { 86 return mCustomTextClassifier; 87 } else if (getSettings().isSystemTextClassifierEnabled()) { 88 return getSystemTextClassifier(SystemTextClassifier.SYSTEM); 89 } else { 90 return getLocalTextClassifier(); 91 } 92 } 93 } 94 95 /** 96 * Sets the text classifier. 97 * Set to null to use the system default text classifier. 98 * Set to {@link TextClassifier#NO_OP} to disable text classifier features. 99 */ setTextClassifier(@ullable TextClassifier textClassifier)100 public void setTextClassifier(@Nullable TextClassifier textClassifier) { 101 synchronized (mLock) { 102 mCustomTextClassifier = textClassifier; 103 } 104 } 105 106 /** 107 * Returns a specific type of text classifier. 108 * If the specified text classifier cannot be found, this returns {@link TextClassifier#NO_OP}. 109 * 110 * @see TextClassifier#LOCAL 111 * @see TextClassifier#SYSTEM 112 * @see TextClassifier#DEFAULT_SYSTEM 113 * @hide 114 */ 115 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) getTextClassifier(@extClassifierType int type)116 public TextClassifier getTextClassifier(@TextClassifierType int type) { 117 switch (type) { 118 case TextClassifier.LOCAL: 119 return getLocalTextClassifier(); 120 default: 121 return getSystemTextClassifier(type); 122 } 123 } 124 125 /** 126 * Returns a specific type of text classifier. 127 * If the specified text classifier cannot be found, this returns {@link TextClassifier#NO_OP}. 128 * <p> 129 * 130 * @see TextClassifier#CLASSIFIER_TYPE_SELF_PROVIDED 131 * @see TextClassifier#CLASSIFIER_TYPE_DEVICE_DEFAULT 132 * @see TextClassifier#CLASSIFIER_TYPE_ANDROID_DEFAULT 133 * @hide 134 */ 135 @SystemApi 136 @NonNull 137 @FlaggedApi(Flags.FLAG_TEXT_CLASSIFIER_CHOICE_API_ENABLED) 138 @RequiresPermission(ACCESS_TEXT_CLASSIFIER_BY_TYPE) getClassifier(@extClassifierType int type)139 public TextClassifier getClassifier(@TextClassifierType int type) { 140 if (mContext.checkCallingOrSelfPermission(ACCESS_TEXT_CLASSIFIER_BY_TYPE) 141 != PackageManager.PERMISSION_GRANTED) { 142 throw new SecurityException( 143 "Caller does not have permission " + ACCESS_TEXT_CLASSIFIER_BY_TYPE); 144 } 145 return getTextClassifier(type); 146 } 147 getSettings()148 private TextClassificationConstants getSettings() { 149 synchronized (mLock) { 150 if (mSettings == null) { 151 mSettings = new TextClassificationConstants(); 152 } 153 return mSettings; 154 } 155 } 156 157 /** 158 * Call this method to start a text classification session with the given context. 159 * A session is created with a context helping the classifier better understand 160 * what the user needs and consists of queries and feedback events. The queries 161 * are directly related to providing useful functionality to the user and the events 162 * are a feedback loop back to the classifier helping it learn and better serve 163 * future queries. 164 * 165 * <p> All interactions with the returned classifier are considered part of a single 166 * session and are logically grouped. For example, when a text widget is focused 167 * all user interactions around text editing (selection, editing, etc) can be 168 * grouped together to allow the classifier get better. 169 * 170 * @param classificationContext The context in which classification would occur 171 * 172 * @return An instance to perform classification in the given context 173 */ 174 @NonNull createTextClassificationSession( @onNull TextClassificationContext classificationContext)175 public TextClassifier createTextClassificationSession( 176 @NonNull TextClassificationContext classificationContext) { 177 Objects.requireNonNull(classificationContext); 178 final TextClassifier textClassifier = 179 mSessionFactory.createTextClassificationSession(classificationContext); 180 Objects.requireNonNull(textClassifier, "Session Factory should never return null"); 181 return textClassifier; 182 } 183 184 /** 185 * @see #createTextClassificationSession(TextClassificationContext, TextClassifier) 186 * @hide 187 */ createTextClassificationSession( TextClassificationContext classificationContext, TextClassifier textClassifier)188 public TextClassifier createTextClassificationSession( 189 TextClassificationContext classificationContext, TextClassifier textClassifier) { 190 Objects.requireNonNull(classificationContext); 191 Objects.requireNonNull(textClassifier); 192 return new TextClassificationSession(classificationContext, textClassifier); 193 } 194 195 /** 196 * Sets a TextClassificationSessionFactory to be used to create session-aware TextClassifiers. 197 * 198 * @param factory the textClassification session factory. If this is null, the default factory 199 * will be used. 200 */ setTextClassificationSessionFactory( @ullable TextClassificationSessionFactory factory)201 public void setTextClassificationSessionFactory( 202 @Nullable TextClassificationSessionFactory factory) { 203 synchronized (mLock) { 204 if (factory != null) { 205 mSessionFactory = factory; 206 } else { 207 mSessionFactory = mDefaultSessionFactory; 208 } 209 } 210 } 211 212 /** @hide */ getSystemTextClassifier(@extClassifierType int type)213 private TextClassifier getSystemTextClassifier(@TextClassifierType int type) { 214 synchronized (mLock) { 215 if (getSettings().isSystemTextClassifierEnabled()) { 216 try { 217 Log.d(LOG_TAG, "Initializing SystemTextClassifier, type = " 218 + TextClassifier.typeToString(type)); 219 return new SystemTextClassifier( 220 mContext, 221 getSettings(), 222 /* useDefault= */ type == TextClassifier.DEFAULT_SYSTEM); 223 } catch (ServiceManager.ServiceNotFoundException e) { 224 Log.e(LOG_TAG, "Could not initialize SystemTextClassifier", e); 225 } 226 } 227 return TextClassifier.NO_OP; 228 } 229 } 230 231 /** 232 * Returns a local textclassifier, which is running in this process. 233 */ 234 @NonNull getLocalTextClassifier()235 private TextClassifier getLocalTextClassifier() { 236 Log.d(LOG_TAG, "Local text-classifier not supported. Returning a no-op text-classifier."); 237 return TextClassifier.NO_OP; 238 } 239 240 /** @hide **/ dump(IndentingPrintWriter pw)241 public void dump(IndentingPrintWriter pw) { 242 getSystemTextClassifier(TextClassifier.DEFAULT_SYSTEM).dump(pw); 243 getSystemTextClassifier(TextClassifier.SYSTEM).dump(pw); 244 getSettings().dump(pw); 245 } 246 247 /** @hide */ getSettings(Context context)248 public static TextClassificationConstants getSettings(Context context) { 249 Objects.requireNonNull(context); 250 final TextClassificationManager tcm = 251 context.getSystemService(TextClassificationManager.class); 252 if (tcm != null) { 253 return tcm.getSettings(); 254 } else { 255 // Use default settings if there is no tcm. 256 return sDefaultSettings; 257 } 258 } 259 } 260