SubtypeSwitcher.java revision 8e3c585265366628b45315123832f4fc372ffdb1
1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of 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, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.inputmethod.latin; 18 19import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.REQ_NETWORK_CONNECTIVITY; 20 21import android.content.Context; 22import android.content.Intent; 23import android.content.res.Resources; 24import android.inputmethodservice.InputMethodService; 25import android.net.ConnectivityManager; 26import android.net.NetworkInfo; 27import android.os.AsyncTask; 28import android.os.IBinder; 29import android.util.Log; 30import android.view.inputmethod.InputMethodInfo; 31import android.view.inputmethod.InputMethodManager; 32import android.view.inputmethod.InputMethodSubtype; 33 34import com.android.inputmethod.annotations.UsedForTesting; 35import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils; 36import com.android.inputmethod.keyboard.KeyboardSwitcher; 37import com.android.inputmethod.keyboard.internal.NeedsToDisplayLanguage; 38import com.android.inputmethod.latin.utils.LocaleUtils; 39import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; 40 41import java.util.HashSet; 42import java.util.List; 43import java.util.Locale; 44import java.util.Map; 45import java.util.Set; 46 47public final class SubtypeSwitcher { 48 private static boolean DBG = LatinImeLogger.sDBG; 49 private static final String TAG = SubtypeSwitcher.class.getSimpleName(); 50 51 private static final SubtypeSwitcher sInstance = new SubtypeSwitcher(); 52 53 private /* final */ RichInputMethodManager mRichImm; 54 private /* final */ Resources mResources; 55 private /* final */ ConnectivityManager mConnectivityManager; 56 57 private final NeedsToDisplayLanguage mNeedsToDisplayLanguage = new NeedsToDisplayLanguage(); 58 private InputMethodInfo mShortcutInputMethodInfo; 59 private InputMethodSubtype mShortcutSubtype; 60 private InputMethodSubtype mNoLanguageSubtype; 61 private InputMethodSubtype mEmojiSubtype; 62 private boolean mIsNetworkConnected; 63 64 private static final String KEYBOARD_MODE = "keyboard"; 65 // Dummy no language QWERTY subtype. See {@link R.xml.method}. 66 private static final int SUBTYPE_ID_OF_DUMMY_NO_LANGUAGE_SUBTYPE = 0xdde0bfd3; 67 private static final String EXTRA_VALUE_OF_DUMMY_NO_LANGUAGE_SUBTYPE = 68 "KeyboardLayoutSet=" + SubtypeLocaleUtils.QWERTY 69 + "," + Constants.Subtype.ExtraValue.ASCII_CAPABLE 70 + "," + Constants.Subtype.ExtraValue.ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE 71 + "," + Constants.Subtype.ExtraValue.EMOJI_CAPABLE; 72 private static final InputMethodSubtype DUMMY_NO_LANGUAGE_SUBTYPE = 73 InputMethodSubtypeCompatUtils.newInputMethodSubtype( 74 R.string.subtype_no_language_qwerty, R.drawable.ic_ime_switcher_dark, 75 SubtypeLocaleUtils.NO_LANGUAGE, KEYBOARD_MODE, 76 EXTRA_VALUE_OF_DUMMY_NO_LANGUAGE_SUBTYPE, 77 false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */, 78 SUBTYPE_ID_OF_DUMMY_NO_LANGUAGE_SUBTYPE); 79 // Caveat: We probably should remove this when we add an Emoji subtype in {@link R.xml.method}. 80 // Dummy Emoji subtype. See {@link R.xml.method}. 81 private static final int SUBTYPE_ID_OF_DUMMY_EMOJI_SUBTYPE = 0xd78b2ed0; 82 private static final String EXTRA_VALUE_OF_DUMMY_EMOJI_SUBTYPE = 83 "KeyboardLayoutSet=" + SubtypeLocaleUtils.EMOJI 84 + "," + Constants.Subtype.ExtraValue.EMOJI_CAPABLE; 85 private static final InputMethodSubtype DUMMY_EMOJI_SUBTYPE = 86 InputMethodSubtypeCompatUtils.newInputMethodSubtype( 87 R.string.subtype_emoji, R.drawable.ic_ime_switcher_dark, 88 SubtypeLocaleUtils.NO_LANGUAGE, KEYBOARD_MODE, 89 EXTRA_VALUE_OF_DUMMY_EMOJI_SUBTYPE, 90 false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */, 91 SUBTYPE_ID_OF_DUMMY_EMOJI_SUBTYPE); 92 93 public static SubtypeSwitcher getInstance() { 94 return sInstance; 95 } 96 97 public static void init(final Context context) { 98 SubtypeLocaleUtils.init(context); 99 RichInputMethodManager.init(context); 100 sInstance.initialize(context); 101 } 102 103 private SubtypeSwitcher() { 104 // Intentional empty constructor for singleton. 105 } 106 107 private void initialize(final Context context) { 108 if (mResources != null) { 109 return; 110 } 111 mResources = context.getResources(); 112 mRichImm = RichInputMethodManager.getInstance(); 113 mConnectivityManager = (ConnectivityManager) context.getSystemService( 114 Context.CONNECTIVITY_SERVICE); 115 116 final NetworkInfo info = mConnectivityManager.getActiveNetworkInfo(); 117 mIsNetworkConnected = (info != null && info.isConnected()); 118 119 onSubtypeChanged(getCurrentSubtype()); 120 updateParametersOnStartInputView(); 121 } 122 123 /** 124 * Update parameters which are changed outside LatinIME. This parameters affect UI so that they 125 * should be updated every time onStartInputView is called. 126 */ 127 public void updateParametersOnStartInputView() { 128 final List<InputMethodSubtype> enabledSubtypesOfThisIme = 129 mRichImm.getMyEnabledInputMethodSubtypeList(true); 130 mNeedsToDisplayLanguage.updateEnabledSubtypeCount(enabledSubtypesOfThisIme.size()); 131 updateShortcutIME(); 132 } 133 134 private void updateShortcutIME() { 135 if (DBG) { 136 Log.d(TAG, "Update shortcut IME from : " 137 + (mShortcutInputMethodInfo == null 138 ? "<null>" : mShortcutInputMethodInfo.getId()) + ", " 139 + (mShortcutSubtype == null ? "<null>" : ( 140 mShortcutSubtype.getLocale() + ", " + mShortcutSubtype.getMode()))); 141 } 142 // TODO: Update an icon for shortcut IME 143 final Map<InputMethodInfo, List<InputMethodSubtype>> shortcuts = 144 mRichImm.getInputMethodManager().getShortcutInputMethodsAndSubtypes(); 145 mShortcutInputMethodInfo = null; 146 mShortcutSubtype = null; 147 for (final InputMethodInfo imi : shortcuts.keySet()) { 148 final List<InputMethodSubtype> subtypes = shortcuts.get(imi); 149 // TODO: Returns the first found IMI for now. Should handle all shortcuts as 150 // appropriate. 151 mShortcutInputMethodInfo = imi; 152 // TODO: Pick up the first found subtype for now. Should handle all subtypes 153 // as appropriate. 154 mShortcutSubtype = subtypes.size() > 0 ? subtypes.get(0) : null; 155 break; 156 } 157 if (DBG) { 158 Log.d(TAG, "Update shortcut IME to : " 159 + (mShortcutInputMethodInfo == null 160 ? "<null>" : mShortcutInputMethodInfo.getId()) + ", " 161 + (mShortcutSubtype == null ? "<null>" : ( 162 mShortcutSubtype.getLocale() + ", " + mShortcutSubtype.getMode()))); 163 } 164 } 165 166 // Update the current subtype. LatinIME.onCurrentInputMethodSubtypeChanged calls this function. 167 public void onSubtypeChanged(final InputMethodSubtype newSubtype) { 168 if (DBG) { 169 Log.w(TAG, "onSubtypeChanged: " 170 + SubtypeLocaleUtils.getSubtypeNameForLogging(newSubtype)); 171 } 172 173 final Locale newLocale = SubtypeLocaleUtils.getSubtypeLocale(newSubtype); 174 final Locale systemLocale = mResources.getConfiguration().locale; 175 final boolean sameLocale = systemLocale.equals(newLocale); 176 final boolean sameLanguage = systemLocale.getLanguage().equals(newLocale.getLanguage()); 177 final boolean implicitlyEnabled = 178 mRichImm.checkIfSubtypeBelongsToThisImeAndImplicitlyEnabled(newSubtype); 179 mNeedsToDisplayLanguage.updateIsSystemLanguageSameAsInputLanguage( 180 sameLocale || (sameLanguage && implicitlyEnabled)); 181 182 updateShortcutIME(); 183 } 184 185 //////////////////////////// 186 // Shortcut IME functions // 187 //////////////////////////// 188 189 public void switchToShortcutIME(final InputMethodService context) { 190 if (mShortcutInputMethodInfo == null) { 191 return; 192 } 193 194 final String imiId = mShortcutInputMethodInfo.getId(); 195 switchToTargetIME(imiId, mShortcutSubtype, context); 196 } 197 198 private void switchToTargetIME(final String imiId, final InputMethodSubtype subtype, 199 final InputMethodService context) { 200 final IBinder token = context.getWindow().getWindow().getAttributes().token; 201 if (token == null) { 202 return; 203 } 204 final InputMethodManager imm = mRichImm.getInputMethodManager(); 205 new AsyncTask<Void, Void, Void>() { 206 @Override 207 protected Void doInBackground(Void... params) { 208 imm.setInputMethodAndSubtype(token, imiId, subtype); 209 return null; 210 } 211 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 212 } 213 214 public boolean isShortcutImeEnabled() { 215 updateShortcutIME(); 216 if (mShortcutInputMethodInfo == null) { 217 return false; 218 } 219 if (mShortcutSubtype == null) { 220 return true; 221 } 222 return mRichImm.checkIfSubtypeBelongsToImeAndEnabled( 223 mShortcutInputMethodInfo, mShortcutSubtype); 224 } 225 226 public boolean isShortcutImeReady() { 227 updateShortcutIME(); 228 if (mShortcutInputMethodInfo == null) { 229 return false; 230 } 231 if (mShortcutSubtype == null) { 232 return true; 233 } 234 if (mShortcutSubtype.containsExtraValueKey(REQ_NETWORK_CONNECTIVITY)) { 235 return mIsNetworkConnected; 236 } 237 return true; 238 } 239 240 public void onNetworkStateChanged(final Intent intent) { 241 final boolean noConnection = intent.getBooleanExtra( 242 ConnectivityManager.EXTRA_NO_CONNECTIVITY, false); 243 mIsNetworkConnected = !noConnection; 244 245 KeyboardSwitcher.getInstance().onNetworkStateChanged(); 246 } 247 248 ////////////////////////////////// 249 // Subtype Switching functions // 250 ////////////////////////////////// 251 252 public boolean needsToDisplayLanguage(final InputMethodSubtype subtype) { 253 return mNeedsToDisplayLanguage.needsToDisplayLanguage(subtype); 254 } 255 256 public boolean isSystemLocaleSameAsLocaleOfAllEnabledSubtypesOfEnabledImes() { 257 final Locale systemLocale = mResources.getConfiguration().locale; 258 final Set<InputMethodSubtype> enabledSubtypesOfEnabledImes = 259 new HashSet<InputMethodSubtype>(); 260 final InputMethodManager inputMethodManager = mRichImm.getInputMethodManager(); 261 final List<InputMethodInfo> enabledInputMethodInfoList = 262 inputMethodManager.getEnabledInputMethodList(); 263 for (final InputMethodInfo info : enabledInputMethodInfoList) { 264 final List<InputMethodSubtype> enabledSubtypes = 265 inputMethodManager.getEnabledInputMethodSubtypeList( 266 info, true /* allowsImplicitlySelectedSubtypes */); 267 if (enabledSubtypes.isEmpty()) { 268 // An IME with no subtypes is found. 269 return false; 270 } 271 enabledSubtypesOfEnabledImes.addAll(enabledSubtypes); 272 } 273 for (final InputMethodSubtype subtype : enabledSubtypesOfEnabledImes) { 274 if (!subtype.isAuxiliary() && !subtype.getLocale().isEmpty() 275 && !systemLocale.equals(SubtypeLocaleUtils.getSubtypeLocale(subtype))) { 276 return false; 277 } 278 } 279 return true; 280 } 281 282 private static InputMethodSubtype sForcedSubtypeForTesting = null; 283 @UsedForTesting 284 void forceSubtype(final InputMethodSubtype subtype) { 285 sForcedSubtypeForTesting = subtype; 286 } 287 288 public Locale getCurrentSubtypeLocale() { 289 if (null != sForcedSubtypeForTesting) { 290 return LocaleUtils.constructLocaleFromString(sForcedSubtypeForTesting.getLocale()); 291 } 292 return SubtypeLocaleUtils.getSubtypeLocale(getCurrentSubtype()); 293 } 294 295 public InputMethodSubtype getCurrentSubtype() { 296 if (null != sForcedSubtypeForTesting) { 297 return sForcedSubtypeForTesting; 298 } 299 return mRichImm.getCurrentInputMethodSubtype(getNoLanguageSubtype()); 300 } 301 302 public InputMethodSubtype getNoLanguageSubtype() { 303 if (mNoLanguageSubtype == null) { 304 mNoLanguageSubtype = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( 305 SubtypeLocaleUtils.NO_LANGUAGE, SubtypeLocaleUtils.QWERTY); 306 } 307 if (mNoLanguageSubtype != null) { 308 return mNoLanguageSubtype; 309 } 310 Log.w(TAG, "Can't find any language with QWERTY subtype"); 311 Log.w(TAG, "No input method subtype found; returning dummy subtype: " 312 + DUMMY_NO_LANGUAGE_SUBTYPE); 313 return DUMMY_NO_LANGUAGE_SUBTYPE; 314 } 315 316 public InputMethodSubtype getEmojiSubtype() { 317 if (mEmojiSubtype == null) { 318 mEmojiSubtype = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( 319 SubtypeLocaleUtils.NO_LANGUAGE, SubtypeLocaleUtils.EMOJI); 320 } 321 if (mEmojiSubtype != null) { 322 return mEmojiSubtype; 323 } 324 Log.w(TAG, "Can't find emoji subtype"); 325 Log.w(TAG, "No input method subtype found; returning dummy subtype: " 326 + DUMMY_EMOJI_SUBTYPE); 327 return DUMMY_EMOJI_SUBTYPE; 328 } 329} 330