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     /**
70      * A compile time switch to control per-profile spell checker, which is not yet ready.
71      * @hide
72      */
73     public static final boolean DISABLE_PER_PROFILE_SPELL_CHECKER = true;
74 
75     private static TextServicesManager sInstance;
76 
77     private final ITextServicesManager mService;
78 
TextServicesManager()79     private TextServicesManager() throws ServiceNotFoundException {
80         mService = ITextServicesManager.Stub.asInterface(
81                 ServiceManager.getServiceOrThrow(Context.TEXT_SERVICES_MANAGER_SERVICE));
82     }
83 
84     /**
85      * Retrieve the global TextServicesManager instance, creating it if it doesn't already exist.
86      * @hide
87      */
getInstance()88     public static TextServicesManager getInstance() {
89         synchronized (TextServicesManager.class) {
90             if (sInstance == null) {
91                 try {
92                     sInstance = new TextServicesManager();
93                 } catch (ServiceNotFoundException e) {
94                     throw new IllegalStateException(e);
95                 }
96             }
97             return sInstance;
98         }
99     }
100 
101     /**
102      * Returns the language component of a given locale string.
103      */
parseLanguageFromLocaleString(String locale)104     private static String parseLanguageFromLocaleString(String locale) {
105         final int idx = locale.indexOf('_');
106         if (idx < 0) {
107             return locale;
108         } else {
109             return locale.substring(0, idx);
110         }
111     }
112 
113     /**
114      * Get a spell checker session for the specified spell checker
115      * @param locale the locale for the spell checker. If {@code locale} is null and
116      * referToSpellCheckerLanguageSettings is true, the locale specified in Settings will be
117      * returned. If {@code locale} is not null and referToSpellCheckerLanguageSettings is true,
118      * the locale specified in Settings will be returned only when it is same as {@code locale}.
119      * Exceptionally, when referToSpellCheckerLanguageSettings is true and {@code locale} is
120      * only language (e.g. "en"), the specified locale in Settings (e.g. "en_US") will be
121      * selected.
122      * @param listener a spell checker session lister for getting results from a spell checker.
123      * @param referToSpellCheckerLanguageSettings if true, the session for one of enabled
124      * languages in settings will be returned.
125      * @return the spell checker session of the spell checker
126      */
newSpellCheckerSession(Bundle bundle, Locale locale, SpellCheckerSessionListener listener, boolean referToSpellCheckerLanguageSettings)127     public SpellCheckerSession newSpellCheckerSession(Bundle bundle, Locale locale,
128             SpellCheckerSessionListener listener, boolean referToSpellCheckerLanguageSettings) {
129         if (listener == null) {
130             throw new NullPointerException();
131         }
132         if (!referToSpellCheckerLanguageSettings && locale == null) {
133             throw new IllegalArgumentException("Locale should not be null if you don't refer"
134                     + " settings.");
135         }
136 
137         if (referToSpellCheckerLanguageSettings && !isSpellCheckerEnabled()) {
138             return null;
139         }
140 
141         final SpellCheckerInfo sci;
142         try {
143             sci = mService.getCurrentSpellChecker(null);
144         } catch (RemoteException e) {
145             return null;
146         }
147         if (sci == null) {
148             return null;
149         }
150         SpellCheckerSubtype subtypeInUse = null;
151         if (referToSpellCheckerLanguageSettings) {
152             subtypeInUse = getCurrentSpellCheckerSubtype(true);
153             if (subtypeInUse == null) {
154                 return null;
155             }
156             if (locale != null) {
157                 final String subtypeLocale = subtypeInUse.getLocale();
158                 final String subtypeLanguage = parseLanguageFromLocaleString(subtypeLocale);
159                 if (subtypeLanguage.length() < 2 || !locale.getLanguage().equals(subtypeLanguage)) {
160                     return null;
161                 }
162             }
163         } else {
164             final String localeStr = locale.toString();
165             for (int i = 0; i < sci.getSubtypeCount(); ++i) {
166                 final SpellCheckerSubtype subtype = sci.getSubtypeAt(i);
167                 final String tempSubtypeLocale = subtype.getLocale();
168                 final String tempSubtypeLanguage = parseLanguageFromLocaleString(tempSubtypeLocale);
169                 if (tempSubtypeLocale.equals(localeStr)) {
170                     subtypeInUse = subtype;
171                     break;
172                 } else if (tempSubtypeLanguage.length() >= 2 &&
173                         locale.getLanguage().equals(tempSubtypeLanguage)) {
174                     subtypeInUse = subtype;
175                 }
176             }
177         }
178         if (subtypeInUse == null) {
179             return null;
180         }
181         final SpellCheckerSession session = new SpellCheckerSession(sci, mService, listener);
182         try {
183             mService.getSpellCheckerService(sci.getId(), subtypeInUse.getLocale(),
184                     session.getTextServicesSessionListener(),
185                     session.getSpellCheckerSessionListener(), bundle);
186         } catch (RemoteException e) {
187             throw e.rethrowFromSystemServer();
188         }
189         return session;
190     }
191 
192     /**
193      * @hide
194      */
getEnabledSpellCheckers()195     public SpellCheckerInfo[] getEnabledSpellCheckers() {
196         try {
197             final SpellCheckerInfo[] retval = mService.getEnabledSpellCheckers();
198             if (DBG) {
199                 Log.d(TAG, "getEnabledSpellCheckers: " + (retval != null ? retval.length : "null"));
200             }
201             return retval;
202         } catch (RemoteException e) {
203             throw e.rethrowFromSystemServer();
204         }
205     }
206 
207     /**
208      * @hide
209      */
getCurrentSpellChecker()210     public SpellCheckerInfo getCurrentSpellChecker() {
211         try {
212             // Passing null as a locale for ICS
213             return mService.getCurrentSpellChecker(null);
214         } catch (RemoteException e) {
215             throw e.rethrowFromSystemServer();
216         }
217     }
218 
219     /**
220      * @hide
221      */
getCurrentSpellCheckerSubtype( boolean allowImplicitlySelectedSubtype)222     public SpellCheckerSubtype getCurrentSpellCheckerSubtype(
223             boolean allowImplicitlySelectedSubtype) {
224         try {
225             // Passing null as a locale until we support multiple enabled spell checker subtypes.
226             return mService.getCurrentSpellCheckerSubtype(null, allowImplicitlySelectedSubtype);
227         } catch (RemoteException e) {
228             throw e.rethrowFromSystemServer();
229         }
230     }
231 
232     /**
233      * @hide
234      */
isSpellCheckerEnabled()235     public boolean isSpellCheckerEnabled() {
236         try {
237             return mService.isSpellCheckerEnabled();
238         } catch (RemoteException e) {
239             throw e.rethrowFromSystemServer();
240         }
241     }
242 }
243