• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 */
TextClassificationManager(Context context)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
getTextClassifier()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      */
setTextClassifier(@ullable TextClassifier textClassifier)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      */
getTextClassifier(@extClassifierType int type)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 
getSettings()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
createTextClassificationSession( @onNull TextClassificationContext classificationContext)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      */
createTextClassificationSession( TextClassificationContext classificationContext, TextClassifier textClassifier)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      */
setTextClassificationSessionFactory( @ullable TextClassificationSessionFactory factory)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
finalize()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 
getSystemTextClassifier()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 
getLocalTextClassifier()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 
isSystemTextClassifierEnabled()230     private boolean isSystemTextClassifierEnabled() {
231         return getSettings().isSystemTextClassifierEnabled()
232                 && TextClassifierService.getServiceComponentName(mContext) != null;
233     }
234 
invalidate()235     private void invalidate() {
236         synchronized (mLock) {
237             mSettings = null;
238             mLocalTextClassifier = null;
239             mSystemTextClassifier = null;
240         }
241     }
242 
getApplicationContext()243     Context getApplicationContext() {
244         return mContext.getApplicationContext() != null
245                 ? mContext.getApplicationContext()
246                 : mContext;
247     }
248 
249     /** @hide */
getSettings(Context context)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 
SettingsObserver(TextClassificationManager tcm)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
onChange(boolean selfChange)277         public void onChange(boolean selfChange) {
278             final TextClassificationManager tcm = mTcm.get();
279             if (tcm != null) {
280                 tcm.invalidate();
281             }
282         }
283     }
284 }
285