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 
     32 import java.util.Collections;
     33 import java.util.List;
     34 
     35 /**
     36  * Enrichment class for InputMethodManager to simplify interaction and add functionality.
     37  */
     38 public final class RichInputMethodManager {
     39     private static final String TAG = RichInputMethodManager.class.getSimpleName();
     40 
     41     private RichInputMethodManager() {
     42         // This utility class is not publicly instantiable.
     43     }
     44 
     45     private static final RichInputMethodManager sInstance = new RichInputMethodManager();
     46 
     47     private InputMethodManagerCompatWrapper mImmWrapper;
     48     private InputMethodInfo mInputMethodInfoOfThisIme;
     49 
     50     private static final int INDEX_NOT_FOUND = -1;
     51 
     52     public static RichInputMethodManager getInstance() {
     53         sInstance.checkInitialized();
     54         return sInstance;
     55     }
     56 
     57     // Caveat: This may cause IPC
     58     public static boolean isInputMethodManagerValidForUserOfThisProcess(final Context context) {
     59         // Basically called to check whether this IME has been triggered by the current user or not
     60         return !((InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE)).
     61                 getInputMethodList().isEmpty();
     62     }
     63 
     64     public static void init(final Context context) {
     65         final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
     66         sInstance.initInternal(context, prefs);
     67     }
     68 
     69     private boolean isInitialized() {
     70         return mImmWrapper != null;
     71     }
     72 
     73     private void checkInitialized() {
     74         if (!isInitialized()) {
     75             throw new RuntimeException(TAG + " is used before initialization");
     76         }
     77     }
     78 
     79     private void initInternal(final Context context, final SharedPreferences prefs) {
     80         if (isInitialized()) {
     81             return;
     82         }
     83         mImmWrapper = new InputMethodManagerCompatWrapper(context);
     84         mInputMethodInfoOfThisIme = getInputMethodInfoOfThisIme(context);
     85 
     86         // Initialize additional subtypes.
     87         SubtypeLocale.init(context);
     88         final String prefAdditionalSubtypes = Settings.readPrefAdditionalSubtypes(
     89                 prefs, context.getResources());
     90         final InputMethodSubtype[] additionalSubtypes =
     91                 AdditionalSubtype.createAdditionalSubtypesArray(prefAdditionalSubtypes);
     92         setAdditionalInputMethodSubtypes(additionalSubtypes);
     93     }
     94 
     95     public InputMethodManager getInputMethodManager() {
     96         checkInitialized();
     97         return mImmWrapper.mImm;
     98     }
     99 
    100     private InputMethodInfo getInputMethodInfoOfThisIme(final Context context) {
    101         final String packageName = context.getPackageName();
    102         for (final InputMethodInfo imi : mImmWrapper.mImm.getInputMethodList()) {
    103             if (imi.getPackageName().equals(packageName)) {
    104                 return imi;
    105             }
    106         }
    107         throw new RuntimeException("Input method id for " + packageName + " not found.");
    108     }
    109 
    110     public List<InputMethodSubtype> getMyEnabledInputMethodSubtypeList(
    111             boolean allowsImplicitlySelectedSubtypes) {
    112         return mImmWrapper.mImm.getEnabledInputMethodSubtypeList(
    113                 mInputMethodInfoOfThisIme, allowsImplicitlySelectedSubtypes);
    114     }
    115 
    116     public boolean switchToNextInputMethod(final IBinder token, final boolean onlyCurrentIme) {
    117         if (mImmWrapper.switchToNextInputMethod(token, onlyCurrentIme)) {
    118             return true;
    119         }
    120         // Was not able to call {@link InputMethodManager#switchToNextInputMethodIBinder,boolean)}
    121         // because the current device is running ICS or previous and lacks the API.
    122         if (switchToNextInputSubtypeInThisIme(token, onlyCurrentIme)) {
    123             return true;
    124         }
    125         return switchToNextInputMethodAndSubtype(token);
    126     }
    127 
    128     private boolean switchToNextInputSubtypeInThisIme(final IBinder token,
    129             final boolean onlyCurrentIme) {
    130         final InputMethodManager imm = mImmWrapper.mImm;
    131         final InputMethodSubtype currentSubtype = imm.getCurrentInputMethodSubtype();
    132         final List<InputMethodSubtype> enabledSubtypes = getMyEnabledInputMethodSubtypeList(
    133                 true /* allowsImplicitlySelectedSubtypes */);
    134         final int currentIndex = getSubtypeIndexInList(currentSubtype, enabledSubtypes);
    135         if (currentIndex == INDEX_NOT_FOUND) {
    136             Log.w(TAG, "Can't find current subtype in enabled subtypes: subtype="
    137                     + SubtypeLocale.getSubtypeDisplayName(currentSubtype));
    138             return false;
    139         }
    140         final int nextIndex = (currentIndex + 1) % enabledSubtypes.size();
    141         if (nextIndex <= currentIndex && !onlyCurrentIme) {
    142             // The current subtype is the last or only enabled one and it needs to switch to
    143             // next IME.
    144             return false;
    145         }
    146         final InputMethodSubtype nextSubtype = enabledSubtypes.get(nextIndex);
    147         setInputMethodAndSubtype(token, nextSubtype);
    148         return true;
    149     }
    150 
    151     private boolean switchToNextInputMethodAndSubtype(final IBinder token) {
    152         final InputMethodManager imm = mImmWrapper.mImm;
    153         final List<InputMethodInfo> enabledImis = imm.getEnabledInputMethodList();
    154         final int currentIndex = getImiIndexInList(mInputMethodInfoOfThisIme, enabledImis);
    155         if (currentIndex == INDEX_NOT_FOUND) {
    156             Log.w(TAG, "Can't find current IME in enabled IMEs: IME package="
    157                     + mInputMethodInfoOfThisIme.getPackageName());
    158             return false;
    159         }
    160         final InputMethodInfo nextImi = getNextNonAuxiliaryIme(currentIndex, enabledImis);
    161         final List<InputMethodSubtype> enabledSubtypes = imm.getEnabledInputMethodSubtypeList(
    162                 nextImi, true /* allowsImplicitlySelectedSubtypes */);
    163         if (enabledSubtypes.isEmpty()) {
    164             // The next IME has no subtype.
    165             imm.setInputMethod(token, nextImi.getId());
    166             return true;
    167         }
    168         final InputMethodSubtype firstSubtype = enabledSubtypes.get(0);
    169         imm.setInputMethodAndSubtype(token, nextImi.getId(), firstSubtype);
    170         return true;
    171     }
    172 
    173     private static int getImiIndexInList(final InputMethodInfo inputMethodInfo,
    174             final List<InputMethodInfo> imiList) {
    175         final int count = imiList.size();
    176         for (int index = 0; index < count; index++) {
    177             final InputMethodInfo imi = imiList.get(index);
    178             if (imi.equals(inputMethodInfo)) {
    179                 return index;
    180             }
    181         }
    182         return INDEX_NOT_FOUND;
    183     }
    184 
    185     // This method mimics {@link InputMethodManager#switchToNextInputMethod(IBinder,boolean)}.
    186     private static InputMethodInfo getNextNonAuxiliaryIme(final int currentIndex,
    187             final List<InputMethodInfo> imiList) {
    188         final int count = imiList.size();
    189         for (int i = 1; i < count; i++) {
    190             final int nextIndex = (currentIndex + i) % count;
    191             final InputMethodInfo nextImi = imiList.get(nextIndex);
    192             if (!isAuxiliaryIme(nextImi)) {
    193                 return nextImi;
    194             }
    195         }
    196         return imiList.get(currentIndex);
    197     }
    198 
    199     // Copied from {@link InputMethodInfo}. See how auxiliary of IME is determined.
    200     private static boolean isAuxiliaryIme(final InputMethodInfo imi) {
    201         final int count = imi.getSubtypeCount();
    202         if (count == 0) {
    203             return false;
    204         }
    205         for (int index = 0; index < count; index++) {
    206             final InputMethodSubtype subtype = imi.getSubtypeAt(index);
    207             if (!subtype.isAuxiliary()) {
    208                 return false;
    209             }
    210         }
    211         return true;
    212     }
    213 
    214     public InputMethodInfo getInputMethodInfoOfThisIme() {
    215         return mInputMethodInfoOfThisIme;
    216     }
    217 
    218     public String getInputMethodIdOfThisIme() {
    219         return mInputMethodInfoOfThisIme.getId();
    220     }
    221 
    222     public boolean checkIfSubtypeBelongsToThisImeAndEnabled(final InputMethodSubtype subtype) {
    223         return checkIfSubtypeBelongsToImeAndEnabled(mInputMethodInfoOfThisIme, subtype);
    224     }
    225 
    226     public boolean checkIfSubtypeBelongsToThisImeAndImplicitlyEnabled(
    227             final InputMethodSubtype subtype) {
    228         final boolean subtypeEnabled = checkIfSubtypeBelongsToThisImeAndEnabled(subtype);
    229         final boolean subtypeExplicitlyEnabled = checkIfSubtypeBelongsToList(
    230                 subtype, getMyEnabledInputMethodSubtypeList(
    231                         false /* allowsImplicitlySelectedSubtypes */));
    232         return subtypeEnabled && !subtypeExplicitlyEnabled;
    233     }
    234 
    235     public boolean checkIfSubtypeBelongsToImeAndEnabled(final InputMethodInfo imi,
    236             final InputMethodSubtype subtype) {
    237         return checkIfSubtypeBelongsToList(
    238                 subtype, mImmWrapper.mImm.getEnabledInputMethodSubtypeList(
    239                         imi, true /* allowsImplicitlySelectedSubtypes */));
    240     }
    241 
    242     private static boolean checkIfSubtypeBelongsToList(final InputMethodSubtype subtype,
    243             final List<InputMethodSubtype> subtypes) {
    244         return getSubtypeIndexInList(subtype, subtypes) != INDEX_NOT_FOUND;
    245     }
    246 
    247     private static int getSubtypeIndexInList(final InputMethodSubtype subtype,
    248             final List<InputMethodSubtype> subtypes) {
    249         final int count = subtypes.size();
    250         for (int index = 0; index < count; index++) {
    251             final InputMethodSubtype ims = subtypes.get(index);
    252             if (ims.equals(subtype)) {
    253                 return index;
    254             }
    255         }
    256         return INDEX_NOT_FOUND;
    257     }
    258 
    259     public boolean checkIfSubtypeBelongsToThisIme(final InputMethodSubtype subtype) {
    260         return getSubtypeIndexInIme(subtype, mInputMethodInfoOfThisIme) != INDEX_NOT_FOUND;
    261     }
    262 
    263     private static int getSubtypeIndexInIme(final InputMethodSubtype subtype,
    264             final InputMethodInfo imi) {
    265         final int count = imi.getSubtypeCount();
    266         for (int index = 0; index < count; index++) {
    267             final InputMethodSubtype ims = imi.getSubtypeAt(index);
    268             if (ims.equals(subtype)) {
    269                 return index;
    270             }
    271         }
    272         return INDEX_NOT_FOUND;
    273     }
    274 
    275     public InputMethodSubtype getCurrentInputMethodSubtype(
    276             final InputMethodSubtype defaultSubtype) {
    277         final InputMethodSubtype currentSubtype = mImmWrapper.mImm.getCurrentInputMethodSubtype();
    278         return (currentSubtype != null) ? currentSubtype : defaultSubtype;
    279     }
    280 
    281     public boolean hasMultipleEnabledIMEsOrSubtypes(final boolean shouldIncludeAuxiliarySubtypes) {
    282         final List<InputMethodInfo> enabledImis = mImmWrapper.mImm.getEnabledInputMethodList();
    283         return hasMultipleEnabledSubtypes(shouldIncludeAuxiliarySubtypes, enabledImis);
    284     }
    285 
    286     public boolean hasMultipleEnabledSubtypesInThisIme(
    287             final boolean shouldIncludeAuxiliarySubtypes) {
    288         final List<InputMethodInfo> imiList = Collections.singletonList(mInputMethodInfoOfThisIme);
    289         return hasMultipleEnabledSubtypes(shouldIncludeAuxiliarySubtypes, imiList);
    290     }
    291 
    292     private boolean hasMultipleEnabledSubtypes(final boolean shouldIncludeAuxiliarySubtypes,
    293             final List<InputMethodInfo> imiList) {
    294         // Number of the filtered IMEs
    295         int filteredImisCount = 0;
    296 
    297         for (InputMethodInfo imi : imiList) {
    298             // We can return true immediately after we find two or more filtered IMEs.
    299             if (filteredImisCount > 1) return true;
    300             final List<InputMethodSubtype> subtypes =
    301                     mImmWrapper.mImm.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 = SubtypeLocale.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     }
    365 }
    366