Home | History | Annotate | Download | only in latin
      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 
     17 package com.android.inputmethod.latin;
     18 
     19 import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.REQ_NETWORK_CONNECTIVITY;
     20 
     21 import android.content.Context;
     22 import android.content.Intent;
     23 import android.content.res.Resources;
     24 import android.inputmethodservice.InputMethodService;
     25 import android.net.ConnectivityManager;
     26 import android.net.NetworkInfo;
     27 import android.os.AsyncTask;
     28 import android.os.IBinder;
     29 import android.util.Log;
     30 import android.view.inputmethod.InputMethodInfo;
     31 import android.view.inputmethod.InputMethodManager;
     32 import android.view.inputmethod.InputMethodSubtype;
     33 
     34 import com.android.inputmethod.annotations.UsedForTesting;
     35 import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils;
     36 import com.android.inputmethod.keyboard.KeyboardSwitcher;
     37 import com.android.inputmethod.keyboard.internal.LanguageOnSpacebarHelper;
     38 import com.android.inputmethod.latin.define.DebugFlags;
     39 import com.android.inputmethod.latin.utils.LocaleUtils;
     40 import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
     41 
     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 public final class SubtypeSwitcher {
     49     private static boolean DBG = DebugFlags.DEBUG_ENABLED;
     50     private static final String TAG = SubtypeSwitcher.class.getSimpleName();
     51 
     52     private static final SubtypeSwitcher sInstance = new SubtypeSwitcher();
     53 
     54     private /* final */ RichInputMethodManager mRichImm;
     55     private /* final */ Resources mResources;
     56 
     57     private final LanguageOnSpacebarHelper mLanguageOnSpacebarHelper =
     58             new LanguageOnSpacebarHelper();
     59     private InputMethodInfo mShortcutInputMethodInfo;
     60     private InputMethodSubtype mShortcutSubtype;
     61     private InputMethodSubtype mNoLanguageSubtype;
     62     private InputMethodSubtype mEmojiSubtype;
     63     private boolean mIsNetworkConnected;
     64 
     65     private static final String KEYBOARD_MODE = "keyboard";
     66     // Dummy no language QWERTY subtype. See {@link R.xml.method}.
     67     private static final int SUBTYPE_ID_OF_DUMMY_NO_LANGUAGE_SUBTYPE = 0xdde0bfd3;
     68     private static final String EXTRA_VALUE_OF_DUMMY_NO_LANGUAGE_SUBTYPE =
     69             "KeyboardLayoutSet=" + SubtypeLocaleUtils.QWERTY
     70             + "," + Constants.Subtype.ExtraValue.ASCII_CAPABLE
     71             + "," + Constants.Subtype.ExtraValue.ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE
     72             + "," + Constants.Subtype.ExtraValue.EMOJI_CAPABLE;
     73     private static final InputMethodSubtype DUMMY_NO_LANGUAGE_SUBTYPE =
     74             InputMethodSubtypeCompatUtils.newInputMethodSubtype(
     75                     R.string.subtype_no_language_qwerty, R.drawable.ic_ime_switcher_dark,
     76                     SubtypeLocaleUtils.NO_LANGUAGE, KEYBOARD_MODE,
     77                     EXTRA_VALUE_OF_DUMMY_NO_LANGUAGE_SUBTYPE,
     78                     false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */,
     79                     SUBTYPE_ID_OF_DUMMY_NO_LANGUAGE_SUBTYPE);
     80     // Caveat: We probably should remove this when we add an Emoji subtype in {@link R.xml.method}.
     81     // Dummy Emoji subtype. See {@link R.xml.method}.
     82     private static final int SUBTYPE_ID_OF_DUMMY_EMOJI_SUBTYPE = 0xd78b2ed0;
     83     private static final String EXTRA_VALUE_OF_DUMMY_EMOJI_SUBTYPE =
     84             "KeyboardLayoutSet=" + SubtypeLocaleUtils.EMOJI
     85             + "," + Constants.Subtype.ExtraValue.EMOJI_CAPABLE;
     86     private static final InputMethodSubtype DUMMY_EMOJI_SUBTYPE =
     87             InputMethodSubtypeCompatUtils.newInputMethodSubtype(
     88                     R.string.subtype_emoji, R.drawable.ic_ime_switcher_dark,
     89                     SubtypeLocaleUtils.NO_LANGUAGE, KEYBOARD_MODE,
     90                     EXTRA_VALUE_OF_DUMMY_EMOJI_SUBTYPE,
     91                     false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */,
     92                     SUBTYPE_ID_OF_DUMMY_EMOJI_SUBTYPE);
     93 
     94     public static SubtypeSwitcher getInstance() {
     95         return sInstance;
     96     }
     97 
     98     public static void init(final Context context) {
     99         SubtypeLocaleUtils.init(context);
    100         RichInputMethodManager.init(context);
    101         sInstance.initialize(context);
    102     }
    103 
    104     private SubtypeSwitcher() {
    105         // Intentional empty constructor for singleton.
    106     }
    107 
    108     private void initialize(final Context context) {
    109         if (mResources != null) {
    110             return;
    111         }
    112         mResources = context.getResources();
    113         mRichImm = RichInputMethodManager.getInstance();
    114         ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(
    115                 Context.CONNECTIVITY_SERVICE);
    116 
    117         final NetworkInfo info = connectivityManager.getActiveNetworkInfo();
    118         mIsNetworkConnected = (info != null && info.isConnected());
    119 
    120         onSubtypeChanged(getCurrentSubtype());
    121         updateParametersOnStartInputView();
    122     }
    123 
    124     /**
    125      * Update parameters which are changed outside LatinIME. This parameters affect UI so that they
    126      * should be updated every time onStartInputView is called.
    127      */
    128     public void updateParametersOnStartInputView() {
    129         final List<InputMethodSubtype> enabledSubtypesOfThisIme =
    130                 mRichImm.getMyEnabledInputMethodSubtypeList(true);
    131         mLanguageOnSpacebarHelper.updateEnabledSubtypes(enabledSubtypesOfThisIme);
    132         updateShortcutIME();
    133     }
    134 
    135     private void updateShortcutIME() {
    136         if (DBG) {
    137             Log.d(TAG, "Update shortcut IME from : "
    138                     + (mShortcutInputMethodInfo == null
    139                             ? "<null>" : mShortcutInputMethodInfo.getId()) + ", "
    140                     + (mShortcutSubtype == null ? "<null>" : (
    141                             mShortcutSubtype.getLocale() + ", " + mShortcutSubtype.getMode())));
    142         }
    143         // TODO: Update an icon for shortcut IME
    144         final Map<InputMethodInfo, List<InputMethodSubtype>> shortcuts =
    145                 mRichImm.getInputMethodManager().getShortcutInputMethodsAndSubtypes();
    146         mShortcutInputMethodInfo = null;
    147         mShortcutSubtype = null;
    148         for (final InputMethodInfo imi : shortcuts.keySet()) {
    149             final List<InputMethodSubtype> subtypes = shortcuts.get(imi);
    150             // TODO: Returns the first found IMI for now. Should handle all shortcuts as
    151             // appropriate.
    152             mShortcutInputMethodInfo = imi;
    153             // TODO: Pick up the first found subtype for now. Should handle all subtypes
    154             // as appropriate.
    155             mShortcutSubtype = subtypes.size() > 0 ? subtypes.get(0) : null;
    156             break;
    157         }
    158         if (DBG) {
    159             Log.d(TAG, "Update shortcut IME to : "
    160                     + (mShortcutInputMethodInfo == null
    161                             ? "<null>" : mShortcutInputMethodInfo.getId()) + ", "
    162                     + (mShortcutSubtype == null ? "<null>" : (
    163                             mShortcutSubtype.getLocale() + ", " + mShortcutSubtype.getMode())));
    164         }
    165     }
    166 
    167     // Update the current subtype. LatinIME.onCurrentInputMethodSubtypeChanged calls this function.
    168     public void onSubtypeChanged(final InputMethodSubtype newSubtype) {
    169         if (DBG) {
    170             Log.w(TAG, "onSubtypeChanged: "
    171                     + SubtypeLocaleUtils.getSubtypeNameForLogging(newSubtype));
    172         }
    173 
    174         final Locale newLocale = SubtypeLocaleUtils.getSubtypeLocale(newSubtype);
    175         final Locale systemLocale = mResources.getConfiguration().locale;
    176         final boolean sameLocale = systemLocale.equals(newLocale);
    177         final boolean sameLanguage = systemLocale.getLanguage().equals(newLocale.getLanguage());
    178         final boolean implicitlyEnabled =
    179                 mRichImm.checkIfSubtypeBelongsToThisImeAndImplicitlyEnabled(newSubtype);
    180         mLanguageOnSpacebarHelper.updateIsSystemLanguageSameAsInputLanguage(
    181                 sameLocale || (sameLanguage && implicitlyEnabled));
    182 
    183         updateShortcutIME();
    184     }
    185 
    186     ////////////////////////////
    187     // Shortcut IME functions //
    188     ////////////////////////////
    189 
    190     public void switchToShortcutIME(final InputMethodService context) {
    191         if (mShortcutInputMethodInfo == null) {
    192             return;
    193         }
    194 
    195         final String imiId = mShortcutInputMethodInfo.getId();
    196         switchToTargetIME(imiId, mShortcutSubtype, context);
    197     }
    198 
    199     private void switchToTargetIME(final String imiId, final InputMethodSubtype subtype,
    200             final InputMethodService context) {
    201         final IBinder token = context.getWindow().getWindow().getAttributes().token;
    202         if (token == null) {
    203             return;
    204         }
    205         final InputMethodManager imm = mRichImm.getInputMethodManager();
    206         new AsyncTask<Void, Void, Void>() {
    207             @Override
    208             protected Void doInBackground(Void... params) {
    209                 imm.setInputMethodAndSubtype(token, imiId, subtype);
    210                 return null;
    211             }
    212         }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
    213     }
    214 
    215     public boolean isShortcutImeEnabled() {
    216         updateShortcutIME();
    217         if (mShortcutInputMethodInfo == null) {
    218             return false;
    219         }
    220         if (mShortcutSubtype == null) {
    221             return true;
    222         }
    223         return mRichImm.checkIfSubtypeBelongsToImeAndEnabled(
    224                 mShortcutInputMethodInfo, mShortcutSubtype);
    225     }
    226 
    227     public boolean isShortcutImeReady() {
    228         updateShortcutIME();
    229         if (mShortcutInputMethodInfo == null) {
    230             return false;
    231         }
    232         if (mShortcutSubtype == null) {
    233             return true;
    234         }
    235         if (mShortcutSubtype.containsExtraValueKey(REQ_NETWORK_CONNECTIVITY)) {
    236             return mIsNetworkConnected;
    237         }
    238         return true;
    239     }
    240 
    241     public void onNetworkStateChanged(final Intent intent) {
    242         final boolean noConnection = intent.getBooleanExtra(
    243                 ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
    244         mIsNetworkConnected = !noConnection;
    245 
    246         KeyboardSwitcher.getInstance().onNetworkStateChanged();
    247     }
    248 
    249     //////////////////////////////////
    250     // Subtype Switching functions //
    251     //////////////////////////////////
    252 
    253     public int getLanguageOnSpacebarFormatType(final InputMethodSubtype subtype) {
    254         return mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(subtype);
    255     }
    256 
    257     public boolean isSystemLocaleSameAsLocaleOfAllEnabledSubtypesOfEnabledImes() {
    258         final Locale systemLocale = mResources.getConfiguration().locale;
    259         final Set<InputMethodSubtype> enabledSubtypesOfEnabledImes = new HashSet<>();
    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     public String getCombiningRulesExtraValueOfCurrentSubtype() {
    331         return SubtypeLocaleUtils.getCombiningRulesExtraValue(getCurrentSubtype());
    332     }
    333 }
    334