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