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 com.android.internal.textservice.ITextServicesManager;
     20 
     21 import android.content.Context;
     22 import android.os.Bundle;
     23 import android.os.IBinder;
     24 import android.os.RemoteException;
     25 import android.os.ServiceManager;
     26 import android.util.Log;
     27 import android.view.textservice.SpellCheckerSession.SpellCheckerSessionListener;
     28 
     29 import java.util.Locale;
     30 
     31 /**
     32  * System API to the overall text services, which arbitrates interaction between applications
     33  * and text services. You can retrieve an instance of this interface with
     34  * {@link Context#getSystemService(String) Context.getSystemService()}.
     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 public final class TextServicesManager {
     65     private static final String TAG = TextServicesManager.class.getSimpleName();
     66     private static final boolean DBG = false;
     67 
     68     private static TextServicesManager sInstance;
     69     private static ITextServicesManager sService;
     70 
     71     private TextServicesManager() {
     72         if (sService == null) {
     73             IBinder b = ServiceManager.getService(Context.TEXT_SERVICES_MANAGER_SERVICE);
     74             sService = ITextServicesManager.Stub.asInterface(b);
     75         }
     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                 return sInstance;
     86             }
     87             sInstance = new TextServicesManager();
     88         }
     89         return sInstance;
     90     }
     91 
     92     /**
     93      * Returns the language component of a given locale string.
     94      */
     95     private static String parseLanguageFromLocaleString(String locale) {
     96         final int idx = locale.indexOf('_');
     97         if (idx < 0) {
     98             return locale;
     99         } else {
    100             return locale.substring(0, idx);
    101         }
    102     }
    103 
    104     /**
    105      * Get a spell checker session for the specified spell checker
    106      * @param locale the locale for the spell checker. If {@code locale} is null and
    107      * referToSpellCheckerLanguageSettings is true, the locale specified in Settings will be
    108      * returned. If {@code locale} is not null and referToSpellCheckerLanguageSettings is true,
    109      * the locale specified in Settings will be returned only when it is same as {@code locale}.
    110      * Exceptionally, when referToSpellCheckerLanguageSettings is true and {@code locale} is
    111      * only language (e.g. "en"), the specified locale in Settings (e.g. "en_US") will be
    112      * selected.
    113      * @param listener a spell checker session lister for getting results from a spell checker.
    114      * @param referToSpellCheckerLanguageSettings if true, the session for one of enabled
    115      * languages in settings will be returned.
    116      * @return the spell checker session of the spell checker
    117      */
    118     public SpellCheckerSession newSpellCheckerSession(Bundle bundle, Locale locale,
    119             SpellCheckerSessionListener listener, boolean referToSpellCheckerLanguageSettings) {
    120         if (listener == null) {
    121             throw new NullPointerException();
    122         }
    123         if (!referToSpellCheckerLanguageSettings && locale == null) {
    124             throw new IllegalArgumentException("Locale should not be null if you don't refer"
    125                     + " settings.");
    126         }
    127 
    128         if (referToSpellCheckerLanguageSettings && !isSpellCheckerEnabled()) {
    129             return null;
    130         }
    131 
    132         final SpellCheckerInfo sci;
    133         try {
    134             sci = sService.getCurrentSpellChecker(null);
    135         } catch (RemoteException e) {
    136             return null;
    137         }
    138         if (sci == null) {
    139             return null;
    140         }
    141         SpellCheckerSubtype subtypeInUse = null;
    142         if (referToSpellCheckerLanguageSettings) {
    143             subtypeInUse = getCurrentSpellCheckerSubtype(true);
    144             if (subtypeInUse == null) {
    145                 return null;
    146             }
    147             if (locale != null) {
    148                 final String subtypeLocale = subtypeInUse.getLocale();
    149                 final String subtypeLanguage = parseLanguageFromLocaleString(subtypeLocale);
    150                 if (subtypeLanguage.length() < 2 || !locale.getLanguage().equals(subtypeLanguage)) {
    151                     return null;
    152                 }
    153             }
    154         } else {
    155             final String localeStr = locale.toString();
    156             for (int i = 0; i < sci.getSubtypeCount(); ++i) {
    157                 final SpellCheckerSubtype subtype = sci.getSubtypeAt(i);
    158                 final String tempSubtypeLocale = subtype.getLocale();
    159                 final String tempSubtypeLanguage = parseLanguageFromLocaleString(tempSubtypeLocale);
    160                 if (tempSubtypeLocale.equals(localeStr)) {
    161                     subtypeInUse = subtype;
    162                     break;
    163                 } else if (tempSubtypeLanguage.length() >= 2 &&
    164                         locale.getLanguage().equals(tempSubtypeLanguage)) {
    165                     subtypeInUse = subtype;
    166                 }
    167             }
    168         }
    169         if (subtypeInUse == null) {
    170             return null;
    171         }
    172         final SpellCheckerSession session = new SpellCheckerSession(
    173                 sci, sService, listener, subtypeInUse);
    174         try {
    175             sService.getSpellCheckerService(sci.getId(), subtypeInUse.getLocale(),
    176                     session.getTextServicesSessionListener(),
    177                     session.getSpellCheckerSessionListener(), bundle);
    178         } catch (RemoteException e) {
    179             return null;
    180         }
    181         return session;
    182     }
    183 
    184     /**
    185      * @hide
    186      */
    187     public SpellCheckerInfo[] getEnabledSpellCheckers() {
    188         try {
    189             final SpellCheckerInfo[] retval = sService.getEnabledSpellCheckers();
    190             if (DBG) {
    191                 Log.d(TAG, "getEnabledSpellCheckers: " + (retval != null ? retval.length : "null"));
    192             }
    193             return retval;
    194         } catch (RemoteException e) {
    195             Log.e(TAG, "Error in getEnabledSpellCheckers: " + e);
    196             return null;
    197         }
    198     }
    199 
    200     /**
    201      * @hide
    202      */
    203     public SpellCheckerInfo getCurrentSpellChecker() {
    204         try {
    205             // Passing null as a locale for ICS
    206             return sService.getCurrentSpellChecker(null);
    207         } catch (RemoteException e) {
    208             return null;
    209         }
    210     }
    211 
    212     /**
    213      * @hide
    214      */
    215     public void setCurrentSpellChecker(SpellCheckerInfo sci) {
    216         try {
    217             if (sci == null) {
    218                 throw new NullPointerException("SpellCheckerInfo is null.");
    219             }
    220             sService.setCurrentSpellChecker(null, sci.getId());
    221         } catch (RemoteException e) {
    222             Log.e(TAG, "Error in setCurrentSpellChecker: " + e);
    223         }
    224     }
    225 
    226     /**
    227      * @hide
    228      */
    229     public SpellCheckerSubtype getCurrentSpellCheckerSubtype(
    230             boolean allowImplicitlySelectedSubtype) {
    231         try {
    232             if (sService == null) {
    233                 // TODO: This is a workaround. Needs to investigate why sService could be null
    234                 // here.
    235                 Log.e(TAG, "sService is null.");
    236                 return null;
    237             }
    238             // Passing null as a locale until we support multiple enabled spell checker subtypes.
    239             return sService.getCurrentSpellCheckerSubtype(null, allowImplicitlySelectedSubtype);
    240         } catch (RemoteException e) {
    241             Log.e(TAG, "Error in getCurrentSpellCheckerSubtype: " + e);
    242             return null;
    243         }
    244     }
    245 
    246     /**
    247      * @hide
    248      */
    249     public void setSpellCheckerSubtype(SpellCheckerSubtype subtype) {
    250         try {
    251             final int hashCode;
    252             if (subtype == null) {
    253                 hashCode = 0;
    254             } else {
    255                 hashCode = subtype.hashCode();
    256             }
    257             sService.setCurrentSpellCheckerSubtype(null, hashCode);
    258         } catch (RemoteException e) {
    259             Log.e(TAG, "Error in setSpellCheckerSubtype:" + e);
    260         }
    261     }
    262 
    263     /**
    264      * @hide
    265      */
    266     public void setSpellCheckerEnabled(boolean enabled) {
    267         try {
    268             sService.setSpellCheckerEnabled(enabled);
    269         } catch (RemoteException e) {
    270             Log.e(TAG, "Error in setSpellCheckerEnabled:" + e);
    271         }
    272     }
    273 
    274     /**
    275      * @hide
    276      */
    277     public boolean isSpellCheckerEnabled() {
    278         try {
    279             return sService.isSpellCheckerEnabled();
    280         } catch (RemoteException e) {
    281             Log.e(TAG, "Error in isSpellCheckerEnabled:" + e);
    282             return false;
    283         }
    284     }
    285 }
    286