blob: 868cbb1ce58e912adf3f7715fb0477200387e424 [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;
Abodunrinwa Tokidb8fc312018-02-26 21:37:51 +000028import android.provider.Settings;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080029import android.service.textclassifier.TextClassifierService;
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +000030import android.view.textclassifier.TextClassifier.TextClassifierType;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000031
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +000032import com.android.internal.annotations.GuardedBy;
Tony Makf93e9e52018-07-16 14:46:29 +020033import com.android.internal.util.IndentingPrintWriter;
Abodunrinwa Toki43e03502017-01-13 13:46:33 -080034import com.android.internal.util.Preconditions;
35
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +010036import java.lang.ref.WeakReference;
37
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000038/**
39 * Interface to the text classification service.
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000040 */
Jeff Sharkeyd86b8fe2017-06-02 17:36:26 -060041@SystemService(Context.TEXT_CLASSIFICATION_SERVICE)
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000042public final class TextClassificationManager {
43
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080044 private static final String LOG_TAG = "TextClassificationManager";
45
46 private final Object mLock = new Object();
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +000047 private final TextClassificationSessionFactory mDefaultSessionFactory =
48 classificationContext -> new TextClassificationSession(
49 classificationContext, getTextClassifier());
Abodunrinwa Tokib89cf022017-02-06 19:53:22 +000050
Abodunrinwa Toki43e03502017-01-13 13:46:33 -080051 private final Context mContext;
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +010052 private final SettingsObserver mSettingsObserver;
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +000053
54 @GuardedBy("mLock")
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +010055 @Nullable
56 private TextClassifier mCustomTextClassifier;
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +000057 @GuardedBy("mLock")
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +010058 @Nullable
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +000059 private TextClassifier mLocalTextClassifier;
60 @GuardedBy("mLock")
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +010061 @Nullable
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080062 private TextClassifier mSystemTextClassifier;
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +000063 @GuardedBy("mLock")
64 private TextClassificationSessionFactory mSessionFactory;
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +010065 @GuardedBy("mLock")
66 private TextClassificationConstants mSettings;
Abodunrinwa Toki43e03502017-01-13 13:46:33 -080067
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000068 /** @hide */
Abodunrinwa Toki43e03502017-01-13 13:46:33 -080069 public TextClassificationManager(Context context) {
70 mContext = Preconditions.checkNotNull(context);
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +000071 mSessionFactory = mDefaultSessionFactory;
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +010072 mSettingsObserver = new SettingsObserver(this);
Abodunrinwa Toki43e03502017-01-13 13:46:33 -080073 }
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000074
Abodunrinwa Tokidf61b032017-03-29 22:41:57 +010075 /**
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +000076 * Returns the text classifier that was set via {@link #setTextClassifier(TextClassifier)}.
77 * If this is null, this method returns a default text classifier (i.e. either the system text
Tony Mak31ff1352019-01-22 21:26:05 +000078 * classifier if one exists, or a local text classifier running in this process.)
79 * <p>
Abodunrinwa Toki324572e2019-02-12 19:01:01 +000080 * Note that requests to the TextClassifier may be handled in an OEM-provided process rather
81 * than in the calling app's process.
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +000082 *
83 * @see #setTextClassifier(TextClassifier)
Abodunrinwa Tokidf61b032017-03-29 22:41:57 +010084 */
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +000085 @NonNull
Abodunrinwa Tokidf61b032017-03-29 22:41:57 +010086 public TextClassifier getTextClassifier() {
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080087 synchronized (mLock) {
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +010088 if (mCustomTextClassifier != null) {
89 return mCustomTextClassifier;
90 } else if (isSystemTextClassifierEnabled()) {
91 return getSystemTextClassifier();
92 } else {
93 return getLocalTextClassifier();
Abodunrinwa Toki43e03502017-01-13 13:46:33 -080094 }
Abodunrinwa Tokidf61b032017-03-29 22:41:57 +010095 }
96 }
97
98 /**
99 * Sets the text classifier.
100 * Set to null to use the system default text classifier.
101 * Set to {@link TextClassifier#NO_OP} to disable text classifier features.
102 */
103 public void setTextClassifier(@Nullable TextClassifier textClassifier) {
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800104 synchronized (mLock) {
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +0100105 mCustomTextClassifier = textClassifier;
Abodunrinwa Toki43e03502017-01-13 13:46:33 -0800106 }
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000107 }
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800108
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +0000109 /**
110 * Returns a specific type of text classifier.
111 * If the specified text classifier cannot be found, this returns {@link TextClassifier#NO_OP}.
112 *
113 * @see TextClassifier#LOCAL
114 * @see TextClassifier#SYSTEM
115 * @hide
116 */
Mathew Inwooda570dee2018-08-17 14:56:00 +0100117 @UnsupportedAppUsage
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +0000118 public TextClassifier getTextClassifier(@TextClassifierType int type) {
119 switch (type) {
120 case TextClassifier.LOCAL:
121 return getLocalTextClassifier();
122 default:
123 return getSystemTextClassifier();
124 }
125 }
126
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +0100127 private TextClassificationConstants getSettings() {
128 synchronized (mLock) {
129 if (mSettings == null) {
130 mSettings = TextClassificationConstants.loadFromString(Settings.Global.getString(
Michael Wrightb112f262018-05-24 13:41:50 +0100131 getApplicationContext().getContentResolver(),
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +0100132 Settings.Global.TEXT_CLASSIFIER_CONSTANTS));
133 }
134 return mSettings;
135 }
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +0000136 }
137
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000138 /**
139 * Call this method to start a text classification session with the given context.
140 * A session is created with a context helping the classifier better understand
141 * what the user needs and consists of queries and feedback events. The queries
142 * are directly related to providing useful functionality to the user and the events
143 * are a feedback loop back to the classifier helping it learn and better serve
144 * future queries.
145 *
146 * <p> All interactions with the returned classifier are considered part of a single
147 * session and are logically grouped. For example, when a text widget is focused
148 * all user interactions around text editing (selection, editing, etc) can be
149 * grouped together to allow the classifier get better.
150 *
151 * @param classificationContext The context in which classification would occur
152 *
153 * @return An instance to perform classification in the given context
154 */
155 @NonNull
156 public TextClassifier createTextClassificationSession(
157 @NonNull TextClassificationContext classificationContext) {
158 Preconditions.checkNotNull(classificationContext);
159 final TextClassifier textClassifier =
160 mSessionFactory.createTextClassificationSession(classificationContext);
161 Preconditions.checkNotNull(textClassifier, "Session Factory should never return null");
162 return textClassifier;
163 }
164
165 /**
166 * @see #createTextClassificationSession(TextClassificationContext, TextClassifier)
167 * @hide
168 */
169 public TextClassifier createTextClassificationSession(
170 TextClassificationContext classificationContext, TextClassifier textClassifier) {
171 Preconditions.checkNotNull(classificationContext);
172 Preconditions.checkNotNull(textClassifier);
173 return new TextClassificationSession(classificationContext, textClassifier);
174 }
175
176 /**
177 * Sets a TextClassificationSessionFactory to be used to create session-aware TextClassifiers.
178 *
179 * @param factory the textClassification session factory. If this is null, the default factory
180 * will be used.
181 */
182 public void setTextClassificationSessionFactory(
183 @Nullable TextClassificationSessionFactory factory) {
184 synchronized (mLock) {
185 if (factory != null) {
186 mSessionFactory = factory;
187 } else {
188 mSessionFactory = mDefaultSessionFactory;
189 }
190 }
191 }
192
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +0100193 @Override
194 protected void finalize() throws Throwable {
195 try {
196 // Note that fields could be null if the constructor threw.
Michael Wrightb112f262018-05-24 13:41:50 +0100197 if (mSettingsObserver != null) {
198 getApplicationContext().getContentResolver()
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +0100199 .unregisterContentObserver(mSettingsObserver);
Tony Makfc374572019-03-05 14:46:24 +0000200 DeviceConfig.removeOnPropertyChangedListener(mSettingsObserver);
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +0100201 }
202 } finally {
203 super.finalize();
204 }
205 }
206
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +0000207 private TextClassifier getSystemTextClassifier() {
208 synchronized (mLock) {
209 if (mSystemTextClassifier == null && isSystemTextClassifierEnabled()) {
210 try {
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +0100211 mSystemTextClassifier = new SystemTextClassifier(mContext, getSettings());
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +0000212 Log.d(LOG_TAG, "Initialized SystemTextClassifier");
213 } catch (ServiceManager.ServiceNotFoundException e) {
214 Log.e(LOG_TAG, "Could not initialize SystemTextClassifier", e);
215 }
216 }
217 }
218 if (mSystemTextClassifier != null) {
219 return mSystemTextClassifier;
220 }
221 return TextClassifier.NO_OP;
222 }
223
Tony Mak31ff1352019-01-22 21:26:05 +0000224 /**
225 * Returns a local textclassifier, which is running in this process.
Tony Mak31ff1352019-01-22 21:26:05 +0000226 */
227 @NonNull
Abodunrinwa Toki324572e2019-02-12 19:01:01 +0000228 private TextClassifier getLocalTextClassifier() {
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +0000229 synchronized (mLock) {
230 if (mLocalTextClassifier == null) {
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +0100231 if (getSettings().isLocalTextClassifierEnabled()) {
Abodunrinwa Toki253827f2018-04-24 19:19:48 +0100232 mLocalTextClassifier =
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +0100233 new TextClassifierImpl(mContext, getSettings(), TextClassifier.NO_OP);
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +0000234 } else {
235 Log.d(LOG_TAG, "Local TextClassifier disabled");
Abodunrinwa Toki253827f2018-04-24 19:19:48 +0100236 mLocalTextClassifier = TextClassifier.NO_OP;
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +0000237 }
238 }
239 return mLocalTextClassifier;
240 }
241 }
242
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800243 private boolean isSystemTextClassifierEnabled() {
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +0100244 return getSettings().isSystemTextClassifierEnabled()
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800245 && TextClassifierService.getServiceComponentName(mContext) != null;
246 }
Abodunrinwa Tokidb8fc312018-02-26 21:37:51 +0000247
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +0100248 private void invalidate() {
249 synchronized (mLock) {
250 mSettings = null;
251 mLocalTextClassifier = null;
252 mSystemTextClassifier = null;
253 }
254 }
255
Michael Wrightb112f262018-05-24 13:41:50 +0100256 Context getApplicationContext() {
257 return mContext.getApplicationContext() != null
258 ? mContext.getApplicationContext()
259 : mContext;
260 }
261
Tony Makf93e9e52018-07-16 14:46:29 +0200262 /** @hide **/
263 public void dump(IndentingPrintWriter pw) {
264 getLocalTextClassifier().dump(pw);
265 getSystemTextClassifier().dump(pw);
266 getSettings().dump(pw);
267 }
268
Abodunrinwa Tokidb8fc312018-02-26 21:37:51 +0000269 /** @hide */
270 public static TextClassificationConstants getSettings(Context context) {
271 Preconditions.checkNotNull(context);
272 final TextClassificationManager tcm =
273 context.getSystemService(TextClassificationManager.class);
274 if (tcm != null) {
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +0100275 return tcm.getSettings();
Abodunrinwa Tokidb8fc312018-02-26 21:37:51 +0000276 } else {
277 return TextClassificationConstants.loadFromString(Settings.Global.getString(
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +0100278 context.getApplicationContext().getContentResolver(),
279 Settings.Global.TEXT_CLASSIFIER_CONSTANTS));
280 }
281 }
282
Tony Makfc374572019-03-05 14:46:24 +0000283 private static final class SettingsObserver extends ContentObserver
284 implements DeviceConfig.OnPropertyChangedListener {
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +0100285
286 private final WeakReference<TextClassificationManager> mTcm;
287
288 SettingsObserver(TextClassificationManager tcm) {
289 super(null);
290 mTcm = new WeakReference<>(tcm);
Michael Wrightb112f262018-05-24 13:41:50 +0100291 tcm.getApplicationContext().getContentResolver().registerContentObserver(
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +0100292 Settings.Global.getUriFor(Settings.Global.TEXT_CLASSIFIER_CONSTANTS),
293 false /* notifyForDescendants */,
294 this);
Tony Makfc374572019-03-05 14:46:24 +0000295 DeviceConfig.addOnPropertyChangedListener(
296 DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
297 ActivityThread.currentApplication().getMainExecutor(),
298 this);
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +0100299 }
300
301 @Override
302 public void onChange(boolean selfChange) {
Tony Makfc374572019-03-05 14:46:24 +0000303 invalidateSettings();
304 }
305
306 @Override
307 public void onPropertyChanged(String namespace, String name, String value) {
308 invalidateSettings();
309 }
310
311 private void invalidateSettings() {
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +0100312 final TextClassificationManager tcm = mTcm.get();
313 if (tcm != null) {
314 tcm.invalidate();
315 }
Abodunrinwa Tokidb8fc312018-02-26 21:37:51 +0000316 }
317 }
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000318}