• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
TextServicesManager()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      */
getInstance()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      */
parseLanguageFromLocaleString(String locale)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      */
newSpellCheckerSession(Bundle bundle, Locale locale, SpellCheckerSessionListener listener, boolean referToSpellCheckerLanguageSettings)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      */
getEnabledSpellCheckers()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      */
getCurrentSpellChecker()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      */
setCurrentSpellChecker(SpellCheckerInfo sci)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      */
getCurrentSpellCheckerSubtype( boolean allowImplicitlySelectedSubtype)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      */
setSpellCheckerSubtype(SpellCheckerSubtype subtype)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      */
setSpellCheckerEnabled(boolean enabled)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      */
isSpellCheckerEnabled()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