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