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