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