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