Home | History | Annotate | Download | only in latin
      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.Constants.Subtype.KEYBOARD_MODE;
     20 
     21 import android.content.Context;
     22 import android.content.SharedPreferences;
     23 import android.os.IBinder;
     24 import android.preference.PreferenceManager;
     25 import android.util.Log;
     26 import android.view.inputmethod.InputMethodInfo;
     27 import android.view.inputmethod.InputMethodManager;
     28 import android.view.inputmethod.InputMethodSubtype;
     29 
     30 import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
     31 import com.android.inputmethod.latin.settings.Settings;
     32 import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
     33 import com.android.inputmethod.latin.utils.CollectionUtils;
     34 import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
     35 
     36 import java.util.Collections;
     37 import java.util.HashMap;
     38 import java.util.List;
     39 
     40 /**
     41  * Enrichment class for InputMethodManager to simplify interaction and add functionality.
     42  */
     43 public final class RichInputMethodManager {
     44     private static final String TAG = RichInputMethodManager.class.getSimpleName();
     45 
     46     private RichInputMethodManager() {
     47         // This utility class is not publicly instantiable.
     48     }
     49 
     50     private static final RichInputMethodManager sInstance = new RichInputMethodManager();
     51 
     52     private InputMethodManagerCompatWrapper mImmWrapper;
     53     private InputMethodInfo mInputMethodInfoOfThisIme;
     54     final HashMap<InputMethodInfo, List<InputMethodSubtype>>
     55             mSubtypeListCacheWithImplicitlySelectedSubtypes = CollectionUtils.newHashMap();
     56     final HashMap<InputMethodInfo, List<InputMethodSubtype>>
     57             mSubtypeListCacheWithoutImplicitlySelectedSubtypes = CollectionUtils.newHashMap();
     58 
     59     private static final int INDEX_NOT_FOUND = -1;
     60 
     61     public static RichInputMethodManager getInstance() {
     62         sInstance.checkInitialized();
     63         return sInstance;
     64     }
     65 
     66     public static void init(final Context context) {
     67         final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
     68         sInstance.initInternal(context, prefs);
     69     }
     70 
     71     private boolean isInitialized() {
     72         return mImmWrapper != null;
     73     }
     74 
     75     private void checkInitialized() {
     76         if (!isInitialized()) {
     77             throw new RuntimeException(TAG + " is used before initialization");
     78         }
     79     }
     80 
     81     private void initInternal(final Context context, final SharedPreferences prefs) {
     82         if (isInitialized()) {
     83             return;
     84         }
     85         mImmWrapper = new InputMethodManagerCompatWrapper(context);
     86         mInputMethodInfoOfThisIme = getInputMethodInfoOfThisIme(context);
     87 
     88         // Initialize additional subtypes.
     89         SubtypeLocaleUtils.init(context);
     90         final String prefAdditionalSubtypes = Settings.readPrefAdditionalSubtypes(
     91                 prefs, context.getResources());
     92         final InputMethodSubtype[] additionalSubtypes =
     93                 AdditionalSubtypeUtils.createAdditionalSubtypesArray(prefAdditionalSubtypes);
     94         setAdditionalInputMethodSubtypes(additionalSubtypes);
     95     }
     96 
     97     public InputMethodManager getInputMethodManager() {
     98         checkInitialized();
     99         return mImmWrapper.mImm;
    100     }
    101 
    102     private InputMethodInfo getInputMethodInfoOfThisIme(final Context context) {
    103         final String packageName = context.getPackageName();
    104         for (final InputMethodInfo imi : mImmWrapper.mImm.getInputMethodList()) {
    105             if (imi.getPackageName().equals(packageName)) {
    106                 return imi;
    107             }
    108         }
    109         throw new RuntimeException("Input method id for " + packageName + " not found.");
    110     }
    111 
    112     public List<InputMethodSubtype> getMyEnabledInputMethodSubtypeList(
    113             boolean allowsImplicitlySelectedSubtypes) {
    114         return getEnabledInputMethodSubtypeList(mInputMethodInfoOfThisIme,
    115                 allowsImplicitlySelectedSubtypes);
    116     }
    117 
    118     public boolean switchToNextInputMethod(final IBinder token, final boolean onlyCurrentIme) {
    119         if (mImmWrapper.switchToNextInputMethod(token, onlyCurrentIme)) {
    120             return true;
    121         }
    122         // Was not able to call {@link InputMethodManager#switchToNextInputMethodIBinder,boolean)}
    123         // because the current device is running ICS or previous and lacks the API.
    124         if (switchToNextInputSubtypeInThisIme(token, onlyCurrentIme)) {
    125             return true;
    126         }
    127         return switchToNextInputMethodAndSubtype(token);
    128     }
    129 
    130     private boolean switchToNextInputSubtypeInThisIme(final IBinder token,
    131             final boolean onlyCurrentIme) {
    132         final InputMethodManager imm = mImmWrapper.mImm;
    133         final InputMethodSubtype currentSubtype = imm.getCurrentInputMethodSubtype();
    134         final List<InputMethodSubtype> enabledSubtypes = getMyEnabledInputMethodSubtypeList(
    135                 true /* allowsImplicitlySelectedSubtypes */);
    136         final int currentIndex = getSubtypeIndexInList(currentSubtype, enabledSubtypes);
    137         if (currentIndex == INDEX_NOT_FOUND) {
    138             Log.w(TAG, "Can't find current subtype in enabled subtypes: subtype="
    139                     + SubtypeLocaleUtils.getSubtypeNameForLogging(currentSubtype));
    140             return false;
    141         }
    142         final int nextIndex = (currentIndex + 1) % enabledSubtypes.size();
    143         if (nextIndex <= currentIndex && !onlyCurrentIme) {
    144             // The current subtype is the last or only enabled one and it needs to switch to
    145             // next IME.
    146             return false;
    147         }
    148         final InputMethodSubtype nextSubtype = enabledSubtypes.get(nextIndex);
    149         setInputMethodAndSubtype(token, nextSubtype);
    150         return true;
    151     }
    152 
    153     private boolean switchToNextInputMethodAndSubtype(final IBinder token) {
    154         final InputMethodManager imm = mImmWrapper.mImm;
    155         final List<InputMethodInfo> enabledImis = imm.getEnabledInputMethodList();
    156         final int currentIndex = getImiIndexInList(mInputMethodInfoOfThisIme, enabledImis);
    157         if (currentIndex == INDEX_NOT_FOUND) {
    158             Log.w(TAG, "Can't find current IME in enabled IMEs: IME package="
    159                     + mInputMethodInfoOfThisIme.getPackageName());
    160             return false;
    161         }
    162         final InputMethodInfo nextImi = getNextNonAuxiliaryIme(currentIndex, enabledImis);
    163         final List<InputMethodSubtype> enabledSubtypes = getEnabledInputMethodSubtypeList(nextImi,
    164                 true /* allowsImplicitlySelectedSubtypes */);
    165         if (enabledSubtypes.isEmpty()) {
    166             // The next IME has no subtype.
    167             imm.setInputMethod(token, nextImi.getId());
    168             return true;
    169         }
    170         final InputMethodSubtype firstSubtype = enabledSubtypes.get(0);
    171         imm.setInputMethodAndSubtype(token, nextImi.getId(), firstSubtype);
    172         return true;
    173     }
    174 
    175     private static int getImiIndexInList(final InputMethodInfo inputMethodInfo,
    176             final List<InputMethodInfo> imiList) {
    177         final int count = imiList.size();
    178         for (int index = 0; index < count; index++) {
    179             final InputMethodInfo imi = imiList.get(index);
    180             if (imi.equals(inputMethodInfo)) {
    181                 return index;
    182             }
    183         }
    184         return INDEX_NOT_FOUND;
    185     }
    186 
    187     // This method mimics {@link InputMethodManager#switchToNextInputMethod(IBinder,boolean)}.
    188     private static InputMethodInfo getNextNonAuxiliaryIme(final int currentIndex,
    189             final List<InputMethodInfo> imiList) {
    190         final int count = imiList.size();
    191         for (int i = 1; i < count; i++) {
    192             final int nextIndex = (currentIndex + i) % count;
    193             final InputMethodInfo nextImi = imiList.get(nextIndex);
    194             if (!isAuxiliaryIme(nextImi)) {
    195                 return nextImi;
    196             }
    197         }
    198         return imiList.get(currentIndex);
    199     }
    200 
    201     // Copied from {@link InputMethodInfo}. See how auxiliary of IME is determined.
    202     private static boolean isAuxiliaryIme(final InputMethodInfo imi) {
    203         final int count = imi.getSubtypeCount();
    204         if (count == 0) {
    205             return false;
    206         }
    207         for (int index = 0; index < count; index++) {
    208             final InputMethodSubtype subtype = imi.getSubtypeAt(index);
    209             if (!subtype.isAuxiliary()) {
    210                 return false;
    211             }
    212         }
    213         return true;
    214     }
    215 
    216     public InputMethodInfo getInputMethodInfoOfThisIme() {
    217         return mInputMethodInfoOfThisIme;
    218     }
    219 
    220     public String getInputMethodIdOfThisIme() {
    221         return mInputMethodInfoOfThisIme.getId();
    222     }
    223 
    224     public boolean checkIfSubtypeBelongsToThisImeAndEnabled(final InputMethodSubtype subtype) {
    225         return checkIfSubtypeBelongsToImeAndEnabled(mInputMethodInfoOfThisIme, subtype);
    226     }
    227 
    228     public boolean checkIfSubtypeBelongsToThisImeAndImplicitlyEnabled(
    229             final InputMethodSubtype subtype) {
    230         final boolean subtypeEnabled = checkIfSubtypeBelongsToThisImeAndEnabled(subtype);
    231         final boolean subtypeExplicitlyEnabled = checkIfSubtypeBelongsToList(
    232                 subtype, getMyEnabledInputMethodSubtypeList(
    233                         false /* allowsImplicitlySelectedSubtypes */));
    234         return subtypeEnabled && !subtypeExplicitlyEnabled;
    235     }
    236 
    237     public boolean checkIfSubtypeBelongsToImeAndEnabled(final InputMethodInfo imi,
    238             final InputMethodSubtype subtype) {
    239         return checkIfSubtypeBelongsToList(subtype, getEnabledInputMethodSubtypeList(imi,
    240                 true /* allowsImplicitlySelectedSubtypes */));
    241     }
    242 
    243     private static boolean checkIfSubtypeBelongsToList(final InputMethodSubtype subtype,
    244             final List<InputMethodSubtype> subtypes) {
    245         return getSubtypeIndexInList(subtype, subtypes) != INDEX_NOT_FOUND;
    246     }
    247 
    248     private static int getSubtypeIndexInList(final InputMethodSubtype subtype,
    249             final List<InputMethodSubtype> subtypes) {
    250         final int count = subtypes.size();
    251         for (int index = 0; index < count; index++) {
    252             final InputMethodSubtype ims = subtypes.get(index);
    253             if (ims.equals(subtype)) {
    254                 return index;
    255             }
    256         }
    257         return INDEX_NOT_FOUND;
    258     }
    259 
    260     public boolean checkIfSubtypeBelongsToThisIme(final InputMethodSubtype subtype) {
    261         return getSubtypeIndexInIme(subtype, mInputMethodInfoOfThisIme) != INDEX_NOT_FOUND;
    262     }
    263 
    264     private static int getSubtypeIndexInIme(final InputMethodSubtype subtype,
    265             final InputMethodInfo imi) {
    266         final int count = imi.getSubtypeCount();
    267         for (int index = 0; index < count; index++) {
    268             final InputMethodSubtype ims = imi.getSubtypeAt(index);
    269             if (ims.equals(subtype)) {
    270                 return index;
    271             }
    272         }
    273         return INDEX_NOT_FOUND;
    274     }
    275 
    276     public InputMethodSubtype getCurrentInputMethodSubtype(
    277             final InputMethodSubtype defaultSubtype) {
    278         final InputMethodSubtype currentSubtype = mImmWrapper.mImm.getCurrentInputMethodSubtype();
    279         return (currentSubtype != null) ? currentSubtype : defaultSubtype;
    280     }
    281 
    282     public boolean hasMultipleEnabledIMEsOrSubtypes(final boolean shouldIncludeAuxiliarySubtypes) {
    283         final List<InputMethodInfo> enabledImis = mImmWrapper.mImm.getEnabledInputMethodList();
    284         return hasMultipleEnabledSubtypes(shouldIncludeAuxiliarySubtypes, enabledImis);
    285     }
    286 
    287     public boolean hasMultipleEnabledSubtypesInThisIme(
    288             final boolean shouldIncludeAuxiliarySubtypes) {
    289         final List<InputMethodInfo> imiList = Collections.singletonList(mInputMethodInfoOfThisIme);
    290         return hasMultipleEnabledSubtypes(shouldIncludeAuxiliarySubtypes, imiList);
    291     }
    292 
    293     private boolean hasMultipleEnabledSubtypes(final boolean shouldIncludeAuxiliarySubtypes,
    294             final List<InputMethodInfo> imiList) {
    295         // Number of the filtered IMEs
    296         int filteredImisCount = 0;
    297 
    298         for (InputMethodInfo imi : imiList) {
    299             // We can return true immediately after we find two or more filtered IMEs.
    300             if (filteredImisCount > 1) return true;
    301             final List<InputMethodSubtype> subtypes = getEnabledInputMethodSubtypeList(imi, true);
    302             // IMEs that have no subtypes should be counted.
    303             if (subtypes.isEmpty()) {
    304                 ++filteredImisCount;
    305                 continue;
    306             }
    307 
    308             int auxCount = 0;
    309             for (InputMethodSubtype subtype : subtypes) {
    310                 if (subtype.isAuxiliary()) {
    311                     ++auxCount;
    312                 }
    313             }
    314             final int nonAuxCount = subtypes.size() - auxCount;
    315 
    316             // IMEs that have one or more non-auxiliary subtypes should be counted.
    317             // If shouldIncludeAuxiliarySubtypes is true, IMEs that have two or more auxiliary
    318             // subtypes should be counted as well.
    319             if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) {
    320                 ++filteredImisCount;
    321                 continue;
    322             }
    323         }
    324 
    325         if (filteredImisCount > 1) {
    326             return true;
    327         }
    328         final List<InputMethodSubtype> subtypes = getMyEnabledInputMethodSubtypeList(true);
    329         int keyboardCount = 0;
    330         // imm.getEnabledInputMethodSubtypeList(null, true) will return the current IME's
    331         // both explicitly and implicitly enabled input method subtype.
    332         // (The current IME should be LatinIME.)
    333         for (InputMethodSubtype subtype : subtypes) {
    334             if (KEYBOARD_MODE.equals(subtype.getMode())) {
    335                 ++keyboardCount;
    336             }
    337         }
    338         return keyboardCount > 1;
    339     }
    340 
    341     public InputMethodSubtype findSubtypeByLocaleAndKeyboardLayoutSet(final String localeString,
    342             final String keyboardLayoutSetName) {
    343         final InputMethodInfo myImi = mInputMethodInfoOfThisIme;
    344         final int count = myImi.getSubtypeCount();
    345         for (int i = 0; i < count; i++) {
    346             final InputMethodSubtype subtype = myImi.getSubtypeAt(i);
    347             final String layoutName = SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype);
    348             if (localeString.equals(subtype.getLocale())
    349                     && keyboardLayoutSetName.equals(layoutName)) {
    350                 return subtype;
    351             }
    352         }
    353         return null;
    354     }
    355 
    356     public void setInputMethodAndSubtype(final IBinder token, final InputMethodSubtype subtype) {
    357         mImmWrapper.mImm.setInputMethodAndSubtype(
    358                 token, mInputMethodInfoOfThisIme.getId(), subtype);
    359     }
    360 
    361     public void setAdditionalInputMethodSubtypes(final InputMethodSubtype[] subtypes) {
    362         mImmWrapper.mImm.setAdditionalInputMethodSubtypes(
    363                 mInputMethodInfoOfThisIme.getId(), subtypes);
    364         // Clear the cache so that we go read the subtypes again next time.
    365         clearSubtypeCaches();
    366     }
    367 
    368     private List<InputMethodSubtype> getEnabledInputMethodSubtypeList(final InputMethodInfo imi,
    369             final boolean allowsImplicitlySelectedSubtypes) {
    370         final HashMap<InputMethodInfo, List<InputMethodSubtype>> cache =
    371                 allowsImplicitlySelectedSubtypes
    372                 ? mSubtypeListCacheWithImplicitlySelectedSubtypes
    373                 : mSubtypeListCacheWithoutImplicitlySelectedSubtypes;
    374         final List<InputMethodSubtype> cachedList = cache.get(imi);
    375         if (null != cachedList) return cachedList;
    376         final List<InputMethodSubtype> result = mImmWrapper.mImm.getEnabledInputMethodSubtypeList(
    377                 imi, allowsImplicitlySelectedSubtypes);
    378         cache.put(imi, result);
    379         return result;
    380     }
    381 
    382     public void clearSubtypeCaches() {
    383         mSubtypeListCacheWithImplicitlySelectedSubtypes.clear();
    384         mSubtypeListCacheWithoutImplicitlySelectedSubtypes.clear();
    385     }
    386 }
    387