• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2012 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.KEYBOARD_MODE;
20 
21 import android.content.Context;
22 import android.content.SharedPreferences;
23 import android.os.IBinder;
24 import android.preference.PreferenceManager;
25 import android.util.Log;
26 import android.view.inputmethod.InputMethodInfo;
27 import android.view.inputmethod.InputMethodManager;
28 import android.view.inputmethod.InputMethodSubtype;
29 
30 import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
31 
32 import java.util.Collections;
33 import java.util.List;
34 
35 /**
36  * Enrichment class for InputMethodManager to simplify interaction and add functionality.
37  */
38 public final class RichInputMethodManager {
39     private static final String TAG = RichInputMethodManager.class.getSimpleName();
40 
RichInputMethodManager()41     private RichInputMethodManager() {
42         // This utility class is not publicly instantiable.
43     }
44 
45     private static final RichInputMethodManager sInstance = new RichInputMethodManager();
46 
47     private InputMethodManagerCompatWrapper mImmWrapper;
48     private InputMethodInfo mInputMethodInfoOfThisIme;
49 
50     private static final int INDEX_NOT_FOUND = -1;
51 
getInstance()52     public static RichInputMethodManager getInstance() {
53         sInstance.checkInitialized();
54         return sInstance;
55     }
56 
57     // Caveat: This may cause IPC
isInputMethodManagerValidForUserOfThisProcess(final Context context)58     public static boolean isInputMethodManagerValidForUserOfThisProcess(final Context context) {
59         // Basically called to check whether this IME has been triggered by the current user or not
60         return !((InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE)).
61                 getInputMethodList().isEmpty();
62     }
63 
init(final Context context)64     public static void init(final Context context) {
65         final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
66         sInstance.initInternal(context, prefs);
67     }
68 
isInitialized()69     private boolean isInitialized() {
70         return mImmWrapper != null;
71     }
72 
checkInitialized()73     private void checkInitialized() {
74         if (!isInitialized()) {
75             throw new RuntimeException(TAG + " is used before initialization");
76         }
77     }
78 
initInternal(final Context context, final SharedPreferences prefs)79     private void initInternal(final Context context, final SharedPreferences prefs) {
80         if (isInitialized()) {
81             return;
82         }
83         mImmWrapper = new InputMethodManagerCompatWrapper(context);
84         mInputMethodInfoOfThisIme = getInputMethodInfoOfThisIme(context);
85 
86         // Initialize additional subtypes.
87         SubtypeLocale.init(context);
88         final String prefAdditionalSubtypes = Settings.readPrefAdditionalSubtypes(
89                 prefs, context.getResources());
90         final InputMethodSubtype[] additionalSubtypes =
91                 AdditionalSubtype.createAdditionalSubtypesArray(prefAdditionalSubtypes);
92         setAdditionalInputMethodSubtypes(additionalSubtypes);
93     }
94 
getInputMethodManager()95     public InputMethodManager getInputMethodManager() {
96         checkInitialized();
97         return mImmWrapper.mImm;
98     }
99 
getInputMethodInfoOfThisIme(final Context context)100     private InputMethodInfo getInputMethodInfoOfThisIme(final Context context) {
101         final String packageName = context.getPackageName();
102         for (final InputMethodInfo imi : mImmWrapper.mImm.getInputMethodList()) {
103             if (imi.getPackageName().equals(packageName)) {
104                 return imi;
105             }
106         }
107         throw new RuntimeException("Input method id for " + packageName + " not found.");
108     }
109 
getMyEnabledInputMethodSubtypeList( boolean allowsImplicitlySelectedSubtypes)110     public List<InputMethodSubtype> getMyEnabledInputMethodSubtypeList(
111             boolean allowsImplicitlySelectedSubtypes) {
112         return mImmWrapper.mImm.getEnabledInputMethodSubtypeList(
113                 mInputMethodInfoOfThisIme, allowsImplicitlySelectedSubtypes);
114     }
115 
switchToNextInputMethod(final IBinder token, final boolean onlyCurrentIme)116     public boolean switchToNextInputMethod(final IBinder token, final boolean onlyCurrentIme) {
117         if (mImmWrapper.switchToNextInputMethod(token, onlyCurrentIme)) {
118             return true;
119         }
120         // Was not able to call {@link InputMethodManager#switchToNextInputMethodIBinder,boolean)}
121         // because the current device is running ICS or previous and lacks the API.
122         if (switchToNextInputSubtypeInThisIme(token, onlyCurrentIme)) {
123             return true;
124         }
125         return switchToNextInputMethodAndSubtype(token);
126     }
127 
switchToNextInputSubtypeInThisIme(final IBinder token, final boolean onlyCurrentIme)128     private boolean switchToNextInputSubtypeInThisIme(final IBinder token,
129             final boolean onlyCurrentIme) {
130         final InputMethodManager imm = mImmWrapper.mImm;
131         final InputMethodSubtype currentSubtype = imm.getCurrentInputMethodSubtype();
132         final List<InputMethodSubtype> enabledSubtypes = getMyEnabledInputMethodSubtypeList(
133                 true /* allowsImplicitlySelectedSubtypes */);
134         final int currentIndex = getSubtypeIndexInList(currentSubtype, enabledSubtypes);
135         if (currentIndex == INDEX_NOT_FOUND) {
136             Log.w(TAG, "Can't find current subtype in enabled subtypes: subtype="
137                     + SubtypeLocale.getSubtypeDisplayName(currentSubtype));
138             return false;
139         }
140         final int nextIndex = (currentIndex + 1) % enabledSubtypes.size();
141         if (nextIndex <= currentIndex && !onlyCurrentIme) {
142             // The current subtype is the last or only enabled one and it needs to switch to
143             // next IME.
144             return false;
145         }
146         final InputMethodSubtype nextSubtype = enabledSubtypes.get(nextIndex);
147         setInputMethodAndSubtype(token, nextSubtype);
148         return true;
149     }
150 
switchToNextInputMethodAndSubtype(final IBinder token)151     private boolean switchToNextInputMethodAndSubtype(final IBinder token) {
152         final InputMethodManager imm = mImmWrapper.mImm;
153         final List<InputMethodInfo> enabledImis = imm.getEnabledInputMethodList();
154         final int currentIndex = getImiIndexInList(mInputMethodInfoOfThisIme, enabledImis);
155         if (currentIndex == INDEX_NOT_FOUND) {
156             Log.w(TAG, "Can't find current IME in enabled IMEs: IME package="
157                     + mInputMethodInfoOfThisIme.getPackageName());
158             return false;
159         }
160         final InputMethodInfo nextImi = getNextNonAuxiliaryIme(currentIndex, enabledImis);
161         final List<InputMethodSubtype> enabledSubtypes = imm.getEnabledInputMethodSubtypeList(
162                 nextImi, true /* allowsImplicitlySelectedSubtypes */);
163         if (enabledSubtypes.isEmpty()) {
164             // The next IME has no subtype.
165             imm.setInputMethod(token, nextImi.getId());
166             return true;
167         }
168         final InputMethodSubtype firstSubtype = enabledSubtypes.get(0);
169         imm.setInputMethodAndSubtype(token, nextImi.getId(), firstSubtype);
170         return true;
171     }
172 
getImiIndexInList(final InputMethodInfo inputMethodInfo, final List<InputMethodInfo> imiList)173     private static int getImiIndexInList(final InputMethodInfo inputMethodInfo,
174             final List<InputMethodInfo> imiList) {
175         final int count = imiList.size();
176         for (int index = 0; index < count; index++) {
177             final InputMethodInfo imi = imiList.get(index);
178             if (imi.equals(inputMethodInfo)) {
179                 return index;
180             }
181         }
182         return INDEX_NOT_FOUND;
183     }
184 
185     // This method mimics {@link InputMethodManager#switchToNextInputMethod(IBinder,boolean)}.
getNextNonAuxiliaryIme(final int currentIndex, final List<InputMethodInfo> imiList)186     private static InputMethodInfo getNextNonAuxiliaryIme(final int currentIndex,
187             final List<InputMethodInfo> imiList) {
188         final int count = imiList.size();
189         for (int i = 1; i < count; i++) {
190             final int nextIndex = (currentIndex + i) % count;
191             final InputMethodInfo nextImi = imiList.get(nextIndex);
192             if (!isAuxiliaryIme(nextImi)) {
193                 return nextImi;
194             }
195         }
196         return imiList.get(currentIndex);
197     }
198 
199     // Copied from {@link InputMethodInfo}. See how auxiliary of IME is determined.
isAuxiliaryIme(final InputMethodInfo imi)200     private static boolean isAuxiliaryIme(final InputMethodInfo imi) {
201         final int count = imi.getSubtypeCount();
202         if (count == 0) {
203             return false;
204         }
205         for (int index = 0; index < count; index++) {
206             final InputMethodSubtype subtype = imi.getSubtypeAt(index);
207             if (!subtype.isAuxiliary()) {
208                 return false;
209             }
210         }
211         return true;
212     }
213 
getInputMethodInfoOfThisIme()214     public InputMethodInfo getInputMethodInfoOfThisIme() {
215         return mInputMethodInfoOfThisIme;
216     }
217 
getInputMethodIdOfThisIme()218     public String getInputMethodIdOfThisIme() {
219         return mInputMethodInfoOfThisIme.getId();
220     }
221 
checkIfSubtypeBelongsToThisImeAndEnabled(final InputMethodSubtype subtype)222     public boolean checkIfSubtypeBelongsToThisImeAndEnabled(final InputMethodSubtype subtype) {
223         return checkIfSubtypeBelongsToImeAndEnabled(mInputMethodInfoOfThisIme, subtype);
224     }
225 
checkIfSubtypeBelongsToThisImeAndImplicitlyEnabled( final InputMethodSubtype subtype)226     public boolean checkIfSubtypeBelongsToThisImeAndImplicitlyEnabled(
227             final InputMethodSubtype subtype) {
228         final boolean subtypeEnabled = checkIfSubtypeBelongsToThisImeAndEnabled(subtype);
229         final boolean subtypeExplicitlyEnabled = checkIfSubtypeBelongsToList(
230                 subtype, getMyEnabledInputMethodSubtypeList(
231                         false /* allowsImplicitlySelectedSubtypes */));
232         return subtypeEnabled && !subtypeExplicitlyEnabled;
233     }
234 
checkIfSubtypeBelongsToImeAndEnabled(final InputMethodInfo imi, final InputMethodSubtype subtype)235     public boolean checkIfSubtypeBelongsToImeAndEnabled(final InputMethodInfo imi,
236             final InputMethodSubtype subtype) {
237         return checkIfSubtypeBelongsToList(
238                 subtype, mImmWrapper.mImm.getEnabledInputMethodSubtypeList(
239                         imi, true /* allowsImplicitlySelectedSubtypes */));
240     }
241 
checkIfSubtypeBelongsToList(final InputMethodSubtype subtype, final List<InputMethodSubtype> subtypes)242     private static boolean checkIfSubtypeBelongsToList(final InputMethodSubtype subtype,
243             final List<InputMethodSubtype> subtypes) {
244         return getSubtypeIndexInList(subtype, subtypes) != INDEX_NOT_FOUND;
245     }
246 
getSubtypeIndexInList(final InputMethodSubtype subtype, final List<InputMethodSubtype> subtypes)247     private static int getSubtypeIndexInList(final InputMethodSubtype subtype,
248             final List<InputMethodSubtype> subtypes) {
249         final int count = subtypes.size();
250         for (int index = 0; index < count; index++) {
251             final InputMethodSubtype ims = subtypes.get(index);
252             if (ims.equals(subtype)) {
253                 return index;
254             }
255         }
256         return INDEX_NOT_FOUND;
257     }
258 
checkIfSubtypeBelongsToThisIme(final InputMethodSubtype subtype)259     public boolean checkIfSubtypeBelongsToThisIme(final InputMethodSubtype subtype) {
260         return getSubtypeIndexInIme(subtype, mInputMethodInfoOfThisIme) != INDEX_NOT_FOUND;
261     }
262 
getSubtypeIndexInIme(final InputMethodSubtype subtype, final InputMethodInfo imi)263     private static int getSubtypeIndexInIme(final InputMethodSubtype subtype,
264             final InputMethodInfo imi) {
265         final int count = imi.getSubtypeCount();
266         for (int index = 0; index < count; index++) {
267             final InputMethodSubtype ims = imi.getSubtypeAt(index);
268             if (ims.equals(subtype)) {
269                 return index;
270             }
271         }
272         return INDEX_NOT_FOUND;
273     }
274 
getCurrentInputMethodSubtype( final InputMethodSubtype defaultSubtype)275     public InputMethodSubtype getCurrentInputMethodSubtype(
276             final InputMethodSubtype defaultSubtype) {
277         final InputMethodSubtype currentSubtype = mImmWrapper.mImm.getCurrentInputMethodSubtype();
278         return (currentSubtype != null) ? currentSubtype : defaultSubtype;
279     }
280 
hasMultipleEnabledIMEsOrSubtypes(final boolean shouldIncludeAuxiliarySubtypes)281     public boolean hasMultipleEnabledIMEsOrSubtypes(final boolean shouldIncludeAuxiliarySubtypes) {
282         final List<InputMethodInfo> enabledImis = mImmWrapper.mImm.getEnabledInputMethodList();
283         return hasMultipleEnabledSubtypes(shouldIncludeAuxiliarySubtypes, enabledImis);
284     }
285 
hasMultipleEnabledSubtypesInThisIme( final boolean shouldIncludeAuxiliarySubtypes)286     public boolean hasMultipleEnabledSubtypesInThisIme(
287             final boolean shouldIncludeAuxiliarySubtypes) {
288         final List<InputMethodInfo> imiList = Collections.singletonList(mInputMethodInfoOfThisIme);
289         return hasMultipleEnabledSubtypes(shouldIncludeAuxiliarySubtypes, imiList);
290     }
291 
hasMultipleEnabledSubtypes(final boolean shouldIncludeAuxiliarySubtypes, final List<InputMethodInfo> imiList)292     private boolean hasMultipleEnabledSubtypes(final boolean shouldIncludeAuxiliarySubtypes,
293             final List<InputMethodInfo> imiList) {
294         // Number of the filtered IMEs
295         int filteredImisCount = 0;
296 
297         for (InputMethodInfo imi : imiList) {
298             // We can return true immediately after we find two or more filtered IMEs.
299             if (filteredImisCount > 1) return true;
300             final List<InputMethodSubtype> subtypes =
301                     mImmWrapper.mImm.getEnabledInputMethodSubtypeList(imi, true);
302             // IMEs that have no subtypes should be counted.
303             if (subtypes.isEmpty()) {
304                 ++filteredImisCount;
305                 continue;
306             }
307 
308             int auxCount = 0;
309             for (InputMethodSubtype subtype : subtypes) {
310                 if (subtype.isAuxiliary()) {
311                     ++auxCount;
312                 }
313             }
314             final int nonAuxCount = subtypes.size() - auxCount;
315 
316             // IMEs that have one or more non-auxiliary subtypes should be counted.
317             // If shouldIncludeAuxiliarySubtypes is true, IMEs that have two or more auxiliary
318             // subtypes should be counted as well.
319             if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) {
320                 ++filteredImisCount;
321                 continue;
322             }
323         }
324 
325         if (filteredImisCount > 1) {
326             return true;
327         }
328         final List<InputMethodSubtype> subtypes = getMyEnabledInputMethodSubtypeList(true);
329         int keyboardCount = 0;
330         // imm.getEnabledInputMethodSubtypeList(null, true) will return the current IME's
331         // both explicitly and implicitly enabled input method subtype.
332         // (The current IME should be LatinIME.)
333         for (InputMethodSubtype subtype : subtypes) {
334             if (KEYBOARD_MODE.equals(subtype.getMode())) {
335                 ++keyboardCount;
336             }
337         }
338         return keyboardCount > 1;
339     }
340 
findSubtypeByLocaleAndKeyboardLayoutSet(final String localeString, final String keyboardLayoutSetName)341     public InputMethodSubtype findSubtypeByLocaleAndKeyboardLayoutSet(final String localeString,
342             final String keyboardLayoutSetName) {
343         final InputMethodInfo myImi = mInputMethodInfoOfThisIme;
344         final int count = myImi.getSubtypeCount();
345         for (int i = 0; i < count; i++) {
346             final InputMethodSubtype subtype = myImi.getSubtypeAt(i);
347             final String layoutName = SubtypeLocale.getKeyboardLayoutSetName(subtype);
348             if (localeString.equals(subtype.getLocale())
349                     && keyboardLayoutSetName.equals(layoutName)) {
350                 return subtype;
351             }
352         }
353         return null;
354     }
355 
setInputMethodAndSubtype(final IBinder token, final InputMethodSubtype subtype)356     public void setInputMethodAndSubtype(final IBinder token, final InputMethodSubtype subtype) {
357         mImmWrapper.mImm.setInputMethodAndSubtype(
358                 token, mInputMethodInfoOfThisIme.getId(), subtype);
359     }
360 
setAdditionalInputMethodSubtypes(final InputMethodSubtype[] subtypes)361     public void setAdditionalInputMethodSubtypes(final InputMethodSubtype[] subtypes) {
362         mImmWrapper.mImm.setAdditionalInputMethodSubtypes(
363                 mInputMethodInfoOfThisIme.getId(), subtypes);
364     }
365 }
366