Home | History | Annotate | Download | only in inputmethod
      1 /*
      2  * Copyright (C) 2013 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.settings.inputmethod;
     18 
     19 import android.app.ActivityManagerNative;
     20 import android.content.Context;
     21 import android.os.RemoteException;
     22 import android.util.Log;
     23 import android.util.Slog;
     24 import android.view.inputmethod.InputMethodInfo;
     25 import android.view.inputmethod.InputMethodManager;
     26 import android.view.inputmethod.InputMethodSubtype;
     27 
     28 import com.android.internal.inputmethod.InputMethodUtils;
     29 import com.android.internal.inputmethod.InputMethodUtils.InputMethodSettings;
     30 
     31 import java.util.ArrayList;
     32 import java.util.HashMap;
     33 import java.util.HashSet;
     34 import java.util.List;
     35 import java.util.Locale;
     36 
     37 /**
     38  * This class is a wrapper for InputMethodSettings. You need to refresh internal states
     39  * manually on some events when "InputMethodInfo"s and "InputMethodSubtype"s can be
     40  * changed.
     41  */
     42 // TODO: Consolidate this with {@link InputMethodAndSubtypeUtil}.
     43 class InputMethodSettingValuesWrapper {
     44     private static final String TAG = InputMethodSettingValuesWrapper.class.getSimpleName();
     45 
     46     private static volatile InputMethodSettingValuesWrapper sInstance;
     47     private final ArrayList<InputMethodInfo> mMethodList = new ArrayList<>();
     48     private final HashMap<String, InputMethodInfo> mMethodMap = new HashMap<>();
     49     private final InputMethodSettings mSettings;
     50     private final InputMethodManager mImm;
     51     private final HashSet<InputMethodInfo> mAsciiCapableEnabledImis = new HashSet<>();
     52 
     53     static InputMethodSettingValuesWrapper getInstance(Context context) {
     54         if (sInstance == null) {
     55             synchronized (TAG) {
     56                 if (sInstance == null) {
     57                     sInstance = new InputMethodSettingValuesWrapper(context);
     58                 }
     59             }
     60         }
     61         return sInstance;
     62     }
     63 
     64     private static int getDefaultCurrentUserId() {
     65         try {
     66             return ActivityManagerNative.getDefault().getCurrentUser().id;
     67         } catch (RemoteException e) {
     68             Slog.w(TAG, "Couldn't get current user ID; guessing it's 0", e);
     69         }
     70         return 0;
     71     }
     72 
     73     // Ensure singleton
     74     private InputMethodSettingValuesWrapper(Context context) {
     75         mSettings = new InputMethodSettings(context.getResources(), context.getContentResolver(),
     76                 mMethodMap, mMethodList, getDefaultCurrentUserId(), false /* copyOnWrite */);
     77         mImm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
     78         refreshAllInputMethodAndSubtypes();
     79     }
     80 
     81     void refreshAllInputMethodAndSubtypes() {
     82         synchronized (mMethodMap) {
     83             mMethodList.clear();
     84             mMethodMap.clear();
     85             final List<InputMethodInfo> imms = mImm.getInputMethodList();
     86             mMethodList.addAll(imms);
     87             for (InputMethodInfo imi : imms) {
     88                 mMethodMap.put(imi.getId(), imi);
     89             }
     90             updateAsciiCapableEnabledImis();
     91         }
     92     }
     93 
     94     // TODO: Add a cts to ensure at least one AsciiCapableSubtypeEnabledImis exist
     95     private void updateAsciiCapableEnabledImis() {
     96         synchronized (mMethodMap) {
     97             mAsciiCapableEnabledImis.clear();
     98             final List<InputMethodInfo> enabledImis = mSettings.getEnabledInputMethodListLocked();
     99             for (final InputMethodInfo imi : enabledImis) {
    100                 final int subtypeCount = imi.getSubtypeCount();
    101                 for (int i = 0; i < subtypeCount; ++i) {
    102                     final InputMethodSubtype subtype = imi.getSubtypeAt(i);
    103                     if (InputMethodUtils.SUBTYPE_MODE_KEYBOARD.equalsIgnoreCase(subtype.getMode())
    104                             && subtype.isAsciiCapable()) {
    105                         mAsciiCapableEnabledImis.add(imi);
    106                         break;
    107                     }
    108                 }
    109             }
    110         }
    111     }
    112 
    113     List<InputMethodInfo> getInputMethodList() {
    114         synchronized (mMethodMap) {
    115             return mMethodList;
    116         }
    117     }
    118 
    119     CharSequence getCurrentInputMethodName(Context context) {
    120         synchronized (mMethodMap) {
    121             final InputMethodInfo imi = mMethodMap.get(mSettings.getSelectedInputMethod());
    122             if (imi == null) {
    123                 Log.w(TAG, "Invalid selected imi: " + mSettings.getSelectedInputMethod());
    124                 return "";
    125             }
    126             final InputMethodSubtype subtype = mImm.getCurrentInputMethodSubtype();
    127             return InputMethodUtils.getImeAndSubtypeDisplayName(context, imi, subtype);
    128         }
    129     }
    130 
    131     boolean isAlwaysCheckedIme(InputMethodInfo imi, Context context) {
    132         final boolean isEnabled = isEnabledImi(imi);
    133         synchronized (mMethodMap) {
    134             if (mSettings.getEnabledInputMethodListLocked().size() <= 1 && isEnabled) {
    135                 return true;
    136             }
    137         }
    138 
    139         final int enabledValidSystemNonAuxAsciiCapableImeCount =
    140                 getEnabledValidSystemNonAuxAsciiCapableImeCount(context);
    141         if (enabledValidSystemNonAuxAsciiCapableImeCount > 1) {
    142             return false;
    143         }
    144 
    145         if (enabledValidSystemNonAuxAsciiCapableImeCount == 1 && !isEnabled) {
    146             return false;
    147         }
    148 
    149         if (!InputMethodUtils.isSystemIme(imi)) {
    150             return false;
    151         }
    152         return isValidSystemNonAuxAsciiCapableIme(imi, context);
    153     }
    154 
    155     private int getEnabledValidSystemNonAuxAsciiCapableImeCount(Context context) {
    156         int count = 0;
    157         final List<InputMethodInfo> enabledImis;
    158         synchronized (mMethodMap) {
    159             enabledImis = mSettings.getEnabledInputMethodListLocked();
    160         }
    161         for (final InputMethodInfo imi : enabledImis) {
    162             if (isValidSystemNonAuxAsciiCapableIme(imi, context)) {
    163                 ++count;
    164             }
    165         }
    166         if (count == 0) {
    167             Log.w(TAG, "No \"enabledValidSystemNonAuxAsciiCapableIme\"s found.");
    168         }
    169         return count;
    170     }
    171 
    172     boolean isEnabledImi(InputMethodInfo imi) {
    173         final List<InputMethodInfo> enabledImis;
    174         synchronized (mMethodMap) {
    175             enabledImis = mSettings.getEnabledInputMethodListLocked();
    176         }
    177         for (final InputMethodInfo tempImi : enabledImis) {
    178             if (tempImi.getId().equals(imi.getId())) {
    179                 return true;
    180             }
    181         }
    182         return false;
    183     }
    184 
    185     boolean isValidSystemNonAuxAsciiCapableIme(InputMethodInfo imi, Context context) {
    186         if (imi.isAuxiliaryIme()) {
    187             return false;
    188         }
    189         final Locale systemLocale = context.getResources().getConfiguration().locale;
    190         if (InputMethodUtils.isSystemImeThatHasSubtypeOf(imi, context,
    191                     true /* checkDefaultAttribute */, systemLocale, false /* checkCountry */,
    192                     InputMethodUtils.SUBTYPE_MODE_ANY)) {
    193             return true;
    194         }
    195         if (mAsciiCapableEnabledImis.isEmpty()) {
    196             Log.w(TAG, "ascii capable subtype enabled imi not found. Fall back to English"
    197                     + " Keyboard subtype.");
    198             return InputMethodUtils.containsSubtypeOf(imi, Locale.ENGLISH, false /* checkCountry */,
    199                     InputMethodUtils.SUBTYPE_MODE_KEYBOARD);
    200         }
    201         return mAsciiCapableEnabledImis.contains(imi);
    202     }
    203 }
    204