Home | History | Annotate | Download | only in textservice
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
      5  * use this file except in compliance with the License. You may obtain a copy of
      6  * 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, WITHOUT
     12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
     13  * License for the specific language governing permissions and limitations under
     14  * the License.
     15  */
     16 
     17 package android.view.textservice;
     18 
     19 import android.annotation.SystemService;
     20 import android.content.Context;
     21 import android.os.Bundle;
     22 import android.os.RemoteException;
     23 import android.os.ServiceManager;
     24 import android.os.ServiceManager.ServiceNotFoundException;
     25 import android.util.Log;
     26 import android.view.textservice.SpellCheckerSession.SpellCheckerSessionListener;
     27 
     28 import com.android.internal.textservice.ITextServicesManager;
     29 
     30 import java.util.Locale;
     31 
     32 /**
     33  * System API to the overall text services, which arbitrates interaction between applications
     34  * and text services.
     35  *
     36  * The user can change the current text services in Settings. And also applications can specify
     37  * the target text services.
     38  *
     39  * <h3>Architecture Overview</h3>
     40  *
     41  * <p>There are three primary parties involved in the text services
     42  * framework (TSF) architecture:</p>
     43  *
     44  * <ul>
     45  * <li> The <strong>text services manager</strong> as expressed by this class
     46  * is the central point of the system that manages interaction between all
     47  * other parts.  It is expressed as the client-side API here which exists
     48  * in each application context and communicates with a global system service
     49  * that manages the interaction across all processes.
     50  * <li> A <strong>text service</strong> implements a particular
     51  * interaction model allowing the client application to retrieve information of text.
     52  * The system binds to the current text service that is in use, causing it to be created and run.
     53  * <li> Multiple <strong>client applications</strong> arbitrate with the text service
     54  * manager for connections to text services.
     55  * </ul>
     56  *
     57  * <h3>Text services sessions</h3>
     58  * <ul>
     59  * <li>The <strong>spell checker session</strong> is one of the text services.
     60  * {@link android.view.textservice.SpellCheckerSession}</li>
     61  * </ul>
     62  *
     63  */
     64 @SystemService(Context.TEXT_SERVICES_MANAGER_SERVICE)
     65 public final class TextServicesManager {
     66     private static final String TAG = TextServicesManager.class.getSimpleName();
     67     private static final boolean DBG = false;
     68 
     69     private static TextServicesManager sInstance;
     70 
     71     private final ITextServicesManager mService;
     72 
     73     private TextServicesManager() throws ServiceNotFoundException {
     74         mService = ITextServicesManager.Stub.asInterface(
     75                 ServiceManager.getServiceOrThrow(Context.TEXT_SERVICES_MANAGER_SERVICE));
     76     }
     77 
     78     /**
     79      * Retrieve the global TextServicesManager instance, creating it if it doesn't already exist.
     80      * @hide
     81      */
     82     public static TextServicesManager getInstance() {
     83         synchronized (TextServicesManager.class) {
     84             if (sInstance == null) {
     85                 try {
     86                     sInstance = new TextServicesManager();
     87                 } catch (ServiceNotFoundException e) {
     88                     throw new IllegalStateException(e);
     89                 }
     90             }
     91             return sInstance;
     92         }
     93     }
     94 
     95     /**
     96      * Returns the language component of a given locale string.
     97      */
     98     private static String parseLanguageFromLocaleString(String locale) {
     99         final int idx = locale.indexOf('_');
    100         if (idx < 0) {
    101             return locale;
    102         } else {
    103             return locale.substring(0, idx);
    104         }
    105     }
    106 
    107     /**
    108      * Get a spell checker session for the specified spell checker
    109      * @param locale the locale for the spell checker. If {@code locale} is null and
    110      * referToSpellCheckerLanguageSettings is true, the locale specified in Settings will be
    111      * returned. If {@code locale} is not null and referToSpellCheckerLanguageSettings is true,
    112      * the locale specified in Settings will be returned only when it is same as {@code locale}.
    113      * Exceptionally, when referToSpellCheckerLanguageSettings is true and {@code locale} is
    114      * only language (e.g. "en"), the specified locale in Settings (e.g. "en_US") will be
    115      * selected.
    116      * @param listener a spell checker session lister for getting results from a spell checker.
    117      * @param referToSpellCheckerLanguageSettings if true, the session for one of enabled
    118      * languages in settings will be returned.
    119      * @return the spell checker session of the spell checker
    120      */
    121     public SpellCheckerSession newSpellCheckerSession(Bundle bundle, Locale locale,
    122             SpellCheckerSessionListener listener, boolean referToSpellCheckerLanguageSettings) {
    123         if (listener == null) {
    124             throw new NullPointerException();
    125         }
    126         if (!referToSpellCheckerLanguageSettings && locale == null) {
    127             throw new IllegalArgumentException("Locale should not be null if you don't refer"
    128                     + " settings.");
    129         }
    130 
    131         if (referToSpellCheckerLanguageSettings && !isSpellCheckerEnabled()) {
    132             return null;
    133         }
    134 
    135         final SpellCheckerInfo sci;
    136         try {
    137             sci = mService.getCurrentSpellChecker(null);
    138         } catch (RemoteException e) {
    139             return null;
    140         }
    141         if (sci == null) {
    142             return null;
    143         }
    144         SpellCheckerSubtype subtypeInUse = null;
    145         if (referToSpellCheckerLanguageSettings) {
    146             subtypeInUse = getCurrentSpellCheckerSubtype(true);
    147             if (subtypeInUse == null) {
    148                 return null;
    149             }
    150             if (locale != null) {
    151                 final String subtypeLocale = subtypeInUse.getLocale();
    152                 final String subtypeLanguage = parseLanguageFromLocaleString(subtypeLocale);
    153                 if (subtypeLanguage.length() < 2 || !locale.getLanguage().equals(subtypeLanguage)) {
    154                     return null;
    155                 }
    156             }
    157         } else {
    158             final String localeStr = locale.toString();
    159             for (int i = 0; i < sci.getSubtypeCount(); ++i) {
    160                 final SpellCheckerSubtype subtype = sci.getSubtypeAt(i);
    161                 final String tempSubtypeLocale = subtype.getLocale();
    162                 final String tempSubtypeLanguage = parseLanguageFromLocaleString(tempSubtypeLocale);
    163                 if (tempSubtypeLocale.equals(localeStr)) {
    164                     subtypeInUse = subtype;
    165                     break;
    166                 } else if (tempSubtypeLanguage.length() >= 2 &&
    167                         locale.getLanguage().equals(tempSubtypeLanguage)) {
    168                     subtypeInUse = subtype;
    169                 }
    170             }
    171         }
    172         if (subtypeInUse == null) {
    173             return null;
    174         }
    175         final SpellCheckerSession session = new SpellCheckerSession(sci, mService, listener);
    176         try {
    177             mService.getSpellCheckerService(sci.getId(), subtypeInUse.getLocale(),
    178                     session.getTextServicesSessionListener(),
    179                     session.getSpellCheckerSessionListener(), bundle);
    180         } catch (RemoteException e) {
    181             throw e.rethrowFromSystemServer();
    182         }
    183         return session;
    184     }
    185 
    186     /**
    187      * @hide
    188      */
    189     public SpellCheckerInfo[] getEnabledSpellCheckers() {
    190         try {
    191             final SpellCheckerInfo[] retval = mService.getEnabledSpellCheckers();
    192             if (DBG) {
    193                 Log.d(TAG, "getEnabledSpellCheckers: " + (retval != null ? retval.length : "null"));
    194             }
    195             return retval;
    196         } catch (RemoteException e) {
    197             throw e.rethrowFromSystemServer();
    198         }
    199     }
    200 
    201     /**
    202      * @hide
    203      */
    204     public SpellCheckerInfo getCurrentSpellChecker() {
    205         try {
    206             // Passing null as a locale for ICS
    207             return mService.getCurrentSpellChecker(null);
    208         } catch (RemoteException e) {
    209             throw e.rethrowFromSystemServer();
    210         }
    211     }
    212 
    213     /**
    214      * @hide
    215      */
    216     public SpellCheckerSubtype getCurrentSpellCheckerSubtype(
    217             boolean allowImplicitlySelectedSubtype) {
    218         try {
    219             // Passing null as a locale until we support multiple enabled spell checker subtypes.
    220             return mService.getCurrentSpellCheckerSubtype(null, allowImplicitlySelectedSubtype);
    221         } catch (RemoteException e) {
    222             throw e.rethrowFromSystemServer();
    223         }
    224     }
    225 
    226     /**
    227      * @hide
    228      */
    229     public boolean isSpellCheckerEnabled() {
    230         try {
    231             return mService.isSpellCheckerEnabled();
    232         } catch (RemoteException e) {
    233             throw e.rethrowFromSystemServer();
    234         }
    235     }
    236 }
    237