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 * Get a spell checker session for the specified spell checker 94 * @param locale the locale for the spell checker. If {@code locale} is null and 95 * referToSpellCheckerLanguageSettings is true, the locale specified in Settings will be 96 * returned. If {@code locale} is not null and referToSpellCheckerLanguageSettings is true, 97 * the locale specified in Settings will be returned only when it is same as {@code locale}. 98 * Exceptionally, when referToSpellCheckerLanguageSettings is true and {@code locale} is 99 * only language (e.g. "en"), the specified locale in Settings (e.g. "en_US") will be 100 * selected. 101 * @param listener a spell checker session lister for getting results from a spell checker. 102 * @param referToSpellCheckerLanguageSettings if true, the session for one of enabled 103 * languages in settings will be returned. 104 * @return the spell checker session of the spell checker 105 */ 106 public SpellCheckerSession newSpellCheckerSession(Bundle bundle, Locale locale, 107 SpellCheckerSessionListener listener, boolean referToSpellCheckerLanguageSettings) { 108 if (listener == null) { 109 throw new NullPointerException(); 110 } 111 if (!referToSpellCheckerLanguageSettings && locale == null) { 112 throw new IllegalArgumentException("Locale should not be null if you don't refer" 113 + " settings."); 114 } 115 116 if (referToSpellCheckerLanguageSettings && !isSpellCheckerEnabled()) { 117 return null; 118 } 119 120 final SpellCheckerInfo sci; 121 try { 122 sci = sService.getCurrentSpellChecker(null); 123 } catch (RemoteException e) { 124 return null; 125 } 126 if (sci == null) { 127 return null; 128 } 129 SpellCheckerSubtype subtypeInUse = null; 130 if (referToSpellCheckerLanguageSettings) { 131 subtypeInUse = getCurrentSpellCheckerSubtype(true); 132 if (subtypeInUse == null) { 133 return null; 134 } 135 if (locale != null) { 136 final String subtypeLocale = subtypeInUse.getLocale(); 137 final String inputLocale = locale.toString(); 138 if (subtypeLocale.length() < 2 || inputLocale.length() < 2 139 || !subtypeLocale.substring(0, 2).equals(inputLocale.substring(0, 2))) { 140 return null; 141 } 142 } 143 } else { 144 final String localeStr = locale.toString(); 145 for (int i = 0; i < sci.getSubtypeCount(); ++i) { 146 final SpellCheckerSubtype subtype = sci.getSubtypeAt(i); 147 final String tempSubtypeLocale = subtype.getLocale(); 148 if (tempSubtypeLocale.equals(localeStr)) { 149 subtypeInUse = subtype; 150 break; 151 } else if (localeStr.length() >= 2 && tempSubtypeLocale.length() >= 2 152 && localeStr.startsWith(tempSubtypeLocale)) { 153 subtypeInUse = subtype; 154 } 155 } 156 } 157 if (subtypeInUse == null) { 158 return null; 159 } 160 final SpellCheckerSession session = new SpellCheckerSession( 161 sci, sService, listener, subtypeInUse); 162 try { 163 sService.getSpellCheckerService(sci.getId(), subtypeInUse.getLocale(), 164 session.getTextServicesSessionListener(), 165 session.getSpellCheckerSessionListener(), bundle); 166 } catch (RemoteException e) { 167 return null; 168 } 169 return session; 170 } 171 172 /** 173 * @hide 174 */ 175 public SpellCheckerInfo[] getEnabledSpellCheckers() { 176 try { 177 final SpellCheckerInfo[] retval = sService.getEnabledSpellCheckers(); 178 if (DBG) { 179 Log.d(TAG, "getEnabledSpellCheckers: " + (retval != null ? retval.length : "null")); 180 } 181 return retval; 182 } catch (RemoteException e) { 183 Log.e(TAG, "Error in getEnabledSpellCheckers: " + e); 184 return null; 185 } 186 } 187 188 /** 189 * @hide 190 */ 191 public SpellCheckerInfo getCurrentSpellChecker() { 192 try { 193 // Passing null as a locale for ICS 194 return sService.getCurrentSpellChecker(null); 195 } catch (RemoteException e) { 196 return null; 197 } 198 } 199 200 /** 201 * @hide 202 */ 203 public void setCurrentSpellChecker(SpellCheckerInfo sci) { 204 try { 205 if (sci == null) { 206 throw new NullPointerException("SpellCheckerInfo is null."); 207 } 208 sService.setCurrentSpellChecker(null, sci.getId()); 209 } catch (RemoteException e) { 210 Log.e(TAG, "Error in setCurrentSpellChecker: " + e); 211 } 212 } 213 214 /** 215 * @hide 216 */ 217 public SpellCheckerSubtype getCurrentSpellCheckerSubtype( 218 boolean allowImplicitlySelectedSubtype) { 219 try { 220 if (sService == null) { 221 // TODO: This is a workaround. Needs to investigate why sService could be null 222 // here. 223 Log.e(TAG, "sService is null."); 224 return null; 225 } 226 // Passing null as a locale until we support multiple enabled spell checker subtypes. 227 return sService.getCurrentSpellCheckerSubtype(null, allowImplicitlySelectedSubtype); 228 } catch (RemoteException e) { 229 Log.e(TAG, "Error in getCurrentSpellCheckerSubtype: " + e); 230 return null; 231 } 232 } 233 234 /** 235 * @hide 236 */ 237 public void setSpellCheckerSubtype(SpellCheckerSubtype subtype) { 238 try { 239 final int hashCode; 240 if (subtype == null) { 241 hashCode = 0; 242 } else { 243 hashCode = subtype.hashCode(); 244 } 245 sService.setCurrentSpellCheckerSubtype(null, hashCode); 246 } catch (RemoteException e) { 247 Log.e(TAG, "Error in setSpellCheckerSubtype:" + e); 248 } 249 } 250 251 /** 252 * @hide 253 */ 254 public void setSpellCheckerEnabled(boolean enabled) { 255 try { 256 sService.setSpellCheckerEnabled(enabled); 257 } catch (RemoteException e) { 258 Log.e(TAG, "Error in setSpellCheckerEnabled:" + e); 259 } 260 } 261 262 /** 263 * @hide 264 */ 265 public boolean isSpellCheckerEnabled() { 266 try { 267 return sService.isSpellCheckerEnabled(); 268 } catch (RemoteException e) { 269 Log.e(TAG, "Error in isSpellCheckerEnabled:" + e); 270 return false; 271 } 272 } 273 } 274