• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.compat.InputMethodSubtypeCompatUtils;
36 import com.android.inputmethod.keyboard.KeyboardSwitcher;
37 import com.android.inputmethod.keyboard.internal.LanguageOnSpacebarHelper;
38 import com.android.inputmethod.latin.define.DebugFlags;
39 import com.android.inputmethod.latin.utils.LocaleUtils;
40 import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
41 
42 import java.util.HashSet;
43 import java.util.List;
44 import java.util.Locale;
45 import java.util.Map;
46 import java.util.Set;
47 
48 public final class SubtypeSwitcher {
49     private static boolean DBG = DebugFlags.DEBUG_ENABLED;
50     private static final String TAG = SubtypeSwitcher.class.getSimpleName();
51 
52     private static final SubtypeSwitcher sInstance = new SubtypeSwitcher();
53 
54     private /* final */ RichInputMethodManager mRichImm;
55     private /* final */ Resources mResources;
56 
57     private final LanguageOnSpacebarHelper mLanguageOnSpacebarHelper =
58             new LanguageOnSpacebarHelper();
59     private InputMethodInfo mShortcutInputMethodInfo;
60     private InputMethodSubtype mShortcutSubtype;
61     private InputMethodSubtype mNoLanguageSubtype;
62     private InputMethodSubtype mEmojiSubtype;
63     private boolean mIsNetworkConnected;
64 
65     private static final String KEYBOARD_MODE = "keyboard";
66     // Dummy no language QWERTY subtype. See {@link R.xml.method}.
67     private static final int SUBTYPE_ID_OF_DUMMY_NO_LANGUAGE_SUBTYPE = 0xdde0bfd3;
68     private static final String EXTRA_VALUE_OF_DUMMY_NO_LANGUAGE_SUBTYPE =
69             "KeyboardLayoutSet=" + SubtypeLocaleUtils.QWERTY
70             + "," + Constants.Subtype.ExtraValue.ASCII_CAPABLE
71             + "," + Constants.Subtype.ExtraValue.ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE
72             + "," + Constants.Subtype.ExtraValue.EMOJI_CAPABLE;
73     private static final InputMethodSubtype DUMMY_NO_LANGUAGE_SUBTYPE =
74             InputMethodSubtypeCompatUtils.newInputMethodSubtype(
75                     R.string.subtype_no_language_qwerty, R.drawable.ic_ime_switcher_dark,
76                     SubtypeLocaleUtils.NO_LANGUAGE, KEYBOARD_MODE,
77                     EXTRA_VALUE_OF_DUMMY_NO_LANGUAGE_SUBTYPE,
78                     false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */,
79                     SUBTYPE_ID_OF_DUMMY_NO_LANGUAGE_SUBTYPE);
80     // Caveat: We probably should remove this when we add an Emoji subtype in {@link R.xml.method}.
81     // Dummy Emoji subtype. See {@link R.xml.method}.
82     private static final int SUBTYPE_ID_OF_DUMMY_EMOJI_SUBTYPE = 0xd78b2ed0;
83     private static final String EXTRA_VALUE_OF_DUMMY_EMOJI_SUBTYPE =
84             "KeyboardLayoutSet=" + SubtypeLocaleUtils.EMOJI
85             + "," + Constants.Subtype.ExtraValue.EMOJI_CAPABLE;
86     private static final InputMethodSubtype DUMMY_EMOJI_SUBTYPE =
87             InputMethodSubtypeCompatUtils.newInputMethodSubtype(
88                     R.string.subtype_emoji, R.drawable.ic_ime_switcher_dark,
89                     SubtypeLocaleUtils.NO_LANGUAGE, KEYBOARD_MODE,
90                     EXTRA_VALUE_OF_DUMMY_EMOJI_SUBTYPE,
91                     false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */,
92                     SUBTYPE_ID_OF_DUMMY_EMOJI_SUBTYPE);
93 
getInstance()94     public static SubtypeSwitcher getInstance() {
95         return sInstance;
96     }
97 
init(final Context context)98     public static void init(final Context context) {
99         SubtypeLocaleUtils.init(context);
100         RichInputMethodManager.init(context);
101         sInstance.initialize(context);
102     }
103 
SubtypeSwitcher()104     private SubtypeSwitcher() {
105         // Intentional empty constructor for singleton.
106     }
107 
initialize(final Context context)108     private void initialize(final Context context) {
109         if (mResources != null) {
110             return;
111         }
112         mResources = context.getResources();
113         mRichImm = RichInputMethodManager.getInstance();
114         ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(
115                 Context.CONNECTIVITY_SERVICE);
116 
117         final NetworkInfo info = connectivityManager.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      */
updateParametersOnStartInputView()128     public void updateParametersOnStartInputView() {
129         final List<InputMethodSubtype> enabledSubtypesOfThisIme =
130                 mRichImm.getMyEnabledInputMethodSubtypeList(true);
131         mLanguageOnSpacebarHelper.updateEnabledSubtypes(enabledSubtypesOfThisIme);
132         updateShortcutIME();
133     }
134 
updateShortcutIME()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.
onSubtypeChanged(final InputMethodSubtype newSubtype)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         mLanguageOnSpacebarHelper.updateIsSystemLanguageSameAsInputLanguage(
181                 sameLocale || (sameLanguage && implicitlyEnabled));
182 
183         updateShortcutIME();
184     }
185 
186     ////////////////////////////
187     // Shortcut IME functions //
188     ////////////////////////////
189 
switchToShortcutIME(final InputMethodService context)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 
switchToTargetIME(final String imiId, final InputMethodSubtype subtype, final InputMethodService context)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 
isShortcutImeEnabled()215     public boolean isShortcutImeEnabled() {
216         updateShortcutIME();
217         if (mShortcutInputMethodInfo == null) {
218             return false;
219         }
220         if (mShortcutSubtype == null) {
221             return true;
222         }
223         return mRichImm.checkIfSubtypeBelongsToImeAndEnabled(
224                 mShortcutInputMethodInfo, mShortcutSubtype);
225     }
226 
isShortcutImeReady()227     public boolean isShortcutImeReady() {
228         updateShortcutIME();
229         if (mShortcutInputMethodInfo == null) {
230             return false;
231         }
232         if (mShortcutSubtype == null) {
233             return true;
234         }
235         if (mShortcutSubtype.containsExtraValueKey(REQ_NETWORK_CONNECTIVITY)) {
236             return mIsNetworkConnected;
237         }
238         return true;
239     }
240 
onNetworkStateChanged(final Intent intent)241     public void onNetworkStateChanged(final Intent intent) {
242         final boolean noConnection = intent.getBooleanExtra(
243                 ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
244         mIsNetworkConnected = !noConnection;
245 
246         KeyboardSwitcher.getInstance().onNetworkStateChanged();
247     }
248 
249     //////////////////////////////////
250     // Subtype Switching functions //
251     //////////////////////////////////
252 
getLanguageOnSpacebarFormatType(final InputMethodSubtype subtype)253     public int getLanguageOnSpacebarFormatType(final InputMethodSubtype subtype) {
254         return mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(subtype);
255     }
256 
isSystemLocaleSameAsLocaleOfAllEnabledSubtypesOfEnabledImes()257     public boolean isSystemLocaleSameAsLocaleOfAllEnabledSubtypesOfEnabledImes() {
258         final Locale systemLocale = mResources.getConfiguration().locale;
259         final Set<InputMethodSubtype> enabledSubtypesOfEnabledImes = new HashSet<>();
260         final InputMethodManager inputMethodManager = mRichImm.getInputMethodManager();
261         final List<InputMethodInfo> enabledInputMethodInfoList =
262                 inputMethodManager.getEnabledInputMethodList();
263         for (final InputMethodInfo info : enabledInputMethodInfoList) {
264             final List<InputMethodSubtype> enabledSubtypes =
265                     inputMethodManager.getEnabledInputMethodSubtypeList(
266                             info, true /* allowsImplicitlySelectedSubtypes */);
267             if (enabledSubtypes.isEmpty()) {
268                 // An IME with no subtypes is found.
269                 return false;
270             }
271             enabledSubtypesOfEnabledImes.addAll(enabledSubtypes);
272         }
273         for (final InputMethodSubtype subtype : enabledSubtypesOfEnabledImes) {
274             if (!subtype.isAuxiliary() && !subtype.getLocale().isEmpty()
275                     && !systemLocale.equals(SubtypeLocaleUtils.getSubtypeLocale(subtype))) {
276                 return false;
277             }
278         }
279         return true;
280     }
281 
282     private static InputMethodSubtype sForcedSubtypeForTesting = null;
283     @UsedForTesting
forceSubtype(final InputMethodSubtype subtype)284     void forceSubtype(final InputMethodSubtype subtype) {
285         sForcedSubtypeForTesting = subtype;
286     }
287 
getCurrentSubtypeLocale()288     public Locale getCurrentSubtypeLocale() {
289         if (null != sForcedSubtypeForTesting) {
290             return LocaleUtils.constructLocaleFromString(sForcedSubtypeForTesting.getLocale());
291         }
292         return SubtypeLocaleUtils.getSubtypeLocale(getCurrentSubtype());
293     }
294 
getCurrentSubtype()295     public InputMethodSubtype getCurrentSubtype() {
296         if (null != sForcedSubtypeForTesting) {
297             return sForcedSubtypeForTesting;
298         }
299         return mRichImm.getCurrentInputMethodSubtype(getNoLanguageSubtype());
300     }
301 
getNoLanguageSubtype()302     public InputMethodSubtype getNoLanguageSubtype() {
303         if (mNoLanguageSubtype == null) {
304             mNoLanguageSubtype = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
305                     SubtypeLocaleUtils.NO_LANGUAGE, SubtypeLocaleUtils.QWERTY);
306         }
307         if (mNoLanguageSubtype != null) {
308             return mNoLanguageSubtype;
309         }
310         Log.w(TAG, "Can't find any language with QWERTY subtype");
311         Log.w(TAG, "No input method subtype found; returning dummy subtype: "
312                 + DUMMY_NO_LANGUAGE_SUBTYPE);
313         return DUMMY_NO_LANGUAGE_SUBTYPE;
314     }
315 
getEmojiSubtype()316     public InputMethodSubtype getEmojiSubtype() {
317         if (mEmojiSubtype == null) {
318             mEmojiSubtype = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
319                     SubtypeLocaleUtils.NO_LANGUAGE, SubtypeLocaleUtils.EMOJI);
320         }
321         if (mEmojiSubtype != null) {
322             return mEmojiSubtype;
323         }
324         Log.w(TAG, "Can't find emoji subtype");
325         Log.w(TAG, "No input method subtype found; returning dummy subtype: "
326                 + DUMMY_EMOJI_SUBTYPE);
327         return DUMMY_EMOJI_SUBTYPE;
328     }
329 
getCombiningRulesExtraValueOfCurrentSubtype()330     public String getCombiningRulesExtraValueOfCurrentSubtype() {
331         return SubtypeLocaleUtils.getCombiningRulesExtraValue(getCurrentSubtype());
332     }
333 }
334