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
17package com.android.settings.inputmethod;
18
19import android.app.ActivityManagerNative;
20import android.content.Context;
21import android.os.RemoteException;
22import android.util.Log;
23import android.util.Slog;
24import android.view.inputmethod.InputMethodInfo;
25import android.view.inputmethod.InputMethodManager;
26import android.view.inputmethod.InputMethodSubtype;
27
28import com.android.internal.inputmethod.InputMethodUtils;
29import com.android.internal.inputmethod.InputMethodUtils.InputMethodSettings;
30
31import java.util.ArrayList;
32import java.util.HashMap;
33import java.util.HashSet;
34import java.util.List;
35import 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}.
43class 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