blob: d047c55a590835d40df7c6b922d412afc5c5cc5a [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;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000023import android.content.Context;
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +010024import android.database.ContentObserver;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080025import android.os.ServiceManager;
Abodunrinwa Tokidb8fc312018-02-26 21:37:51 +000026import android.provider.Settings;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080027import android.service.textclassifier.TextClassifierService;
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +000028import android.view.textclassifier.TextClassifier.TextClassifierType;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000029
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +000030import com.android.internal.annotations.GuardedBy;
Tony Makf93e9e52018-07-16 14:46:29 +020031import com.android.internal.util.IndentingPrintWriter;
Abodunrinwa Toki43e03502017-01-13 13:46:33 -080032import com.android.internal.util.Preconditions;
33
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +010034import java.lang.ref.WeakReference;
35
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000036/**
37 * Interface to the text classification service.
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000038 */
Jeff Sharkeyd86b8fe2017-06-02 17:36:26 -060039@SystemService(Context.TEXT_CLASSIFICATION_SERVICE)
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000040public final class TextClassificationManager {
41
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080042 private static final String LOG_TAG = "TextClassificationManager";
43
44 private final Object mLock = new Object();
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +000045 private final TextClassificationSessionFactory mDefaultSessionFactory =
46 classificationContext -> new TextClassificationSession(
47 classificationContext, getTextClassifier());
Abodunrinwa Tokib89cf022017-02-06 19:53:22 +000048
Abodunrinwa Toki43e03502017-01-13 13:46:33 -080049 private final Context mContext;
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +010050 private final SettingsObserver mSettingsObserver;
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +000051
52 @GuardedBy("mLock")
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +010053 @Nullable
54 private TextClassifier mCustomTextClassifier;
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +000055 @GuardedBy("mLock")
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +010056 @Nullable
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +000057 private TextClassifier mLocalTextClassifier;
58 @GuardedBy("mLock")
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +010059 @Nullable
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080060 private TextClassifier mSystemTextClassifier;
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +000061 @GuardedBy("mLock")
62 private TextClassificationSessionFactory mSessionFactory;
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +010063 @GuardedBy("mLock")
64 private TextClassificationConstants mSettings;
Abodunrinwa Toki43e03502017-01-13 13:46:33 -080065
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000066 /** @hide */
Abodunrinwa Toki43e03502017-01-13 13:46:33 -080067 public TextClassificationManager(Context context) {
68 mContext = Preconditions.checkNotNull(context);
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +000069 mSessionFactory = mDefaultSessionFactory;
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +010070 mSettingsObserver = new SettingsObserver(this);
Abodunrinwa Toki43e03502017-01-13 13:46:33 -080071 }
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000072
Abodunrinwa Tokidf61b032017-03-29 22:41:57 +010073 /**
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +000074 * Returns the text classifier that was set via {@link #setTextClassifier(TextClassifier)}.
75 * If this is null, this method returns a default text classifier (i.e. either the system text
Tony Mak31ff1352019-01-22 21:26:05 +000076 * classifier if one exists, or a local text classifier running in this process.)
77 * <p>
Abodunrinwa Toki324572e2019-02-12 19:01:01 +000078 * Note that requests to the TextClassifier may be handled in an OEM-provided process rather
79 * than in the calling app's process.
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +000080 *
81 * @see #setTextClassifier(TextClassifier)
Abodunrinwa Tokidf61b032017-03-29 22:41:57 +010082 */
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +000083 @NonNull
Abodunrinwa Tokidf61b032017-03-29 22:41:57 +010084 public TextClassifier getTextClassifier() {
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080085 synchronized (mLock) {
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +010086 if (mCustomTextClassifier != null) {
87 return mCustomTextClassifier;
88 } else if (isSystemTextClassifierEnabled()) {
89 return getSystemTextClassifier();
90 } else {
91 return getLocalTextClassifier();
Abodunrinwa Toki43e03502017-01-13 13:46:33 -080092 }
Abodunrinwa Tokidf61b032017-03-29 22:41:57 +010093 }
94 }
95
96 /**
97 * Sets the text classifier.
98 * Set to null to use the system default text classifier.
99 * Set to {@link TextClassifier#NO_OP} to disable text classifier features.
100 */
101 public void setTextClassifier(@Nullable TextClassifier textClassifier) {
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800102 synchronized (mLock) {
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +0100103 mCustomTextClassifier = textClassifier;
Abodunrinwa Toki43e03502017-01-13 13:46:33 -0800104 }
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000105 }
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800106
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +0000107 /**
108 * Returns a specific type of text classifier.
109 * If the specified text classifier cannot be found, this returns {@link TextClassifier#NO_OP}.
110 *
111 * @see TextClassifier#LOCAL
112 * @see TextClassifier#SYSTEM
113 * @hide
114 */
Mathew Inwooda570dee2018-08-17 14:56:00 +0100115 @UnsupportedAppUsage
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +0000116 public TextClassifier getTextClassifier(@TextClassifierType int type) {
117 switch (type) {
118 case TextClassifier.LOCAL:
119 return getLocalTextClassifier();
120 default:
121 return getSystemTextClassifier();
122 }
123 }
124
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +0100125 private TextClassificationConstants getSettings() {
126 synchronized (mLock) {
127 if (mSettings == null) {
128 mSettings = TextClassificationConstants.loadFromString(Settings.Global.getString(
Michael Wrightb112f262018-05-24 13:41:50 +0100129 getApplicationContext().getContentResolver(),
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +0100130 Settings.Global.TEXT_CLASSIFIER_CONSTANTS));
131 }
132 return mSettings;
133 }
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +0000134 }
135
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000136 /**
137 * Call this method to start a text classification session with the given context.
138 * A session is created with a context helping the classifier better understand
139 * what the user needs and consists of queries and feedback events. The queries
140 * are directly related to providing useful functionality to the user and the events
141 * are a feedback loop back to the classifier helping it learn and better serve
142 * future queries.
143 *
144 * <p> All interactions with the returned classifier are considered part of a single
145 * session and are logically grouped. For example, when a text widget is focused
146 * all user interactions around text editing (selection, editing, etc) can be
147 * grouped together to allow the classifier get better.
148 *
149 * @param classificationContext The context in which classification would occur
150 *
151 * @return An instance to perform classification in the given context
152 */
153 @NonNull
154 public TextClassifier createTextClassificationSession(
155 @NonNull TextClassificationContext classificationContext) {
156 Preconditions.checkNotNull(classificationContext);
157 final TextClassifier textClassifier =
158 mSessionFactory.createTextClassificationSession(classificationContext);
159 Preconditions.checkNotNull(textClassifier, "Session Factory should never return null");
160 return textClassifier;
161 }
162
163 /**
164 * @see #createTextClassificationSession(TextClassificationContext, TextClassifier)
165 * @hide
166 */
167 public TextClassifier createTextClassificationSession(
168 TextClassificationContext classificationContext, TextClassifier textClassifier) {
169 Preconditions.checkNotNull(classificationContext);
170 Preconditions.checkNotNull(textClassifier);
171 return new TextClassificationSession(classificationContext, textClassifier);
172 }
173
174 /**
175 * Sets a TextClassificationSessionFactory to be used to create session-aware TextClassifiers.
176 *
177 * @param factory the textClassification session factory. If this is null, the default factory
178 * will be used.
179 */
180 public void setTextClassificationSessionFactory(
181 @Nullable TextClassificationSessionFactory factory) {
182 synchronized (mLock) {
183 if (factory != null) {
184 mSessionFactory = factory;
185 } else {
186 mSessionFactory = mDefaultSessionFactory;
187 }
188 }
189 }
190
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +0100191 @Override
192 protected void finalize() throws Throwable {
193 try {
194 // Note that fields could be null if the constructor threw.
Michael Wrightb112f262018-05-24 13:41:50 +0100195 if (mSettingsObserver != null) {
196 getApplicationContext().getContentResolver()
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +0100197 .unregisterContentObserver(mSettingsObserver);
198 }
199 } finally {
200 super.finalize();
201 }
202 }
203
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +0000204 private TextClassifier getSystemTextClassifier() {
205 synchronized (mLock) {
206 if (mSystemTextClassifier == null && isSystemTextClassifierEnabled()) {
207 try {
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +0100208 mSystemTextClassifier = new SystemTextClassifier(mContext, getSettings());
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +0000209 Log.d(LOG_TAG, "Initialized SystemTextClassifier");
210 } catch (ServiceManager.ServiceNotFoundException e) {
211 Log.e(LOG_TAG, "Could not initialize SystemTextClassifier", e);
212 }
213 }
214 }
215 if (mSystemTextClassifier != null) {
216 return mSystemTextClassifier;
217 }
218 return TextClassifier.NO_OP;
219 }
220
Tony Mak31ff1352019-01-22 21:26:05 +0000221 /**
222 * Returns a local textclassifier, which is running in this process.
Tony Mak31ff1352019-01-22 21:26:05 +0000223 */
224 @NonNull
Abodunrinwa Toki324572e2019-02-12 19:01:01 +0000225 private TextClassifier getLocalTextClassifier() {
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +0000226 synchronized (mLock) {
227 if (mLocalTextClassifier == null) {
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +0100228 if (getSettings().isLocalTextClassifierEnabled()) {
Abodunrinwa Toki253827f2018-04-24 19:19:48 +0100229 mLocalTextClassifier =
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +0100230 new TextClassifierImpl(mContext, getSettings(), TextClassifier.NO_OP);
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +0000231 } else {
232 Log.d(LOG_TAG, "Local TextClassifier disabled");
Abodunrinwa Toki253827f2018-04-24 19:19:48 +0100233 mLocalTextClassifier = TextClassifier.NO_OP;
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +0000234 }
235 }
236 return mLocalTextClassifier;
237 }
238 }
239
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800240 private boolean isSystemTextClassifierEnabled() {
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +0100241 return getSettings().isSystemTextClassifierEnabled()
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800242 && TextClassifierService.getServiceComponentName(mContext) != null;
243 }
Abodunrinwa Tokidb8fc312018-02-26 21:37:51 +0000244
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +0100245 private void invalidate() {
246 synchronized (mLock) {
247 mSettings = null;
248 mLocalTextClassifier = null;
249 mSystemTextClassifier = null;
250 }
251 }
252
Michael Wrightb112f262018-05-24 13:41:50 +0100253 Context getApplicationContext() {
254 return mContext.getApplicationContext() != null
255 ? mContext.getApplicationContext()
256 : mContext;
257 }
258
Tony Makf93e9e52018-07-16 14:46:29 +0200259 /** @hide **/
260 public void dump(IndentingPrintWriter pw) {
261 getLocalTextClassifier().dump(pw);
262 getSystemTextClassifier().dump(pw);
263 getSettings().dump(pw);
264 }
265
Abodunrinwa Tokidb8fc312018-02-26 21:37:51 +0000266 /** @hide */
267 public static TextClassificationConstants getSettings(Context context) {
268 Preconditions.checkNotNull(context);
269 final TextClassificationManager tcm =
270 context.getSystemService(TextClassificationManager.class);
271 if (tcm != null) {
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +0100272 return tcm.getSettings();
Abodunrinwa Tokidb8fc312018-02-26 21:37:51 +0000273 } else {
274 return TextClassificationConstants.loadFromString(Settings.Global.getString(
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +0100275 context.getApplicationContext().getContentResolver(),
276 Settings.Global.TEXT_CLASSIFIER_CONSTANTS));
277 }
278 }
279
280 private static final class SettingsObserver extends ContentObserver {
281
282 private final WeakReference<TextClassificationManager> mTcm;
283
284 SettingsObserver(TextClassificationManager tcm) {
285 super(null);
286 mTcm = new WeakReference<>(tcm);
Michael Wrightb112f262018-05-24 13:41:50 +0100287 tcm.getApplicationContext().getContentResolver().registerContentObserver(
Abodunrinwa Tokic2449b82018-05-01 21:36:48 +0100288 Settings.Global.getUriFor(Settings.Global.TEXT_CLASSIFIER_CONSTANTS),
289 false /* notifyForDescendants */,
290 this);
291 }
292
293 @Override
294 public void onChange(boolean selfChange) {
295 final TextClassificationManager tcm = mTcm.get();
296 if (tcm != null) {
297 tcm.invalidate();
298 }
Abodunrinwa Tokidb8fc312018-02-26 21:37:51 +0000299 }
300 }
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000301}