• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 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.compat;
18 
19 import android.app.AlertDialog;
20 import android.content.Context;
21 import android.content.DialogInterface;
22 import android.content.DialogInterface.OnClickListener;
23 import android.content.Intent;
24 import android.content.pm.ApplicationInfo;
25 import android.content.pm.PackageManager;
26 import android.os.IBinder;
27 import android.text.TextUtils;
28 import android.util.Log;
29 import android.view.inputmethod.InputMethodInfo;
30 import android.view.inputmethod.InputMethodManager;
31 
32 import com.android.inputmethod.deprecated.LanguageSwitcherProxy;
33 import com.android.inputmethod.latin.R;
34 import com.android.inputmethod.latin.SubtypeSwitcher;
35 import com.android.inputmethod.latin.Utils;
36 
37 import java.lang.reflect.Method;
38 import java.util.ArrayList;
39 import java.util.Collections;
40 import java.util.Comparator;
41 import java.util.HashMap;
42 import java.util.List;
43 import java.util.Locale;
44 import java.util.Map;
45 
46 // TODO: Override this class with the concrete implementation if we need to take care of the
47 // performance.
48 public class InputMethodManagerCompatWrapper {
49     private static final String TAG = InputMethodManagerCompatWrapper.class.getSimpleName();
50     private static final Method METHOD_getCurrentInputMethodSubtype =
51             CompatUtils.getMethod(InputMethodManager.class, "getCurrentInputMethodSubtype");
52     private static final Method METHOD_getEnabledInputMethodSubtypeList =
53             CompatUtils.getMethod(InputMethodManager.class, "getEnabledInputMethodSubtypeList",
54                     InputMethodInfo.class, boolean.class);
55     private static final Method METHOD_getShortcutInputMethodsAndSubtypes =
56             CompatUtils.getMethod(InputMethodManager.class, "getShortcutInputMethodsAndSubtypes");
57     private static final Method METHOD_setInputMethodAndSubtype =
58             CompatUtils.getMethod(
59                     InputMethodManager.class, "setInputMethodAndSubtype", IBinder.class,
60                     String.class, InputMethodSubtypeCompatWrapper.CLASS_InputMethodSubtype);
61     private static final Method METHOD_switchToLastInputMethod = CompatUtils.getMethod(
62             InputMethodManager.class, "switchToLastInputMethod", IBinder.class);
63 
64     private static final InputMethodManagerCompatWrapper sInstance =
65             new InputMethodManagerCompatWrapper();
66 
67     public static final boolean SUBTYPE_SUPPORTED;
68 
69     static {
70         // This static initializer guarantees that METHOD_getShortcutInputMethodsAndSubtypes is
71         // already instantiated.
72         SUBTYPE_SUPPORTED = METHOD_getShortcutInputMethodsAndSubtypes != null;
73     }
74 
75     // For the compatibility, IMM will create dummy subtypes if subtypes are not found.
76     // This is required to be false if the current behavior is broken. For now, it's ok to be true.
77     public static final boolean FORCE_ENABLE_VOICE_EVEN_WITH_NO_VOICE_SUBTYPES =
78             !InputMethodServiceCompatWrapper.CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED;
79     private static final String VOICE_MODE = "voice";
80     private static final String KEYBOARD_MODE = "keyboard";
81 
82     private InputMethodServiceCompatWrapper mService;
83     private InputMethodManager mImm;
84     private PackageManager mPackageManager;
85     private ApplicationInfo mApplicationInfo;
86     private LanguageSwitcherProxy mLanguageSwitcherProxy;
87     private String mLatinImePackageName;
88 
getInstance()89     public static InputMethodManagerCompatWrapper getInstance() {
90         if (sInstance.mImm == null)
91             Log.w(TAG, "getInstance() is called before initialization");
92         return sInstance;
93     }
94 
init(InputMethodServiceCompatWrapper service)95     public static void init(InputMethodServiceCompatWrapper service) {
96         sInstance.mService = service;
97         sInstance.mImm = (InputMethodManager) service.getSystemService(
98                 Context.INPUT_METHOD_SERVICE);
99         sInstance.mLatinImePackageName = service.getPackageName();
100         sInstance.mPackageManager = service.getPackageManager();
101         sInstance.mApplicationInfo = service.getApplicationInfo();
102         sInstance.mLanguageSwitcherProxy = LanguageSwitcherProxy.getInstance();
103     }
104 
getCurrentInputMethodSubtype()105     public InputMethodSubtypeCompatWrapper getCurrentInputMethodSubtype() {
106         if (!SUBTYPE_SUPPORTED) {
107             return new InputMethodSubtypeCompatWrapper(
108                     0, 0, mLanguageSwitcherProxy.getInputLocale().toString(), KEYBOARD_MODE, "");
109         }
110         Object o = CompatUtils.invoke(mImm, null, METHOD_getCurrentInputMethodSubtype);
111         return new InputMethodSubtypeCompatWrapper(o);
112     }
113 
getEnabledInputMethodSubtypeList( InputMethodInfoCompatWrapper imi, boolean allowsImplicitlySelectedSubtypes)114     public List<InputMethodSubtypeCompatWrapper> getEnabledInputMethodSubtypeList(
115             InputMethodInfoCompatWrapper imi, boolean allowsImplicitlySelectedSubtypes) {
116         if (!SUBTYPE_SUPPORTED) {
117             String[] languages = mLanguageSwitcherProxy.getEnabledLanguages(
118                     allowsImplicitlySelectedSubtypes);
119             List<InputMethodSubtypeCompatWrapper> subtypeList =
120                     new ArrayList<InputMethodSubtypeCompatWrapper>();
121             for (String lang: languages) {
122                 subtypeList.add(new InputMethodSubtypeCompatWrapper(0, 0, lang, KEYBOARD_MODE, ""));
123             }
124             return subtypeList;
125         }
126         Object retval = CompatUtils.invoke(mImm, null, METHOD_getEnabledInputMethodSubtypeList,
127                 (imi != null ? imi.getInputMethodInfo() : null), allowsImplicitlySelectedSubtypes);
128         if (retval == null || !(retval instanceof List<?>) || ((List<?>)retval).isEmpty()) {
129             if (!FORCE_ENABLE_VOICE_EVEN_WITH_NO_VOICE_SUBTYPES) {
130                 // Returns an empty list
131                 return Collections.emptyList();
132             }
133             // Creates dummy subtypes
134             @SuppressWarnings("unused")
135             List<InputMethodSubtypeCompatWrapper> subtypeList =
136                     new ArrayList<InputMethodSubtypeCompatWrapper>();
137             InputMethodSubtypeCompatWrapper keyboardSubtype = getLastResortSubtype(KEYBOARD_MODE);
138             InputMethodSubtypeCompatWrapper voiceSubtype = getLastResortSubtype(VOICE_MODE);
139             if (keyboardSubtype != null) {
140                 subtypeList.add(keyboardSubtype);
141             }
142             if (voiceSubtype != null) {
143                 subtypeList.add(voiceSubtype);
144             }
145             return subtypeList;
146         }
147         return CompatUtils.copyInputMethodSubtypeListToWrapper(retval);
148     }
149 
getLatinImeInputMethodInfo()150     private InputMethodInfoCompatWrapper getLatinImeInputMethodInfo() {
151         if (TextUtils.isEmpty(mLatinImePackageName))
152             return null;
153         return Utils.getInputMethodInfo(this, mLatinImePackageName);
154     }
155 
156     @SuppressWarnings("unused")
getLastResortSubtype(String mode)157     private InputMethodSubtypeCompatWrapper getLastResortSubtype(String mode) {
158         if (VOICE_MODE.equals(mode) && !FORCE_ENABLE_VOICE_EVEN_WITH_NO_VOICE_SUBTYPES)
159             return null;
160         Locale inputLocale = SubtypeSwitcher.getInstance().getInputLocale();
161         if (inputLocale == null)
162             return null;
163         return new InputMethodSubtypeCompatWrapper(0, 0, inputLocale.toString(), mode, "");
164     }
165 
166     public Map<InputMethodInfoCompatWrapper, List<InputMethodSubtypeCompatWrapper>>
getShortcutInputMethodsAndSubtypes()167             getShortcutInputMethodsAndSubtypes() {
168         Object retval = CompatUtils.invoke(mImm, null, METHOD_getShortcutInputMethodsAndSubtypes);
169         if (retval == null || !(retval instanceof Map<?, ?>) || ((Map<?, ?>)retval).isEmpty()) {
170             if (!FORCE_ENABLE_VOICE_EVEN_WITH_NO_VOICE_SUBTYPES) {
171                 // Returns an empty map
172                 return Collections.emptyMap();
173             }
174             // Creates dummy subtypes
175             @SuppressWarnings("unused")
176             InputMethodInfoCompatWrapper imi = getLatinImeInputMethodInfo();
177             InputMethodSubtypeCompatWrapper voiceSubtype = getLastResortSubtype(VOICE_MODE);
178             if (imi != null && voiceSubtype != null) {
179                 Map<InputMethodInfoCompatWrapper, List<InputMethodSubtypeCompatWrapper>>
180                         shortcutMap =
181                                 new HashMap<InputMethodInfoCompatWrapper,
182                                         List<InputMethodSubtypeCompatWrapper>>();
183                 List<InputMethodSubtypeCompatWrapper> subtypeList =
184                         new ArrayList<InputMethodSubtypeCompatWrapper>();
185                 subtypeList.add(voiceSubtype);
186                 shortcutMap.put(imi, subtypeList);
187                 return shortcutMap;
188             } else {
189                 return Collections.emptyMap();
190             }
191         }
192         Map<InputMethodInfoCompatWrapper, List<InputMethodSubtypeCompatWrapper>> shortcutMap =
193                 new HashMap<InputMethodInfoCompatWrapper, List<InputMethodSubtypeCompatWrapper>>();
194         final Map<?, ?> retvalMap = (Map<?, ?>)retval;
195         for (Object key : retvalMap.keySet()) {
196             if (!(key instanceof InputMethodInfo)) {
197                 Log.e(TAG, "Class type error.");
198                 return null;
199             }
200             shortcutMap.put(new InputMethodInfoCompatWrapper((InputMethodInfo)key),
201                     CompatUtils.copyInputMethodSubtypeListToWrapper(retvalMap.get(key)));
202         }
203         return shortcutMap;
204     }
205 
206     // We don't call this method when we switch between subtypes within this IME.
setInputMethodAndSubtype( IBinder token, String id, InputMethodSubtypeCompatWrapper subtype)207     public void setInputMethodAndSubtype(
208             IBinder token, String id, InputMethodSubtypeCompatWrapper subtype) {
209         // TODO: Support subtype change on non-subtype-supported platform.
210         if (subtype != null && subtype.hasOriginalObject()) {
211             CompatUtils.invoke(mImm, null, METHOD_setInputMethodAndSubtype,
212                     token, id, subtype.getOriginalObject());
213         } else {
214             mImm.setInputMethod(token, id);
215         }
216     }
217 
switchToLastInputMethod(IBinder token)218     public boolean switchToLastInputMethod(IBinder token) {
219         if (SubtypeSwitcher.getInstance().isDummyVoiceMode()) {
220             return true;
221         }
222         return (Boolean)CompatUtils.invoke(mImm, false, METHOD_switchToLastInputMethod, token);
223     }
224 
getEnabledInputMethodList()225     public List<InputMethodInfoCompatWrapper> getEnabledInputMethodList() {
226         if (mImm == null) return null;
227         List<InputMethodInfoCompatWrapper> imis = new ArrayList<InputMethodInfoCompatWrapper>();
228         for (InputMethodInfo imi : mImm.getEnabledInputMethodList()) {
229             imis.add(new InputMethodInfoCompatWrapper(imi));
230         }
231         return imis;
232     }
233 
showInputMethodPicker()234     public void showInputMethodPicker() {
235         if (mImm == null) return;
236         if (SUBTYPE_SUPPORTED) {
237             mImm.showInputMethodPicker();
238             return;
239         }
240 
241         // The code below are based on {@link InputMethodManager#showInputMethodMenuInternal}.
242 
243         final InputMethodInfoCompatWrapper myImi = Utils.getInputMethodInfo(
244                 this, mLatinImePackageName);
245         final List<InputMethodSubtypeCompatWrapper> myImsList = getEnabledInputMethodSubtypeList(
246                 myImi, true);
247         final InputMethodSubtypeCompatWrapper currentIms = getCurrentInputMethodSubtype();
248         final List<InputMethodInfoCompatWrapper> imiList = getEnabledInputMethodList();
249         imiList.remove(myImi);
250         Collections.sort(imiList, new Comparator<InputMethodInfoCompatWrapper>() {
251             @Override
252             public int compare(InputMethodInfoCompatWrapper imi1,
253                     InputMethodInfoCompatWrapper imi2) {
254                 final CharSequence imiId1 = imi1.loadLabel(mPackageManager) + "/" + imi1.getId();
255                 final CharSequence imiId2 = imi2.loadLabel(mPackageManager) + "/" + imi2.getId();
256                 return imiId1.toString().compareTo(imiId2.toString());
257             }
258         });
259 
260         final int myImsCount = myImsList.size();
261         final int imiCount = imiList.size();
262         final CharSequence[] items = new CharSequence[myImsCount + imiCount];
263 
264         int checkedItem = 0;
265         int index = 0;
266         final CharSequence myImiLabel = myImi.loadLabel(mPackageManager);
267         for (int i = 0; i < myImsCount; i++) {
268             InputMethodSubtypeCompatWrapper ims = myImsList.get(i);
269             if (currentIms.equals(ims))
270                 checkedItem = index;
271             final CharSequence title = TextUtils.concat(
272                     ims.getDisplayName(mService, mLatinImePackageName, mApplicationInfo),
273                     " (" + myImiLabel, ")");
274             items[index] = title;
275             index++;
276         }
277 
278         for (int i = 0; i < imiCount; i++) {
279             final InputMethodInfoCompatWrapper imi = imiList.get(i);
280             final CharSequence title = imi.loadLabel(mPackageManager);
281             items[index] = title;
282             index++;
283         }
284 
285         final OnClickListener buttonListener = new OnClickListener() {
286             @Override
287             public void onClick(DialogInterface di, int whichButton) {
288                 final Intent intent = new Intent("android.settings.INPUT_METHOD_SETTINGS");
289                 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
290                         | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
291                         | Intent.FLAG_ACTIVITY_CLEAR_TOP);
292                 mService.startActivity(intent);
293             }
294         };
295         final InputMethodServiceCompatWrapper service = mService;
296         final IBinder token = service.getWindow().getWindow().getAttributes().token;
297         final OnClickListener selectionListener = new OnClickListener() {
298             @Override
299             public void onClick(DialogInterface di, int which) {
300                 di.dismiss();
301                 if (which < myImsCount) {
302                     final int imsIndex = which;
303                     final InputMethodSubtypeCompatWrapper ims = myImsList.get(imsIndex);
304                     service.notifyOnCurrentInputMethodSubtypeChanged(ims);
305                 } else {
306                     final int imiIndex = which - myImsCount;
307                     final InputMethodInfoCompatWrapper imi = imiList.get(imiIndex);
308                     setInputMethodAndSubtype(token, imi.getId(), null);
309                 }
310             }
311         };
312 
313         final AlertDialog.Builder builder = new AlertDialog.Builder(mService)
314                 .setTitle(mService.getString(R.string.selectInputMethod))
315                 .setNeutralButton(R.string.configure_input_method, buttonListener)
316                 .setSingleChoiceItems(items, checkedItem, selectionListener);
317         mService.showOptionDialogInternal(builder.create());
318     }
319 }
320