Home | History | Annotate | Download | only in textclassifier
      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