1 /* 2 * Copyright (C) 2012 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 17 package com.android.inputmethod.latin; 18 19 import static com.android.inputmethod.latin.common.Constants.Subtype.KEYBOARD_MODE; 20 21 import android.content.Context; 22 import android.content.SharedPreferences; 23 import android.inputmethodservice.InputMethodService; 24 import android.os.AsyncTask; 25 import android.os.Build; 26 import android.os.IBinder; 27 import android.preference.PreferenceManager; 28 import android.util.Log; 29 import android.view.inputmethod.InputMethodInfo; 30 import android.view.inputmethod.InputMethodManager; 31 import android.view.inputmethod.InputMethodSubtype; 32 33 import com.android.inputmethod.annotations.UsedForTesting; 34 import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; 35 import com.android.inputmethod.latin.settings.Settings; 36 import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils; 37 import com.android.inputmethod.latin.utils.LanguageOnSpacebarUtils; 38 import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; 39 40 import java.util.Collections; 41 import java.util.HashMap; 42 import java.util.HashSet; 43 import java.util.List; 44 import java.util.Locale; 45 import java.util.Map; 46 import java.util.Set; 47 48 import javax.annotation.Nonnull; 49 import javax.annotation.Nullable; 50 51 /** 52 * Enrichment class for InputMethodManager to simplify interaction and add functionality. 53 */ 54 // non final for easy mocking. 55 public class RichInputMethodManager { 56 private static final String TAG = RichInputMethodManager.class.getSimpleName(); 57 private static final boolean DEBUG = false; 58 RichInputMethodManager()59 private RichInputMethodManager() { 60 // This utility class is not publicly instantiable. 61 } 62 63 private static final RichInputMethodManager sInstance = new RichInputMethodManager(); 64 65 private Context mContext; 66 private InputMethodManagerCompatWrapper mImmWrapper; 67 private InputMethodInfoCache mInputMethodInfoCache; 68 private RichInputMethodSubtype mCurrentRichInputMethodSubtype; 69 private InputMethodInfo mShortcutInputMethodInfo; 70 private InputMethodSubtype mShortcutSubtype; 71 72 private static final int INDEX_NOT_FOUND = -1; 73 getInstance()74 public static RichInputMethodManager getInstance() { 75 sInstance.checkInitialized(); 76 return sInstance; 77 } 78 init(final Context context)79 public static void init(final Context context) { 80 sInstance.initInternal(context); 81 } 82 isInitialized()83 private boolean isInitialized() { 84 return mImmWrapper != null; 85 } 86 checkInitialized()87 private void checkInitialized() { 88 if (!isInitialized()) { 89 throw new RuntimeException(TAG + " is used before initialization"); 90 } 91 } 92 initInternal(final Context context)93 private void initInternal(final Context context) { 94 if (isInitialized()) { 95 return; 96 } 97 mImmWrapper = new InputMethodManagerCompatWrapper(context); 98 mContext = context; 99 mInputMethodInfoCache = new InputMethodInfoCache( 100 mImmWrapper.mImm, context.getPackageName()); 101 102 // Initialize additional subtypes. 103 SubtypeLocaleUtils.init(context); 104 final InputMethodSubtype[] additionalSubtypes = getAdditionalSubtypes(); 105 mImmWrapper.mImm.setAdditionalInputMethodSubtypes( 106 getInputMethodIdOfThisIme(), additionalSubtypes); 107 108 // Initialize the current input method subtype and the shortcut IME. 109 refreshSubtypeCaches(); 110 } 111 getAdditionalSubtypes()112 public InputMethodSubtype[] getAdditionalSubtypes() { 113 final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext); 114 final String prefAdditionalSubtypes = Settings.readPrefAdditionalSubtypes( 115 prefs, mContext.getResources()); 116 return AdditionalSubtypeUtils.createAdditionalSubtypesArray(prefAdditionalSubtypes); 117 } 118 getInputMethodManager()119 public InputMethodManager getInputMethodManager() { 120 checkInitialized(); 121 return mImmWrapper.mImm; 122 } 123 getMyEnabledInputMethodSubtypeList( boolean allowsImplicitlySelectedSubtypes)124 public List<InputMethodSubtype> getMyEnabledInputMethodSubtypeList( 125 boolean allowsImplicitlySelectedSubtypes) { 126 return getEnabledInputMethodSubtypeList( 127 getInputMethodInfoOfThisIme(), allowsImplicitlySelectedSubtypes); 128 } 129 switchToNextInputMethod(final IBinder token, final boolean onlyCurrentIme)130 public boolean switchToNextInputMethod(final IBinder token, final boolean onlyCurrentIme) { 131 if (mImmWrapper.switchToNextInputMethod(token, onlyCurrentIme)) { 132 return true; 133 } 134 // Was not able to call {@link InputMethodManager#switchToNextInputMethodIBinder,boolean)} 135 // because the current device is running ICS or previous and lacks the API. 136 if (switchToNextInputSubtypeInThisIme(token, onlyCurrentIme)) { 137 return true; 138 } 139 return switchToNextInputMethodAndSubtype(token); 140 } 141 switchToNextInputSubtypeInThisIme(final IBinder token, final boolean onlyCurrentIme)142 private boolean switchToNextInputSubtypeInThisIme(final IBinder token, 143 final boolean onlyCurrentIme) { 144 final InputMethodManager imm = mImmWrapper.mImm; 145 final InputMethodSubtype currentSubtype = imm.getCurrentInputMethodSubtype(); 146 final List<InputMethodSubtype> enabledSubtypes = getMyEnabledInputMethodSubtypeList( 147 true /* allowsImplicitlySelectedSubtypes */); 148 final int currentIndex = getSubtypeIndexInList(currentSubtype, enabledSubtypes); 149 if (currentIndex == INDEX_NOT_FOUND) { 150 Log.w(TAG, "Can't find current subtype in enabled subtypes: subtype=" 151 + SubtypeLocaleUtils.getSubtypeNameForLogging(currentSubtype)); 152 return false; 153 } 154 final int nextIndex = (currentIndex + 1) % enabledSubtypes.size(); 155 if (nextIndex <= currentIndex && !onlyCurrentIme) { 156 // The current subtype is the last or only enabled one and it needs to switch to 157 // next IME. 158 return false; 159 } 160 final InputMethodSubtype nextSubtype = enabledSubtypes.get(nextIndex); 161 setInputMethodAndSubtype(token, nextSubtype); 162 return true; 163 } 164 switchToNextInputMethodAndSubtype(final IBinder token)165 private boolean switchToNextInputMethodAndSubtype(final IBinder token) { 166 final InputMethodManager imm = mImmWrapper.mImm; 167 final List<InputMethodInfo> enabledImis = imm.getEnabledInputMethodList(); 168 final int currentIndex = getImiIndexInList(getInputMethodInfoOfThisIme(), enabledImis); 169 if (currentIndex == INDEX_NOT_FOUND) { 170 Log.w(TAG, "Can't find current IME in enabled IMEs: IME package=" 171 + getInputMethodInfoOfThisIme().getPackageName()); 172 return false; 173 } 174 final InputMethodInfo nextImi = getNextNonAuxiliaryIme(currentIndex, enabledImis); 175 final List<InputMethodSubtype> enabledSubtypes = getEnabledInputMethodSubtypeList(nextImi, 176 true /* allowsImplicitlySelectedSubtypes */); 177 if (enabledSubtypes.isEmpty()) { 178 // The next IME has no subtype. 179 imm.setInputMethod(token, nextImi.getId()); 180 return true; 181 } 182 final InputMethodSubtype firstSubtype = enabledSubtypes.get(0); 183 imm.setInputMethodAndSubtype(token, nextImi.getId(), firstSubtype); 184 return true; 185 } 186 getImiIndexInList(final InputMethodInfo inputMethodInfo, final List<InputMethodInfo> imiList)187 private static int getImiIndexInList(final InputMethodInfo inputMethodInfo, 188 final List<InputMethodInfo> imiList) { 189 final int count = imiList.size(); 190 for (int index = 0; index < count; index++) { 191 final InputMethodInfo imi = imiList.get(index); 192 if (imi.equals(inputMethodInfo)) { 193 return index; 194 } 195 } 196 return INDEX_NOT_FOUND; 197 } 198 199 // This method mimics {@link InputMethodManager#switchToNextInputMethod(IBinder,boolean)}. getNextNonAuxiliaryIme(final int currentIndex, final List<InputMethodInfo> imiList)200 private static InputMethodInfo getNextNonAuxiliaryIme(final int currentIndex, 201 final List<InputMethodInfo> imiList) { 202 final int count = imiList.size(); 203 for (int i = 1; i < count; i++) { 204 final int nextIndex = (currentIndex + i) % count; 205 final InputMethodInfo nextImi = imiList.get(nextIndex); 206 if (!isAuxiliaryIme(nextImi)) { 207 return nextImi; 208 } 209 } 210 return imiList.get(currentIndex); 211 } 212 213 // Copied from {@link InputMethodInfo}. See how auxiliary of IME is determined. isAuxiliaryIme(final InputMethodInfo imi)214 private static boolean isAuxiliaryIme(final InputMethodInfo imi) { 215 final int count = imi.getSubtypeCount(); 216 if (count == 0) { 217 return false; 218 } 219 for (int index = 0; index < count; index++) { 220 final InputMethodSubtype subtype = imi.getSubtypeAt(index); 221 if (!subtype.isAuxiliary()) { 222 return false; 223 } 224 } 225 return true; 226 } 227 228 private static class InputMethodInfoCache { 229 private final InputMethodManager mImm; 230 private final String mImePackageName; 231 232 private InputMethodInfo mCachedThisImeInfo; 233 private final HashMap<InputMethodInfo, List<InputMethodSubtype>> 234 mCachedSubtypeListWithImplicitlySelected; 235 private final HashMap<InputMethodInfo, List<InputMethodSubtype>> 236 mCachedSubtypeListOnlyExplicitlySelected; 237 InputMethodInfoCache(final InputMethodManager imm, final String imePackageName)238 public InputMethodInfoCache(final InputMethodManager imm, final String imePackageName) { 239 mImm = imm; 240 mImePackageName = imePackageName; 241 mCachedSubtypeListWithImplicitlySelected = new HashMap<>(); 242 mCachedSubtypeListOnlyExplicitlySelected = new HashMap<>(); 243 } 244 getInputMethodOfThisIme()245 public synchronized InputMethodInfo getInputMethodOfThisIme() { 246 if (mCachedThisImeInfo != null) { 247 return mCachedThisImeInfo; 248 } 249 for (final InputMethodInfo imi : mImm.getInputMethodList()) { 250 if (imi.getPackageName().equals(mImePackageName)) { 251 mCachedThisImeInfo = imi; 252 return imi; 253 } 254 } 255 throw new RuntimeException("Input method id for " + mImePackageName + " not found."); 256 } 257 getEnabledInputMethodSubtypeList( final InputMethodInfo imi, final boolean allowsImplicitlySelectedSubtypes)258 public synchronized List<InputMethodSubtype> getEnabledInputMethodSubtypeList( 259 final InputMethodInfo imi, final boolean allowsImplicitlySelectedSubtypes) { 260 final HashMap<InputMethodInfo, List<InputMethodSubtype>> cache = 261 allowsImplicitlySelectedSubtypes 262 ? mCachedSubtypeListWithImplicitlySelected 263 : mCachedSubtypeListOnlyExplicitlySelected; 264 final List<InputMethodSubtype> cachedList = cache.get(imi); 265 if (cachedList != null) { 266 return cachedList; 267 } 268 final List<InputMethodSubtype> result = mImm.getEnabledInputMethodSubtypeList( 269 imi, allowsImplicitlySelectedSubtypes); 270 cache.put(imi, result); 271 return result; 272 } 273 clear()274 public synchronized void clear() { 275 mCachedThisImeInfo = null; 276 mCachedSubtypeListWithImplicitlySelected.clear(); 277 mCachedSubtypeListOnlyExplicitlySelected.clear(); 278 } 279 } 280 getInputMethodInfoOfThisIme()281 public InputMethodInfo getInputMethodInfoOfThisIme() { 282 return mInputMethodInfoCache.getInputMethodOfThisIme(); 283 } 284 getInputMethodIdOfThisIme()285 public String getInputMethodIdOfThisIme() { 286 return getInputMethodInfoOfThisIme().getId(); 287 } 288 checkIfSubtypeBelongsToThisImeAndEnabled(final InputMethodSubtype subtype)289 public boolean checkIfSubtypeBelongsToThisImeAndEnabled(final InputMethodSubtype subtype) { 290 return checkIfSubtypeBelongsToList(subtype, 291 getEnabledInputMethodSubtypeList( 292 getInputMethodInfoOfThisIme(), 293 true /* allowsImplicitlySelectedSubtypes */)); 294 } 295 checkIfSubtypeBelongsToThisImeAndImplicitlyEnabled( final InputMethodSubtype subtype)296 public boolean checkIfSubtypeBelongsToThisImeAndImplicitlyEnabled( 297 final InputMethodSubtype subtype) { 298 final boolean subtypeEnabled = checkIfSubtypeBelongsToThisImeAndEnabled(subtype); 299 final boolean subtypeExplicitlyEnabled = checkIfSubtypeBelongsToList(subtype, 300 getMyEnabledInputMethodSubtypeList(false /* allowsImplicitlySelectedSubtypes */)); 301 return subtypeEnabled && !subtypeExplicitlyEnabled; 302 } 303 checkIfSubtypeBelongsToList(final InputMethodSubtype subtype, final List<InputMethodSubtype> subtypes)304 private static boolean checkIfSubtypeBelongsToList(final InputMethodSubtype subtype, 305 final List<InputMethodSubtype> subtypes) { 306 return getSubtypeIndexInList(subtype, subtypes) != INDEX_NOT_FOUND; 307 } 308 getSubtypeIndexInList(final InputMethodSubtype subtype, final List<InputMethodSubtype> subtypes)309 private static int getSubtypeIndexInList(final InputMethodSubtype subtype, 310 final List<InputMethodSubtype> subtypes) { 311 final int count = subtypes.size(); 312 for (int index = 0; index < count; index++) { 313 final InputMethodSubtype ims = subtypes.get(index); 314 if (ims.equals(subtype)) { 315 return index; 316 } 317 } 318 return INDEX_NOT_FOUND; 319 } 320 onSubtypeChanged(@onnull final InputMethodSubtype newSubtype)321 public void onSubtypeChanged(@Nonnull final InputMethodSubtype newSubtype) { 322 updateCurrentSubtype(newSubtype); 323 updateShortcutIme(); 324 if (DEBUG) { 325 Log.w(TAG, "onSubtypeChanged: " + mCurrentRichInputMethodSubtype.getNameForLogging()); 326 } 327 } 328 329 private static RichInputMethodSubtype sForcedSubtypeForTesting = null; 330 331 @UsedForTesting forceSubtype(@onnull final InputMethodSubtype subtype)332 static void forceSubtype(@Nonnull final InputMethodSubtype subtype) { 333 sForcedSubtypeForTesting = RichInputMethodSubtype.getRichInputMethodSubtype(subtype); 334 } 335 336 @Nonnull getCurrentSubtypeLocale()337 public Locale getCurrentSubtypeLocale() { 338 if (null != sForcedSubtypeForTesting) { 339 return sForcedSubtypeForTesting.getLocale(); 340 } 341 return getCurrentSubtype().getLocale(); 342 } 343 344 @Nonnull getCurrentSubtype()345 public RichInputMethodSubtype getCurrentSubtype() { 346 if (null != sForcedSubtypeForTesting) { 347 return sForcedSubtypeForTesting; 348 } 349 return mCurrentRichInputMethodSubtype; 350 } 351 352 getCombiningRulesExtraValueOfCurrentSubtype()353 public String getCombiningRulesExtraValueOfCurrentSubtype() { 354 return SubtypeLocaleUtils.getCombiningRulesExtraValue(getCurrentSubtype().getRawSubtype()); 355 } 356 hasMultipleEnabledIMEsOrSubtypes(final boolean shouldIncludeAuxiliarySubtypes)357 public boolean hasMultipleEnabledIMEsOrSubtypes(final boolean shouldIncludeAuxiliarySubtypes) { 358 final List<InputMethodInfo> enabledImis = mImmWrapper.mImm.getEnabledInputMethodList(); 359 return hasMultipleEnabledSubtypes(shouldIncludeAuxiliarySubtypes, enabledImis); 360 } 361 hasMultipleEnabledSubtypesInThisIme( final boolean shouldIncludeAuxiliarySubtypes)362 public boolean hasMultipleEnabledSubtypesInThisIme( 363 final boolean shouldIncludeAuxiliarySubtypes) { 364 final List<InputMethodInfo> imiList = Collections.singletonList( 365 getInputMethodInfoOfThisIme()); 366 return hasMultipleEnabledSubtypes(shouldIncludeAuxiliarySubtypes, imiList); 367 } 368 hasMultipleEnabledSubtypes(final boolean shouldIncludeAuxiliarySubtypes, final List<InputMethodInfo> imiList)369 private boolean hasMultipleEnabledSubtypes(final boolean shouldIncludeAuxiliarySubtypes, 370 final List<InputMethodInfo> imiList) { 371 // Number of the filtered IMEs 372 int filteredImisCount = 0; 373 374 for (InputMethodInfo imi : imiList) { 375 // We can return true immediately after we find two or more filtered IMEs. 376 if (filteredImisCount > 1) return true; 377 final List<InputMethodSubtype> subtypes = getEnabledInputMethodSubtypeList(imi, true); 378 // IMEs that have no subtypes should be counted. 379 if (subtypes.isEmpty()) { 380 ++filteredImisCount; 381 continue; 382 } 383 384 int auxCount = 0; 385 for (InputMethodSubtype subtype : subtypes) { 386 if (subtype.isAuxiliary()) { 387 ++auxCount; 388 } 389 } 390 final int nonAuxCount = subtypes.size() - auxCount; 391 392 // IMEs that have one or more non-auxiliary subtypes should be counted. 393 // If shouldIncludeAuxiliarySubtypes is true, IMEs that have two or more auxiliary 394 // subtypes should be counted as well. 395 if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) { 396 ++filteredImisCount; 397 } 398 } 399 400 if (filteredImisCount > 1) { 401 return true; 402 } 403 final List<InputMethodSubtype> subtypes = getMyEnabledInputMethodSubtypeList(true); 404 int keyboardCount = 0; 405 // imm.getEnabledInputMethodSubtypeList(null, true) will return the current IME's 406 // both explicitly and implicitly enabled input method subtype. 407 // (The current IME should be LatinIME.) 408 for (InputMethodSubtype subtype : subtypes) { 409 if (KEYBOARD_MODE.equals(subtype.getMode())) { 410 ++keyboardCount; 411 } 412 } 413 return keyboardCount > 1; 414 } 415 findSubtypeByLocaleAndKeyboardLayoutSet(final String localeString, final String keyboardLayoutSetName)416 public InputMethodSubtype findSubtypeByLocaleAndKeyboardLayoutSet(final String localeString, 417 final String keyboardLayoutSetName) { 418 final InputMethodInfo myImi = getInputMethodInfoOfThisIme(); 419 final int count = myImi.getSubtypeCount(); 420 for (int i = 0; i < count; i++) { 421 final InputMethodSubtype subtype = myImi.getSubtypeAt(i); 422 final String layoutName = SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype); 423 if (localeString.equals(subtype.getLocale()) 424 && keyboardLayoutSetName.equals(layoutName)) { 425 return subtype; 426 } 427 } 428 return null; 429 } 430 setInputMethodAndSubtype(final IBinder token, final InputMethodSubtype subtype)431 public void setInputMethodAndSubtype(final IBinder token, final InputMethodSubtype subtype) { 432 mImmWrapper.mImm.setInputMethodAndSubtype( 433 token, getInputMethodIdOfThisIme(), subtype); 434 } 435 setAdditionalInputMethodSubtypes(final InputMethodSubtype[] subtypes)436 public void setAdditionalInputMethodSubtypes(final InputMethodSubtype[] subtypes) { 437 mImmWrapper.mImm.setAdditionalInputMethodSubtypes( 438 getInputMethodIdOfThisIme(), subtypes); 439 // Clear the cache so that we go read the {@link InputMethodInfo} of this IME and list of 440 // subtypes again next time. 441 refreshSubtypeCaches(); 442 } 443 getEnabledInputMethodSubtypeList(final InputMethodInfo imi, final boolean allowsImplicitlySelectedSubtypes)444 private List<InputMethodSubtype> getEnabledInputMethodSubtypeList(final InputMethodInfo imi, 445 final boolean allowsImplicitlySelectedSubtypes) { 446 return mInputMethodInfoCache.getEnabledInputMethodSubtypeList( 447 imi, allowsImplicitlySelectedSubtypes); 448 } 449 refreshSubtypeCaches()450 public void refreshSubtypeCaches() { 451 mInputMethodInfoCache.clear(); 452 updateCurrentSubtype(mImmWrapper.mImm.getCurrentInputMethodSubtype()); 453 updateShortcutIme(); 454 } 455 shouldOfferSwitchingToNextInputMethod(final IBinder binder, boolean defaultValue)456 public boolean shouldOfferSwitchingToNextInputMethod(final IBinder binder, 457 boolean defaultValue) { 458 // Use the default value instead on Jelly Bean MR2 and previous where 459 // {@link InputMethodManager#shouldOfferSwitchingToNextInputMethod} isn't yet available 460 // and on KitKat where the API is still just a stub to return true always. 461 if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) { 462 return defaultValue; 463 } 464 return mImmWrapper.shouldOfferSwitchingToNextInputMethod(binder); 465 } 466 isSystemLocaleSameAsLocaleOfAllEnabledSubtypesOfEnabledImes()467 public boolean isSystemLocaleSameAsLocaleOfAllEnabledSubtypesOfEnabledImes() { 468 final Locale systemLocale = mContext.getResources().getConfiguration().locale; 469 final Set<InputMethodSubtype> enabledSubtypesOfEnabledImes = new HashSet<>(); 470 final InputMethodManager inputMethodManager = getInputMethodManager(); 471 final List<InputMethodInfo> enabledInputMethodInfoList = 472 inputMethodManager.getEnabledInputMethodList(); 473 for (final InputMethodInfo info : enabledInputMethodInfoList) { 474 final List<InputMethodSubtype> enabledSubtypes = 475 inputMethodManager.getEnabledInputMethodSubtypeList( 476 info, true /* allowsImplicitlySelectedSubtypes */); 477 if (enabledSubtypes.isEmpty()) { 478 // An IME with no subtypes is found. 479 return false; 480 } 481 enabledSubtypesOfEnabledImes.addAll(enabledSubtypes); 482 } 483 for (final InputMethodSubtype subtype : enabledSubtypesOfEnabledImes) { 484 if (!subtype.isAuxiliary() && !subtype.getLocale().isEmpty() 485 && !systemLocale.equals(SubtypeLocaleUtils.getSubtypeLocale(subtype))) { 486 return false; 487 } 488 } 489 return true; 490 } 491 updateCurrentSubtype(@ullable final InputMethodSubtype subtype)492 private void updateCurrentSubtype(@Nullable final InputMethodSubtype subtype) { 493 mCurrentRichInputMethodSubtype = RichInputMethodSubtype.getRichInputMethodSubtype(subtype); 494 } 495 updateShortcutIme()496 private void updateShortcutIme() { 497 if (DEBUG) { 498 Log.d(TAG, "Update shortcut IME from : " 499 + (mShortcutInputMethodInfo == null 500 ? "<null>" : mShortcutInputMethodInfo.getId()) + ", " 501 + (mShortcutSubtype == null ? "<null>" : ( 502 mShortcutSubtype.getLocale() + ", " + mShortcutSubtype.getMode()))); 503 } 504 final RichInputMethodSubtype richSubtype = mCurrentRichInputMethodSubtype; 505 final boolean implicitlyEnabledSubtype = checkIfSubtypeBelongsToThisImeAndImplicitlyEnabled( 506 richSubtype.getRawSubtype()); 507 final Locale systemLocale = mContext.getResources().getConfiguration().locale; 508 LanguageOnSpacebarUtils.onSubtypeChanged( 509 richSubtype, implicitlyEnabledSubtype, systemLocale); 510 LanguageOnSpacebarUtils.setEnabledSubtypes(getMyEnabledInputMethodSubtypeList( 511 true /* allowsImplicitlySelectedSubtypes */)); 512 513 // TODO: Update an icon for shortcut IME 514 final Map<InputMethodInfo, List<InputMethodSubtype>> shortcuts = 515 getInputMethodManager().getShortcutInputMethodsAndSubtypes(); 516 mShortcutInputMethodInfo = null; 517 mShortcutSubtype = null; 518 for (final InputMethodInfo imi : shortcuts.keySet()) { 519 final List<InputMethodSubtype> subtypes = shortcuts.get(imi); 520 // TODO: Returns the first found IMI for now. Should handle all shortcuts as 521 // appropriate. 522 mShortcutInputMethodInfo = imi; 523 // TODO: Pick up the first found subtype for now. Should handle all subtypes 524 // as appropriate. 525 mShortcutSubtype = subtypes.size() > 0 ? subtypes.get(0) : null; 526 break; 527 } 528 if (DEBUG) { 529 Log.d(TAG, "Update shortcut IME to : " 530 + (mShortcutInputMethodInfo == null 531 ? "<null>" : mShortcutInputMethodInfo.getId()) + ", " 532 + (mShortcutSubtype == null ? "<null>" : ( 533 mShortcutSubtype.getLocale() + ", " + mShortcutSubtype.getMode()))); 534 } 535 } 536 switchToShortcutIme(final InputMethodService context)537 public void switchToShortcutIme(final InputMethodService context) { 538 if (mShortcutInputMethodInfo == null) { 539 return; 540 } 541 542 final String imiId = mShortcutInputMethodInfo.getId(); 543 switchToTargetIME(imiId, mShortcutSubtype, context); 544 } 545 switchToTargetIME(final String imiId, final InputMethodSubtype subtype, final InputMethodService context)546 private void switchToTargetIME(final String imiId, final InputMethodSubtype subtype, 547 final InputMethodService context) { 548 final IBinder token = context.getWindow().getWindow().getAttributes().token; 549 if (token == null) { 550 return; 551 } 552 final InputMethodManager imm = getInputMethodManager(); 553 new AsyncTask<Void, Void, Void>() { 554 @Override 555 protected Void doInBackground(Void... params) { 556 imm.setInputMethodAndSubtype(token, imiId, subtype); 557 return null; 558 } 559 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 560 } 561 isShortcutImeReady()562 public boolean isShortcutImeReady() { 563 if (mShortcutInputMethodInfo == null) { 564 return false; 565 } 566 if (mShortcutSubtype == null) { 567 return true; 568 } 569 return true; 570 } 571 } 572