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.keyboard.KeyboardSwitcher;
     36 import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
     37 
     38 import java.util.List;
     39 import java.util.Locale;
     40 import java.util.Map;
     41 
     42 public final class SubtypeSwitcher {
     43     private static boolean DBG = LatinImeLogger.sDBG;
     44     private static final String TAG = SubtypeSwitcher.class.getSimpleName();
     45 
     46     private static final SubtypeSwitcher sInstance = new SubtypeSwitcher();
     47 
     48     private /* final */ RichInputMethodManager mRichImm;
     49     private /* final */ Resources mResources;
     50     private /* final */ ConnectivityManager mConnectivityManager;
     51 
     52     private final NeedsToDisplayLanguage mNeedsToDisplayLanguage = new NeedsToDisplayLanguage();
     53     private InputMethodInfo mShortcutInputMethodInfo;
     54     private InputMethodSubtype mShortcutSubtype;
     55     private InputMethodSubtype mNoLanguageSubtype;
     56     private InputMethodSubtype mEmojiSubtype;
     57     private boolean mIsNetworkConnected;
     58 
     59     // Dummy no language QWERTY subtype. See {@link R.xml.method}.
     60     private static final InputMethodSubtype DUMMY_NO_LANGUAGE_SUBTYPE = new InputMethodSubtype(
     61             R.string.subtype_no_language_qwerty, R.drawable.ic_ime_switcher_dark,
     62             SubtypeLocaleUtils.NO_LANGUAGE, "keyboard", "KeyboardLayoutSet="
     63                     + SubtypeLocaleUtils.QWERTY
     64                     + "," + Constants.Subtype.ExtraValue.ASCII_CAPABLE
     65                     + ",EnabledWhenDefaultIsNotAsciiCapable,"
     66                     + Constants.Subtype.ExtraValue.EMOJI_CAPABLE,
     67             false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */);
     68     // Caveat: We probably should remove this when we add an Emoji subtype in {@link R.xml.method}.
     69     // Dummy Emoji subtype. See {@link R.xml.method}.
     70     private static final InputMethodSubtype DUMMY_EMOJI_SUBTYPE = new InputMethodSubtype(
     71             R.string.subtype_emoji, R.drawable.ic_ime_switcher_dark,
     72             SubtypeLocaleUtils.NO_LANGUAGE, "keyboard", "KeyboardLayoutSet="
     73                     + SubtypeLocaleUtils.EMOJI + ","
     74                     + Constants.Subtype.ExtraValue.EMOJI_CAPABLE,
     75             false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */);
     76 
     77     static final class NeedsToDisplayLanguage {
     78         private int mEnabledSubtypeCount;
     79         private boolean mIsSystemLanguageSameAsInputLanguage;
     80 
     81         public boolean getValue() {
     82             return mEnabledSubtypeCount >= 2 || !mIsSystemLanguageSameAsInputLanguage;
     83         }
     84 
     85         public void updateEnabledSubtypeCount(final int count) {
     86             mEnabledSubtypeCount = count;
     87         }
     88 
     89         public void updateIsSystemLanguageSameAsInputLanguage(final boolean isSame) {
     90             mIsSystemLanguageSameAsInputLanguage = isSame;
     91         }
     92     }
     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         mConnectivityManager = (ConnectivityManager) context.getSystemService(
    115                 Context.CONNECTIVITY_SERVICE);
    116 
    117         final NetworkInfo info = mConnectivityManager.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         mNeedsToDisplayLanguage.updateEnabledSubtypeCount(enabledSubtypesOfThisIme.size());
    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         mNeedsToDisplayLanguage.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         if (mShortcutInputMethodInfo == null) {
    217             return false;
    218         }
    219         if (mShortcutSubtype == null) {
    220             return true;
    221         }
    222         return mRichImm.checkIfSubtypeBelongsToImeAndEnabled(
    223                 mShortcutInputMethodInfo, mShortcutSubtype);
    224     }
    225 
    226     public boolean isShortcutImeReady() {
    227         if (mShortcutInputMethodInfo == null)
    228             return false;
    229         if (mShortcutSubtype == null)
    230             return true;
    231         if (mShortcutSubtype.containsExtraValueKey(REQ_NETWORK_CONNECTIVITY)) {
    232             return mIsNetworkConnected;
    233         }
    234         return true;
    235     }
    236 
    237     public void onNetworkStateChanged(final Intent intent) {
    238         final boolean noConnection = intent.getBooleanExtra(
    239                 ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
    240         mIsNetworkConnected = !noConnection;
    241 
    242         KeyboardSwitcher.getInstance().onNetworkStateChanged();
    243     }
    244 
    245     //////////////////////////////////
    246     // Subtype Switching functions //
    247     //////////////////////////////////
    248 
    249     public boolean needsToDisplayLanguage(final Locale keyboardLocale) {
    250         if (keyboardLocale.toString().equals(SubtypeLocaleUtils.NO_LANGUAGE)) {
    251             return true;
    252         }
    253         if (!keyboardLocale.equals(getCurrentSubtypeLocale())) {
    254             return false;
    255         }
    256         return mNeedsToDisplayLanguage.getValue();
    257     }
    258 
    259     private static Locale sForcedLocaleForTesting = null;
    260     @UsedForTesting
    261     void forceLocale(final Locale locale) {
    262         sForcedLocaleForTesting = locale;
    263     }
    264 
    265     public Locale getCurrentSubtypeLocale() {
    266         if (null != sForcedLocaleForTesting) return sForcedLocaleForTesting;
    267         return SubtypeLocaleUtils.getSubtypeLocale(getCurrentSubtype());
    268     }
    269 
    270     public InputMethodSubtype getCurrentSubtype() {
    271         return mRichImm.getCurrentInputMethodSubtype(getNoLanguageSubtype());
    272     }
    273 
    274     public InputMethodSubtype getNoLanguageSubtype() {
    275         if (mNoLanguageSubtype == null) {
    276             mNoLanguageSubtype = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
    277                     SubtypeLocaleUtils.NO_LANGUAGE, SubtypeLocaleUtils.QWERTY);
    278         }
    279         if (mNoLanguageSubtype != null) {
    280             return mNoLanguageSubtype;
    281         }
    282         Log.w(TAG, "Can't find no lanugage with QWERTY subtype");
    283         Log.w(TAG, "No input method subtype found; return dummy subtype: "
    284                 + DUMMY_NO_LANGUAGE_SUBTYPE);
    285         return DUMMY_NO_LANGUAGE_SUBTYPE;
    286     }
    287 
    288     public InputMethodSubtype getEmojiSubtype() {
    289         if (mEmojiSubtype == null) {
    290             mEmojiSubtype = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
    291                     SubtypeLocaleUtils.NO_LANGUAGE, SubtypeLocaleUtils.EMOJI);
    292         }
    293         if (mEmojiSubtype != null) {
    294             return mEmojiSubtype;
    295         }
    296         Log.w(TAG, "Can't find Emoji subtype");
    297         Log.w(TAG, "No input method subtype found; return dummy subtype: " + DUMMY_EMOJI_SUBTYPE);
    298         return DUMMY_EMOJI_SUBTYPE;
    299     }
    300 }
    301