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 android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.SystemService; 22 import android.content.Context; 23 import android.database.ContentObserver; 24 import android.os.ServiceManager; 25 import android.provider.Settings; 26 import android.service.textclassifier.TextClassifierService; 27 import android.view.textclassifier.TextClassifier.TextClassifierType; 28 29 import com.android.internal.annotations.GuardedBy; 30 import com.android.internal.util.Preconditions; 31 32 import java.lang.ref.WeakReference; 33 34 /** 35 * Interface to the text classification service. 36 */ 37 @SystemService(Context.TEXT_CLASSIFICATION_SERVICE) 38 public final class TextClassificationManager { 39 40 private static final String LOG_TAG = "TextClassificationManager"; 41 42 private final Object mLock = new Object(); 43 private final TextClassificationSessionFactory mDefaultSessionFactory = 44 classificationContext -> new TextClassificationSession( 45 classificationContext, getTextClassifier()); 46 47 private final Context mContext; 48 private final SettingsObserver mSettingsObserver; 49 50 @GuardedBy("mLock") 51 @Nullable 52 private TextClassifier mCustomTextClassifier; 53 @GuardedBy("mLock") 54 @Nullable 55 private TextClassifier mLocalTextClassifier; 56 @GuardedBy("mLock") 57 @Nullable 58 private TextClassifier mSystemTextClassifier; 59 @GuardedBy("mLock") 60 private TextClassificationSessionFactory mSessionFactory; 61 @GuardedBy("mLock") 62 private TextClassificationConstants mSettings; 63 64 /** @hide */ 65 public TextClassificationManager(Context context) { 66 mContext = Preconditions.checkNotNull(context); 67 mSessionFactory = mDefaultSessionFactory; 68 mSettingsObserver = new SettingsObserver(this); 69 } 70 71 /** 72 * Returns the text classifier that was set via {@link #setTextClassifier(TextClassifier)}. 73 * If this is null, this method returns a default text classifier (i.e. either the system text 74 * classifier if one exists, or a local text classifier running in this app.) 75 * 76 * @see #setTextClassifier(TextClassifier) 77 */ 78 @NonNull 79 public TextClassifier getTextClassifier() { 80 synchronized (mLock) { 81 if (mCustomTextClassifier != null) { 82 return mCustomTextClassifier; 83 } else if (isSystemTextClassifierEnabled()) { 84 return getSystemTextClassifier(); 85 } else { 86 return getLocalTextClassifier(); 87 } 88 } 89 } 90 91 /** 92 * Sets the text classifier. 93 * Set to null to use the system default text classifier. 94 * Set to {@link TextClassifier#NO_OP} to disable text classifier features. 95 */ 96 public void setTextClassifier(@Nullable TextClassifier textClassifier) { 97 synchronized (mLock) { 98 mCustomTextClassifier = textClassifier; 99 } 100 } 101 102 /** 103 * Returns a specific type of text classifier. 104 * If the specified text classifier cannot be found, this returns {@link TextClassifier#NO_OP}. 105 * 106 * @see TextClassifier#LOCAL 107 * @see TextClassifier#SYSTEM 108 * @hide 109 */ 110 public TextClassifier getTextClassifier(@TextClassifierType int type) { 111 switch (type) { 112 case TextClassifier.LOCAL: 113 return getLocalTextClassifier(); 114 default: 115 return getSystemTextClassifier(); 116 } 117 } 118 119 private TextClassificationConstants getSettings() { 120 synchronized (mLock) { 121 if (mSettings == null) { 122 mSettings = TextClassificationConstants.loadFromString(Settings.Global.getString( 123 getApplicationContext().getContentResolver(), 124 Settings.Global.TEXT_CLASSIFIER_CONSTANTS)); 125 } 126 return mSettings; 127 } 128 } 129 130 /** 131 * Call this method to start a text classification session with the given context. 132 * A session is created with a context helping the classifier better understand 133 * what the user needs and consists of queries and feedback events. The queries 134 * are directly related to providing useful functionality to the user and the events 135 * are a feedback loop back to the classifier helping it learn and better serve 136 * future queries. 137 * 138 * <p> All interactions with the returned classifier are considered part of a single 139 * session and are logically grouped. For example, when a text widget is focused 140 * all user interactions around text editing (selection, editing, etc) can be 141 * grouped together to allow the classifier get better. 142 * 143 * @param classificationContext The context in which classification would occur 144 * 145 * @return An instance to perform classification in the given context 146 */ 147 @NonNull 148 public TextClassifier createTextClassificationSession( 149 @NonNull TextClassificationContext classificationContext) { 150 Preconditions.checkNotNull(classificationContext); 151 final TextClassifier textClassifier = 152 mSessionFactory.createTextClassificationSession(classificationContext); 153 Preconditions.checkNotNull(textClassifier, "Session Factory should never return null"); 154 return textClassifier; 155 } 156 157 /** 158 * @see #createTextClassificationSession(TextClassificationContext, TextClassifier) 159 * @hide 160 */ 161 public TextClassifier createTextClassificationSession( 162 TextClassificationContext classificationContext, TextClassifier textClassifier) { 163 Preconditions.checkNotNull(classificationContext); 164 Preconditions.checkNotNull(textClassifier); 165 return new TextClassificationSession(classificationContext, textClassifier); 166 } 167 168 /** 169 * Sets a TextClassificationSessionFactory to be used to create session-aware TextClassifiers. 170 * 171 * @param factory the textClassification session factory. If this is null, the default factory 172 * will be used. 173 */ 174 public void setTextClassificationSessionFactory( 175 @Nullable TextClassificationSessionFactory factory) { 176 synchronized (mLock) { 177 if (factory != null) { 178 mSessionFactory = factory; 179 } else { 180 mSessionFactory = mDefaultSessionFactory; 181 } 182 } 183 } 184 185 @Override 186 protected void finalize() throws Throwable { 187 try { 188 // Note that fields could be null if the constructor threw. 189 if (mSettingsObserver != null) { 190 getApplicationContext().getContentResolver() 191 .unregisterContentObserver(mSettingsObserver); 192 } 193 } finally { 194 super.finalize(); 195 } 196 } 197 198 private TextClassifier getSystemTextClassifier() { 199 synchronized (mLock) { 200 if (mSystemTextClassifier == null && isSystemTextClassifierEnabled()) { 201 try { 202 mSystemTextClassifier = new SystemTextClassifier(mContext, getSettings()); 203 Log.d(LOG_TAG, "Initialized SystemTextClassifier"); 204 } catch (ServiceManager.ServiceNotFoundException e) { 205 Log.e(LOG_TAG, "Could not initialize SystemTextClassifier", e); 206 } 207 } 208 } 209 if (mSystemTextClassifier != null) { 210 return mSystemTextClassifier; 211 } 212 return TextClassifier.NO_OP; 213 } 214 215 private TextClassifier getLocalTextClassifier() { 216 synchronized (mLock) { 217 if (mLocalTextClassifier == null) { 218 if (getSettings().isLocalTextClassifierEnabled()) { 219 mLocalTextClassifier = 220 new TextClassifierImpl(mContext, getSettings(), TextClassifier.NO_OP); 221 } else { 222 Log.d(LOG_TAG, "Local TextClassifier disabled"); 223 mLocalTextClassifier = TextClassifier.NO_OP; 224 } 225 } 226 return mLocalTextClassifier; 227 } 228 } 229 230 private boolean isSystemTextClassifierEnabled() { 231 return getSettings().isSystemTextClassifierEnabled() 232 && TextClassifierService.getServiceComponentName(mContext) != null; 233 } 234 235 private void invalidate() { 236 synchronized (mLock) { 237 mSettings = null; 238 mLocalTextClassifier = null; 239 mSystemTextClassifier = null; 240 } 241 } 242 243 Context getApplicationContext() { 244 return mContext.getApplicationContext() != null 245 ? mContext.getApplicationContext() 246 : mContext; 247 } 248 249 /** @hide */ 250 public static TextClassificationConstants getSettings(Context context) { 251 Preconditions.checkNotNull(context); 252 final TextClassificationManager tcm = 253 context.getSystemService(TextClassificationManager.class); 254 if (tcm != null) { 255 return tcm.getSettings(); 256 } else { 257 return TextClassificationConstants.loadFromString(Settings.Global.getString( 258 context.getApplicationContext().getContentResolver(), 259 Settings.Global.TEXT_CLASSIFIER_CONSTANTS)); 260 } 261 } 262 263 private static final class SettingsObserver extends ContentObserver { 264 265 private final WeakReference<TextClassificationManager> mTcm; 266 267 SettingsObserver(TextClassificationManager tcm) { 268 super(null); 269 mTcm = new WeakReference<>(tcm); 270 tcm.getApplicationContext().getContentResolver().registerContentObserver( 271 Settings.Global.getUriFor(Settings.Global.TEXT_CLASSIFIER_CONSTANTS), 272 false /* notifyForDescendants */, 273 this); 274 } 275 276 @Override 277 public void onChange(boolean selfChange) { 278 final TextClassificationManager tcm = mTcm.get(); 279 if (tcm != null) { 280 tcm.invalidate(); 281 } 282 } 283 } 284 } 285