blob: 95ca9deb28718475801fb1bd21b36f6067de7d41 [file] [log] [blame]
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +00001/*
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
17package android.view.textclassifier;
18
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +000019import android.annotation.NonNull;
Abodunrinwa Tokidf61b032017-03-29 22:41:57 +010020import android.annotation.Nullable;
Jeff Sharkeyd86b8fe2017-06-02 17:36:26 -060021import android.annotation.SystemService;
Mathew Inwooda570dee2018-08-17 14:56:00 +010022import android.annotation.UnsupportedAppUsage;
Tony Makfc374572019-03-05 14:46:24 +000023import android.app.ActivityThread;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000024import android.content.Context;
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +010025import android.database.ContentObserver;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080026import android.os.ServiceManager;
Tony Makfc374572019-03-05 14:46:24 +000027import android.provider.DeviceConfig;
Matt Pape15769e22019-04-19 12:31:24 -070028import android.provider.DeviceConfig.Properties;
Abodunrinwa Tokidb8fc312018-02-26 21:37:51 +000029import android.provider.Settings;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080030import android.service.textclassifier.TextClassifierService;
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +000031import android.view.textclassifier.TextClassifier.TextClassifierType;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000032
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +000033import com.android.internal.annotations.GuardedBy;
Tony Mak751afc92019-04-02 18:23:54 +010034import com.android.internal.annotations.VisibleForTesting;
Tony Makf93e9e52018-07-16 14:46:29 +020035import com.android.internal.util.IndentingPrintWriter;
Abodunrinwa Toki43e03502017-01-13 13:46:33 -080036import com.android.internal.util.Preconditions;
37
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +010038import java.lang.ref.WeakReference;
39
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000040/**
41 * Interface to the text classification service.
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000042 */
Jeff Sharkeyd86b8fe2017-06-02 17:36:26 -060043@SystemService(Context.TEXT_CLASSIFICATION_SERVICE)
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000044public final class TextClassificationManager {
45
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080046 private static final String LOG_TAG = "TextClassificationManager";
47
Abodunrinwa Toki0634af32019-04-04 13:10:59 +010048 private static final TextClassificationConstants sDefaultSettings =
49 new TextClassificationConstants(() -> null);
50
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080051 private final Object mLock = new Object();
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +000052 private final TextClassificationSessionFactory mDefaultSessionFactory =
53 classificationContext -> new TextClassificationSession(
54 classificationContext, getTextClassifier());
Abodunrinwa Tokib89cf022017-02-06 19:53:22 +000055
Abodunrinwa Toki43e03502017-01-13 13:46:33 -080056 private final Context mContext;
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +010057 private final SettingsObserver mSettingsObserver;
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +000058
59 @GuardedBy("mLock")
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +010060 @Nullable
61 private TextClassifier mCustomTextClassifier;
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +000062 @GuardedBy("mLock")
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +010063 @Nullable
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +000064 private TextClassifier mLocalTextClassifier;
65 @GuardedBy("mLock")
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +010066 @Nullable
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080067 private TextClassifier mSystemTextClassifier;
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +000068 @GuardedBy("mLock")
69 private TextClassificationSessionFactory mSessionFactory;
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +010070 @GuardedBy("mLock")
71 private TextClassificationConstants mSettings;
Abodunrinwa Toki43e03502017-01-13 13:46:33 -080072
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000073 /** @hide */
Abodunrinwa Toki43e03502017-01-13 13:46:33 -080074 public TextClassificationManager(Context context) {
75 mContext = Preconditions.checkNotNull(context);
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +000076 mSessionFactory = mDefaultSessionFactory;
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +010077 mSettingsObserver = new SettingsObserver(this);
Abodunrinwa Toki43e03502017-01-13 13:46:33 -080078 }
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000079
Abodunrinwa Tokidf61b032017-03-29 22:41:57 +010080 /**
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +000081 * Returns the text classifier that was set via {@link #setTextClassifier(TextClassifier)}.
82 * If this is null, this method returns a default text classifier (i.e. either the system text
Tony Mak31ff1352019-01-22 21:26:05 +000083 * classifier if one exists, or a local text classifier running in this process.)
84 * <p>
Abodunrinwa Toki324572e2019-02-12 19:01:01 +000085 * Note that requests to the TextClassifier may be handled in an OEM-provided process rather
86 * than in the calling app's process.
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +000087 *
88 * @see #setTextClassifier(TextClassifier)
Abodunrinwa Tokidf61b032017-03-29 22:41:57 +010089 */
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +000090 @NonNull
Abodunrinwa Tokidf61b032017-03-29 22:41:57 +010091 public TextClassifier getTextClassifier() {
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080092 synchronized (mLock) {
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +010093 if (mCustomTextClassifier != null) {
94 return mCustomTextClassifier;
95 } else if (isSystemTextClassifierEnabled()) {
96 return getSystemTextClassifier();
97 } else {
98 return getLocalTextClassifier();
Abodunrinwa Toki43e03502017-01-13 13:46:33 -080099 }
Abodunrinwa Tokidf61b032017-03-29 22:41:57 +0100100 }
101 }
102
103 /**
104 * Sets the text classifier.
105 * Set to null to use the system default text classifier.
106 * Set to {@link TextClassifier#NO_OP} to disable text classifier features.
107 */
108 public void setTextClassifier(@Nullable TextClassifier textClassifier) {
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800109 synchronized (mLock) {
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +0100110 mCustomTextClassifier = textClassifier;
Abodunrinwa Toki43e03502017-01-13 13:46:33 -0800111 }
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000112 }
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800113
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +0000114 /**
115 * Returns a specific type of text classifier.
116 * If the specified text classifier cannot be found, this returns {@link TextClassifier#NO_OP}.
117 *
118 * @see TextClassifier#LOCAL
119 * @see TextClassifier#SYSTEM
120 * @hide
121 */
Mathew Inwooda570dee2018-08-17 14:56:00 +0100122 @UnsupportedAppUsage
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +0000123 public TextClassifier getTextClassifier(@TextClassifierType int type) {
124 switch (type) {
125 case TextClassifier.LOCAL:
126 return getLocalTextClassifier();
127 default:
128 return getSystemTextClassifier();
129 }
130 }
131
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +0100132 private TextClassificationConstants getSettings() {
133 synchronized (mLock) {
134 if (mSettings == null) {
Abodunrinwa Toki0634af32019-04-04 13:10:59 +0100135 mSettings = new TextClassificationConstants(
136 () -> Settings.Global.getString(
137 getApplicationContext().getContentResolver(),
138 Settings.Global.TEXT_CLASSIFIER_CONSTANTS));
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +0100139 }
140 return mSettings;
141 }
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +0000142 }
143
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000144 /**
145 * Call this method to start a text classification session with the given context.
146 * A session is created with a context helping the classifier better understand
147 * what the user needs and consists of queries and feedback events. The queries
148 * are directly related to providing useful functionality to the user and the events
149 * are a feedback loop back to the classifier helping it learn and better serve
150 * future queries.
151 *
152 * <p> All interactions with the returned classifier are considered part of a single
153 * session and are logically grouped. For example, when a text widget is focused
154 * all user interactions around text editing (selection, editing, etc) can be
155 * grouped together to allow the classifier get better.
156 *
157 * @param classificationContext The context in which classification would occur
158 *
159 * @return An instance to perform classification in the given context
160 */
161 @NonNull
162 public TextClassifier createTextClassificationSession(
163 @NonNull TextClassificationContext classificationContext) {
164 Preconditions.checkNotNull(classificationContext);
165 final TextClassifier textClassifier =
166 mSessionFactory.createTextClassificationSession(classificationContext);
167 Preconditions.checkNotNull(textClassifier, "Session Factory should never return null");
168 return textClassifier;
169 }
170
171 /**
172 * @see #createTextClassificationSession(TextClassificationContext, TextClassifier)
173 * @hide
174 */
175 public TextClassifier createTextClassificationSession(
176 TextClassificationContext classificationContext, TextClassifier textClassifier) {
177 Preconditions.checkNotNull(classificationContext);
178 Preconditions.checkNotNull(textClassifier);
179 return new TextClassificationSession(classificationContext, textClassifier);
180 }
181
182 /**
183 * Sets a TextClassificationSessionFactory to be used to create session-aware TextClassifiers.
184 *
185 * @param factory the textClassification session factory. If this is null, the default factory
186 * will be used.
187 */
188 public void setTextClassificationSessionFactory(
189 @Nullable TextClassificationSessionFactory factory) {
190 synchronized (mLock) {
191 if (factory != null) {
192 mSessionFactory = factory;
193 } else {
194 mSessionFactory = mDefaultSessionFactory;
195 }
196 }
197 }
198
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +0100199 @Override
200 protected void finalize() throws Throwable {
201 try {
202 // Note that fields could be null if the constructor threw.
Michael Wrightb112f262018-05-24 13:41:50 +0100203 if (mSettingsObserver != null) {
204 getApplicationContext().getContentResolver()
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +0100205 .unregisterContentObserver(mSettingsObserver);
John Reckbcc0b912019-03-29 14:49:05 -0700206 if (ConfigParser.ENABLE_DEVICE_CONFIG) {
Matt Pape15769e22019-04-19 12:31:24 -0700207 DeviceConfig.removeOnPropertiesChangedListener(mSettingsObserver);
John Reckbcc0b912019-03-29 14:49:05 -0700208 }
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +0100209 }
210 } finally {
211 super.finalize();
212 }
213 }
214
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +0000215 private TextClassifier getSystemTextClassifier() {
216 synchronized (mLock) {
217 if (mSystemTextClassifier == null && isSystemTextClassifierEnabled()) {
218 try {
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +0100219 mSystemTextClassifier = new SystemTextClassifier(mContext, getSettings());
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +0000220 Log.d(LOG_TAG, "Initialized SystemTextClassifier");
221 } catch (ServiceManager.ServiceNotFoundException e) {
222 Log.e(LOG_TAG, "Could not initialize SystemTextClassifier", e);
223 }
224 }
225 }
226 if (mSystemTextClassifier != null) {
227 return mSystemTextClassifier;
228 }
229 return TextClassifier.NO_OP;
230 }
231
Tony Mak31ff1352019-01-22 21:26:05 +0000232 /**
233 * Returns a local textclassifier, which is running in this process.
Tony Mak31ff1352019-01-22 21:26:05 +0000234 */
235 @NonNull
Abodunrinwa Toki324572e2019-02-12 19:01:01 +0000236 private TextClassifier getLocalTextClassifier() {
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +0000237 synchronized (mLock) {
238 if (mLocalTextClassifier == null) {
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +0100239 if (getSettings().isLocalTextClassifierEnabled()) {
Abodunrinwa Toki253827f2018-04-24 19:19:48 +0100240 mLocalTextClassifier =
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +0100241 new TextClassifierImpl(mContext, getSettings(), TextClassifier.NO_OP);
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +0000242 } else {
243 Log.d(LOG_TAG, "Local TextClassifier disabled");
Abodunrinwa Toki253827f2018-04-24 19:19:48 +0100244 mLocalTextClassifier = TextClassifier.NO_OP;
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +0000245 }
246 }
247 return mLocalTextClassifier;
248 }
249 }
250
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800251 private boolean isSystemTextClassifierEnabled() {
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +0100252 return getSettings().isSystemTextClassifierEnabled()
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800253 && TextClassifierService.getServiceComponentName(mContext) != null;
254 }
Abodunrinwa Tokidb8fc312018-02-26 21:37:51 +0000255
Tony Mak751afc92019-04-02 18:23:54 +0100256 /** @hide */
257 @VisibleForTesting
Abodunrinwa Toki0634af32019-04-04 13:10:59 +0100258 public void invalidateForTesting() {
259 invalidate();
260 }
261
262 private void invalidate() {
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +0100263 synchronized (mLock) {
264 mSettings = null;
265 mLocalTextClassifier = null;
266 mSystemTextClassifier = null;
267 }
268 }
269
Michael Wrightb112f262018-05-24 13:41:50 +0100270 Context getApplicationContext() {
271 return mContext.getApplicationContext() != null
272 ? mContext.getApplicationContext()
273 : mContext;
274 }
275
Tony Makf93e9e52018-07-16 14:46:29 +0200276 /** @hide **/
277 public void dump(IndentingPrintWriter pw) {
278 getLocalTextClassifier().dump(pw);
279 getSystemTextClassifier().dump(pw);
280 getSettings().dump(pw);
281 }
282
Abodunrinwa Tokidb8fc312018-02-26 21:37:51 +0000283 /** @hide */
284 public static TextClassificationConstants getSettings(Context context) {
285 Preconditions.checkNotNull(context);
286 final TextClassificationManager tcm =
287 context.getSystemService(TextClassificationManager.class);
288 if (tcm != null) {
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +0100289 return tcm.getSettings();
Abodunrinwa Tokidb8fc312018-02-26 21:37:51 +0000290 } else {
Abodunrinwa Toki0634af32019-04-04 13:10:59 +0100291 // Use default settings if there is no tcm.
292 return sDefaultSettings;
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +0100293 }
294 }
295
Tony Makfc374572019-03-05 14:46:24 +0000296 private static final class SettingsObserver extends ContentObserver
Matt Pape15769e22019-04-19 12:31:24 -0700297 implements DeviceConfig.OnPropertiesChangedListener {
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +0100298
299 private final WeakReference<TextClassificationManager> mTcm;
300
301 SettingsObserver(TextClassificationManager tcm) {
302 super(null);
303 mTcm = new WeakReference<>(tcm);
Michael Wrightb112f262018-05-24 13:41:50 +0100304 tcm.getApplicationContext().getContentResolver().registerContentObserver(
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +0100305 Settings.Global.getUriFor(Settings.Global.TEXT_CLASSIFIER_CONSTANTS),
306 false /* notifyForDescendants */,
307 this);
John Reckbcc0b912019-03-29 14:49:05 -0700308 if (ConfigParser.ENABLE_DEVICE_CONFIG) {
Matt Pape15769e22019-04-19 12:31:24 -0700309 DeviceConfig.addOnPropertiesChangedListener(
John Reckbcc0b912019-03-29 14:49:05 -0700310 DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
311 ActivityThread.currentApplication().getMainExecutor(),
312 this);
313 }
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +0100314 }
315
316 @Override
317 public void onChange(boolean selfChange) {
Tony Makfc374572019-03-05 14:46:24 +0000318 invalidateSettings();
319 }
320
321 @Override
Matt Pape15769e22019-04-19 12:31:24 -0700322 public void onPropertiesChanged(Properties properties) {
Tony Makfc374572019-03-05 14:46:24 +0000323 invalidateSettings();
324 }
325
326 private void invalidateSettings() {
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +0100327 final TextClassificationManager tcm = mTcm.get();
328 if (tcm != null) {
329 tcm.invalidate();
330 }
Abodunrinwa Tokidb8fc312018-02-26 21:37:51 +0000331 }
332 }
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000333}