Home | History | Annotate | Download | only in inputmethod
      1 /*
      2  * Copyright (C) 2017 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.settingslib.inputmethod;
     18 
     19 import android.app.ActivityManager;
     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 public 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     public 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 ActivityManager.getService().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     public 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     public List<InputMethodInfo> getInputMethodList() {
    114         synchronized (mMethodMap) {
    115             return mMethodList;
    116         }
    117     }
    118 
    119     public boolean isAlwaysCheckedIme(InputMethodInfo imi, Context context) {
    120         final boolean isEnabled = isEnabledImi(imi);
    121         synchronized (mMethodMap) {
    122             if (mSettings.getEnabledInputMethodListLocked().size() <= 1 && isEnabled) {
    123                 return true;
    124             }
    125         }
    126 
    127         final int enabledValidSystemNonAuxAsciiCapableImeCount =
    128                 getEnabledValidSystemNonAuxAsciiCapableImeCount(context);
    129 
    130         return enabledValidSystemNonAuxAsciiCapableImeCount <= 1
    131                 && !(enabledValidSystemNonAuxAsciiCapableImeCount == 1 && !isEnabled)
    132                 && InputMethodUtils.isSystemIme(imi)
    133                 && isValidSystemNonAuxAsciiCapableIme(imi, context);
    134 
    135     }
    136 
    137     private int getEnabledValidSystemNonAuxAsciiCapableImeCount(Context context) {
    138         int count = 0;
    139         final List<InputMethodInfo> enabledImis;
    140         synchronized (mMethodMap) {
    141             enabledImis = mSettings.getEnabledInputMethodListLocked();
    142         }
    143         for (final InputMethodInfo imi : enabledImis) {
    144             if (isValidSystemNonAuxAsciiCapableIme(imi, context)) {
    145                 ++count;
    146             }
    147         }
    148         if (count == 0) {
    149             Log.w(TAG, "No \"enabledValidSystemNonAuxAsciiCapableIme\"s found.");
    150         }
    151         return count;
    152     }
    153 
    154     public boolean isEnabledImi(InputMethodInfo imi) {
    155         final List<InputMethodInfo> enabledImis;
    156         synchronized (mMethodMap) {
    157             enabledImis = mSettings.getEnabledInputMethodListLocked();
    158         }
    159         for (final InputMethodInfo tempImi : enabledImis) {
    160             if (tempImi.getId().equals(imi.getId())) {
    161                 return true;
    162             }
    163         }
    164         return false;
    165     }
    166 
    167     public boolean isValidSystemNonAuxAsciiCapableIme(InputMethodInfo imi, Context context) {
    168         if (imi.isAuxiliaryIme()) {
    169             return false;
    170         }
    171         final Locale systemLocale = context.getResources().getConfiguration().locale;
    172         if (InputMethodUtils.isSystemImeThatHasSubtypeOf(imi, context,
    173                     true /* checkDefaultAttribute */, systemLocale, false /* checkCountry */,
    174                     InputMethodUtils.SUBTYPE_MODE_ANY)) {
    175             return true;
    176         }
    177         if (mAsciiCapableEnabledImis.isEmpty()) {
    178             Log.w(TAG, "ascii capable subtype enabled imi not found. Fall back to English"
    179                     + " Keyboard subtype.");
    180             return InputMethodUtils.containsSubtypeOf(imi, Locale.ENGLISH, false /* checkCountry */,
    181                     InputMethodUtils.SUBTYPE_MODE_KEYBOARD);
    182         }
    183         return mAsciiCapableEnabledImis.contains(imi);
    184     }
    185 }
    186