• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *
3  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
4  * use this file except in compliance with the License. You may obtain a copy of
5  * the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12  * License for the specific language governing permissions and limitations under
13  * the License.
14  */
15 
16 package com.android.server;
17 
18 import com.android.internal.content.PackageMonitor;
19 import com.android.internal.os.AtomicFile;
20 import com.android.internal.os.HandlerCaller;
21 import com.android.internal.util.FastXmlSerializer;
22 import com.android.internal.view.IInputContext;
23 import com.android.internal.view.IInputMethod;
24 import com.android.internal.view.IInputMethodCallback;
25 import com.android.internal.view.IInputMethodClient;
26 import com.android.internal.view.IInputMethodManager;
27 import com.android.internal.view.IInputMethodSession;
28 import com.android.internal.view.InputBindResult;
29 import com.android.server.EventLogTags;
30 
31 import org.xmlpull.v1.XmlPullParser;
32 import org.xmlpull.v1.XmlPullParserException;
33 import org.xmlpull.v1.XmlSerializer;
34 
35 import android.app.ActivityManagerNative;
36 import android.app.AlertDialog;
37 import android.app.KeyguardManager;
38 import android.app.Notification;
39 import android.app.NotificationManager;
40 import android.app.PendingIntent;
41 import android.content.ComponentName;
42 import android.content.ContentResolver;
43 import android.content.Context;
44 import android.content.DialogInterface;
45 import android.content.DialogInterface.OnCancelListener;
46 import android.content.Intent;
47 import android.content.IntentFilter;
48 import android.content.ServiceConnection;
49 import android.content.pm.ApplicationInfo;
50 import android.content.pm.PackageManager;
51 import android.content.pm.PackageManager.NameNotFoundException;
52 import android.content.pm.ResolveInfo;
53 import android.content.pm.ServiceInfo;
54 import android.content.res.Configuration;
55 import android.content.res.Resources;
56 import android.content.res.TypedArray;
57 import android.database.ContentObserver;
58 import android.inputmethodservice.InputMethodService;
59 import android.os.Binder;
60 import android.os.Environment;
61 import android.os.Handler;
62 import android.os.IBinder;
63 import android.os.IInterface;
64 import android.os.Message;
65 import android.os.Parcel;
66 import android.os.RemoteException;
67 import android.os.ResultReceiver;
68 import android.os.ServiceManager;
69 import android.os.SystemClock;
70 import android.provider.Settings;
71 import android.provider.Settings.Secure;
72 import android.provider.Settings.SettingNotFoundException;
73 import android.text.TextUtils;
74 import android.text.style.SuggestionSpan;
75 import android.util.EventLog;
76 import android.util.LruCache;
77 import android.util.Pair;
78 import android.util.PrintWriterPrinter;
79 import android.util.Printer;
80 import android.util.Slog;
81 import android.util.Xml;
82 import android.view.IWindowManager;
83 import android.view.LayoutInflater;
84 import android.view.View;
85 import android.view.ViewGroup;
86 import android.view.WindowManager;
87 import android.view.inputmethod.EditorInfo;
88 import android.view.inputmethod.InputBinding;
89 import android.view.inputmethod.InputMethod;
90 import android.view.inputmethod.InputMethodInfo;
91 import android.view.inputmethod.InputMethodManager;
92 import android.view.inputmethod.InputMethodSubtype;
93 import android.widget.ArrayAdapter;
94 import android.widget.RadioButton;
95 import android.widget.TextView;
96 
97 import java.io.File;
98 import java.io.FileDescriptor;
99 import java.io.FileInputStream;
100 import java.io.FileOutputStream;
101 import java.io.IOException;
102 import java.io.PrintWriter;
103 import java.util.ArrayList;
104 import java.util.Comparator;
105 import java.util.HashMap;
106 import java.util.HashSet;
107 import java.util.List;
108 import java.util.TreeMap;
109 
110 /**
111  * This class provides a system service that manages input methods.
112  */
113 public class InputMethodManagerService extends IInputMethodManager.Stub
114         implements ServiceConnection, Handler.Callback {
115     static final boolean DEBUG = false;
116     static final String TAG = "InputManagerService";
117 
118     static final int MSG_SHOW_IM_PICKER = 1;
119     static final int MSG_SHOW_IM_SUBTYPE_PICKER = 2;
120     static final int MSG_SHOW_IM_SUBTYPE_ENABLER = 3;
121     static final int MSG_SHOW_IM_CONFIG = 4;
122 
123     static final int MSG_UNBIND_INPUT = 1000;
124     static final int MSG_BIND_INPUT = 1010;
125     static final int MSG_SHOW_SOFT_INPUT = 1020;
126     static final int MSG_HIDE_SOFT_INPUT = 1030;
127     static final int MSG_ATTACH_TOKEN = 1040;
128     static final int MSG_CREATE_SESSION = 1050;
129 
130     static final int MSG_START_INPUT = 2000;
131     static final int MSG_RESTART_INPUT = 2010;
132 
133     static final int MSG_UNBIND_METHOD = 3000;
134     static final int MSG_BIND_METHOD = 3010;
135 
136     static final long TIME_TO_RECONNECT = 10*1000;
137 
138     static final int SECURE_SUGGESTION_SPANS_MAX_SIZE = 20;
139 
140     private static final int NOT_A_SUBTYPE_ID = -1;
141     private static final String NOT_A_SUBTYPE_ID_STR = String.valueOf(NOT_A_SUBTYPE_ID);
142     private static final String SUBTYPE_MODE_KEYBOARD = "keyboard";
143     private static final String SUBTYPE_MODE_VOICE = "voice";
144     private static final String TAG_TRY_SUPPRESSING_IME_SWITCHER = "TrySuppressingImeSwitcher";
145 
146     final Context mContext;
147     final Resources mRes;
148     final Handler mHandler;
149     final InputMethodSettings mSettings;
150     final SettingsObserver mSettingsObserver;
151     final IWindowManager mIWindowManager;
152     final HandlerCaller mCaller;
153     private final InputMethodFileManager mFileManager;
154 
155     final InputBindResult mNoBinding = new InputBindResult(null, null, -1);
156 
157     // All known input methods.  mMethodMap also serves as the global
158     // lock for this class.
159     final ArrayList<InputMethodInfo> mMethodList = new ArrayList<InputMethodInfo>();
160     final HashMap<String, InputMethodInfo> mMethodMap = new HashMap<String, InputMethodInfo>();
161     private final LruCache<SuggestionSpan, InputMethodInfo> mSecureSuggestionSpans =
162             new LruCache<SuggestionSpan, InputMethodInfo>(SECURE_SUGGESTION_SPANS_MAX_SIZE);
163 
164     // Ongoing notification
165     private NotificationManager mNotificationManager;
166     private KeyguardManager mKeyguardManager;
167     private StatusBarManagerService mStatusBar;
168     private Notification mImeSwitcherNotification;
169     private PendingIntent mImeSwitchPendingIntent;
170     private boolean mShowOngoingImeSwitcherForPhones;
171     private boolean mNotificationShown;
172 
173     class SessionState {
174         final ClientState client;
175         final IInputMethod method;
176         final IInputMethodSession session;
177 
178         @Override
toString()179         public String toString() {
180             return "SessionState{uid " + client.uid + " pid " + client.pid
181                     + " method " + Integer.toHexString(
182                             System.identityHashCode(method))
183                     + " session " + Integer.toHexString(
184                             System.identityHashCode(session))
185                     + "}";
186         }
187 
SessionState(ClientState _client, IInputMethod _method, IInputMethodSession _session)188         SessionState(ClientState _client, IInputMethod _method,
189                 IInputMethodSession _session) {
190             client = _client;
191             method = _method;
192             session = _session;
193         }
194     }
195 
196     class ClientState {
197         final IInputMethodClient client;
198         final IInputContext inputContext;
199         final int uid;
200         final int pid;
201         final InputBinding binding;
202 
203         boolean sessionRequested;
204         SessionState curSession;
205 
206         @Override
toString()207         public String toString() {
208             return "ClientState{" + Integer.toHexString(
209                     System.identityHashCode(this)) + " uid " + uid
210                     + " pid " + pid + "}";
211         }
212 
ClientState(IInputMethodClient _client, IInputContext _inputContext, int _uid, int _pid)213         ClientState(IInputMethodClient _client, IInputContext _inputContext,
214                 int _uid, int _pid) {
215             client = _client;
216             inputContext = _inputContext;
217             uid = _uid;
218             pid = _pid;
219             binding = new InputBinding(null, inputContext.asBinder(), uid, pid);
220         }
221     }
222 
223     final HashMap<IBinder, ClientState> mClients
224             = new HashMap<IBinder, ClientState>();
225 
226     /**
227      * Set once the system is ready to run third party code.
228      */
229     boolean mSystemReady;
230 
231     /**
232      * Id of the currently selected input method.
233      */
234     String mCurMethodId;
235 
236     /**
237      * The current binding sequence number, incremented every time there is
238      * a new bind performed.
239      */
240     int mCurSeq;
241 
242     /**
243      * The client that is currently bound to an input method.
244      */
245     ClientState mCurClient;
246 
247     /**
248      * The last window token that gained focus.
249      */
250     IBinder mCurFocusedWindow;
251 
252     /**
253      * The input context last provided by the current client.
254      */
255     IInputContext mCurInputContext;
256 
257     /**
258      * The attributes last provided by the current client.
259      */
260     EditorInfo mCurAttribute;
261 
262     /**
263      * The input method ID of the input method service that we are currently
264      * connected to or in the process of connecting to.
265      */
266     String mCurId;
267 
268     /**
269      * The current subtype of the current input method.
270      */
271     private InputMethodSubtype mCurrentSubtype;
272 
273     // This list contains the pairs of InputMethodInfo and InputMethodSubtype.
274     private final HashMap<InputMethodInfo, ArrayList<InputMethodSubtype>>
275             mShortcutInputMethodsAndSubtypes =
276                 new HashMap<InputMethodInfo, ArrayList<InputMethodSubtype>>();
277 
278     /**
279      * Set to true if our ServiceConnection is currently actively bound to
280      * a service (whether or not we have gotten its IBinder back yet).
281      */
282     boolean mHaveConnection;
283 
284     /**
285      * Set if the client has asked for the input method to be shown.
286      */
287     boolean mShowRequested;
288 
289     /**
290      * Set if we were explicitly told to show the input method.
291      */
292     boolean mShowExplicitlyRequested;
293 
294     /**
295      * Set if we were forced to be shown.
296      */
297     boolean mShowForced;
298 
299     /**
300      * Set if we last told the input method to show itself.
301      */
302     boolean mInputShown;
303 
304     /**
305      * The Intent used to connect to the current input method.
306      */
307     Intent mCurIntent;
308 
309     /**
310      * The token we have made for the currently active input method, to
311      * identify it in the future.
312      */
313     IBinder mCurToken;
314 
315     /**
316      * If non-null, this is the input method service we are currently connected
317      * to.
318      */
319     IInputMethod mCurMethod;
320 
321     /**
322      * Time that we last initiated a bind to the input method, to determine
323      * if we should try to disconnect and reconnect to it.
324      */
325     long mLastBindTime;
326 
327     /**
328      * Have we called mCurMethod.bindInput()?
329      */
330     boolean mBoundToMethod;
331 
332     /**
333      * Currently enabled session.  Only touched by service thread, not
334      * protected by a lock.
335      */
336     SessionState mEnabledSession;
337 
338     /**
339      * True if the screen is on.  The value is true initially.
340      */
341     boolean mScreenOn = true;
342 
343     int mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT;
344     int mImeWindowVis;
345 
346     private AlertDialog.Builder mDialogBuilder;
347     private AlertDialog mSwitchingDialog;
348     private InputMethodInfo[] mIms;
349     private int[] mSubtypeIds;
350 
351     class SettingsObserver extends ContentObserver {
SettingsObserver(Handler handler)352         SettingsObserver(Handler handler) {
353             super(handler);
354             ContentResolver resolver = mContext.getContentResolver();
355             resolver.registerContentObserver(Settings.Secure.getUriFor(
356                     Settings.Secure.DEFAULT_INPUT_METHOD), false, this);
357             resolver.registerContentObserver(Settings.Secure.getUriFor(
358                     Settings.Secure.ENABLED_INPUT_METHODS), false, this);
359             resolver.registerContentObserver(Settings.Secure.getUriFor(
360                     Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE), false, this);
361         }
362 
onChange(boolean selfChange)363         @Override public void onChange(boolean selfChange) {
364             synchronized (mMethodMap) {
365                 updateFromSettingsLocked();
366             }
367         }
368     }
369 
370     class ScreenOnOffReceiver extends android.content.BroadcastReceiver {
371         @Override
onReceive(Context context, Intent intent)372         public void onReceive(Context context, Intent intent) {
373             if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
374                 mScreenOn = true;
375             } else if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
376                 mScreenOn = false;
377             } else if (intent.getAction().equals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) {
378                 hideInputMethodMenu();
379                 return;
380             } else {
381                 Slog.w(TAG, "Unexpected intent " + intent);
382             }
383 
384             // Inform the current client of the change in active status
385             try {
386                 if (mCurClient != null && mCurClient.client != null) {
387                     mCurClient.client.setActive(mScreenOn);
388                 }
389             } catch (RemoteException e) {
390                 Slog.w(TAG, "Got RemoteException sending 'screen on/off' notification to pid "
391                         + mCurClient.pid + " uid " + mCurClient.uid);
392             }
393         }
394     }
395 
396     class MyPackageMonitor extends PackageMonitor {
397 
398         @Override
onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit)399         public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
400             synchronized (mMethodMap) {
401                 String curInputMethodId = Settings.Secure.getString(mContext
402                         .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
403                 final int N = mMethodList.size();
404                 if (curInputMethodId != null) {
405                     for (int i=0; i<N; i++) {
406                         InputMethodInfo imi = mMethodList.get(i);
407                         if (imi.getId().equals(curInputMethodId)) {
408                             for (String pkg : packages) {
409                                 if (imi.getPackageName().equals(pkg)) {
410                                     if (!doit) {
411                                         return true;
412                                     }
413                                     resetSelectedInputMethodAndSubtypeLocked("");
414                                     chooseNewDefaultIMELocked();
415                                     return true;
416                                 }
417                             }
418                         }
419                     }
420                 }
421             }
422             return false;
423         }
424 
425         @Override
onSomePackagesChanged()426         public void onSomePackagesChanged() {
427             synchronized (mMethodMap) {
428                 InputMethodInfo curIm = null;
429                 String curInputMethodId = Settings.Secure.getString(mContext
430                         .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
431                 final int N = mMethodList.size();
432                 if (curInputMethodId != null) {
433                     for (int i=0; i<N; i++) {
434                         InputMethodInfo imi = mMethodList.get(i);
435                         final String imiId = imi.getId();
436                         if (imiId.equals(curInputMethodId)) {
437                             curIm = imi;
438                         }
439 
440                         int change = isPackageDisappearing(imi.getPackageName());
441                         if (isPackageModified(imi.getPackageName())) {
442                             mFileManager.deleteAllInputMethodSubtypes(imiId);
443                         }
444                         if (change == PACKAGE_TEMPORARY_CHANGE
445                                 || change == PACKAGE_PERMANENT_CHANGE) {
446                             Slog.i(TAG, "Input method uninstalled, disabling: "
447                                     + imi.getComponent());
448                             setInputMethodEnabledLocked(imi.getId(), false);
449                         }
450                     }
451                 }
452 
453                 buildInputMethodListLocked(mMethodList, mMethodMap);
454 
455                 boolean changed = false;
456 
457                 if (curIm != null) {
458                     int change = isPackageDisappearing(curIm.getPackageName());
459                     if (change == PACKAGE_TEMPORARY_CHANGE
460                             || change == PACKAGE_PERMANENT_CHANGE) {
461                         ServiceInfo si = null;
462                         try {
463                             si = mContext.getPackageManager().getServiceInfo(
464                                     curIm.getComponent(), 0);
465                         } catch (PackageManager.NameNotFoundException ex) {
466                         }
467                         if (si == null) {
468                             // Uh oh, current input method is no longer around!
469                             // Pick another one...
470                             Slog.i(TAG, "Current input method removed: " + curInputMethodId);
471                             mImeWindowVis = 0;
472                             updateImeWindowStatusLocked();
473                             if (!chooseNewDefaultIMELocked()) {
474                                 changed = true;
475                                 curIm = null;
476                                 Slog.i(TAG, "Unsetting current input method");
477                                 resetSelectedInputMethodAndSubtypeLocked("");
478                             }
479                         }
480                     }
481                 }
482 
483                 if (curIm == null) {
484                     // We currently don't have a default input method... is
485                     // one now available?
486                     changed = chooseNewDefaultIMELocked();
487                 }
488 
489                 if (changed) {
490                     updateFromSettingsLocked();
491                 }
492             }
493         }
494     }
495 
496     class MethodCallback extends IInputMethodCallback.Stub {
497         final IInputMethod mMethod;
498 
MethodCallback(IInputMethod method)499         MethodCallback(IInputMethod method) {
500             mMethod = method;
501         }
502 
503         @Override
finishedEvent(int seq, boolean handled)504         public void finishedEvent(int seq, boolean handled) throws RemoteException {
505         }
506 
507         @Override
sessionCreated(IInputMethodSession session)508         public void sessionCreated(IInputMethodSession session) throws RemoteException {
509             onSessionCreated(mMethod, session);
510         }
511     }
512 
InputMethodManagerService(Context context)513     public InputMethodManagerService(Context context) {
514         mContext = context;
515         mRes = context.getResources();
516         mHandler = new Handler(this);
517         mIWindowManager = IWindowManager.Stub.asInterface(
518                 ServiceManager.getService(Context.WINDOW_SERVICE));
519         mCaller = new HandlerCaller(context, new HandlerCaller.Callback() {
520             @Override
521             public void executeMessage(Message msg) {
522                 handleMessage(msg);
523             }
524         });
525 
526         mImeSwitcherNotification = new Notification();
527         mImeSwitcherNotification.icon = com.android.internal.R.drawable.ic_notification_ime_default;
528         mImeSwitcherNotification.when = 0;
529         mImeSwitcherNotification.flags = Notification.FLAG_ONGOING_EVENT;
530         mImeSwitcherNotification.tickerText = null;
531         mImeSwitcherNotification.defaults = 0; // please be quiet
532         mImeSwitcherNotification.sound = null;
533         mImeSwitcherNotification.vibrate = null;
534         Intent intent = new Intent(Settings.ACTION_SHOW_INPUT_METHOD_PICKER);
535         mImeSwitchPendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
536 
537         mShowOngoingImeSwitcherForPhones = false;
538 
539         synchronized (mMethodMap) {
540             mFileManager = new InputMethodFileManager(mMethodMap);
541         }
542 
543         (new MyPackageMonitor()).register(mContext, true);
544 
545         IntentFilter screenOnOffFilt = new IntentFilter();
546         screenOnOffFilt.addAction(Intent.ACTION_SCREEN_ON);
547         screenOnOffFilt.addAction(Intent.ACTION_SCREEN_OFF);
548         screenOnOffFilt.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
549         mContext.registerReceiver(new ScreenOnOffReceiver(), screenOnOffFilt);
550 
551         mNotificationShown = false;
552 
553         // mSettings should be created before buildInputMethodListLocked
554         mSettings = new InputMethodSettings(
555                 mRes, context.getContentResolver(), mMethodMap, mMethodList);
556         buildInputMethodListLocked(mMethodList, mMethodMap);
557         mSettings.enableAllIMEsIfThereIsNoEnabledIME();
558 
559         if (TextUtils.isEmpty(Settings.Secure.getString(
560                 mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD))) {
561             InputMethodInfo defIm = null;
562             for (InputMethodInfo imi: mMethodList) {
563                 if (defIm == null && imi.getIsDefaultResourceId() != 0) {
564                     try {
565                         Resources res = context.createPackageContext(
566                                 imi.getPackageName(), 0).getResources();
567                         if (res.getBoolean(imi.getIsDefaultResourceId())) {
568                             defIm = imi;
569                             Slog.i(TAG, "Selected default: " + imi.getId());
570                         }
571                     } catch (PackageManager.NameNotFoundException ex) {
572                     } catch (Resources.NotFoundException ex) {
573                     }
574                 }
575             }
576             if (defIm == null && mMethodList.size() > 0) {
577                 defIm = getMostApplicableDefaultIMELocked();
578                 Slog.i(TAG, "No default found, using " + defIm.getId());
579             }
580             if (defIm != null) {
581                 setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false);
582             }
583         }
584 
585         mSettingsObserver = new SettingsObserver(mHandler);
586         updateFromSettingsLocked();
587     }
588 
589     @Override
onTransact(int code, Parcel data, Parcel reply, int flags)590     public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
591             throws RemoteException {
592         try {
593             return super.onTransact(code, data, reply, flags);
594         } catch (RuntimeException e) {
595             // The input method manager only throws security exceptions, so let's
596             // log all others.
597             if (!(e instanceof SecurityException)) {
598                 Slog.e(TAG, "Input Method Manager Crash", e);
599             }
600             throw e;
601         }
602     }
603 
systemReady(StatusBarManagerService statusBar)604     public void systemReady(StatusBarManagerService statusBar) {
605         synchronized (mMethodMap) {
606             if (!mSystemReady) {
607                 mSystemReady = true;
608                 mKeyguardManager = (KeyguardManager)
609                         mContext.getSystemService(Context.KEYGUARD_SERVICE);
610                 mNotificationManager = (NotificationManager)
611                         mContext.getSystemService(Context.NOTIFICATION_SERVICE);
612                 mStatusBar = statusBar;
613                 statusBar.setIconVisibility("ime", false);
614                 updateImeWindowStatusLocked();
615                 mShowOngoingImeSwitcherForPhones = mRes.getBoolean(
616                         com.android.internal.R.bool.show_ongoing_ime_switcher);
617                 try {
618                     startInputInnerLocked();
619                 } catch (RuntimeException e) {
620                     Slog.w(TAG, "Unexpected exception", e);
621                 }
622             }
623         }
624     }
625 
updateImeWindowStatusLocked()626     void updateImeWindowStatusLocked() {
627         setImeWindowStatus(mCurToken, mImeWindowVis, mBackDisposition);
628     }
629 
630     @Override
getInputMethodList()631     public List<InputMethodInfo> getInputMethodList() {
632         synchronized (mMethodMap) {
633             return new ArrayList<InputMethodInfo>(mMethodList);
634         }
635     }
636 
637     @Override
getEnabledInputMethodList()638     public List<InputMethodInfo> getEnabledInputMethodList() {
639         synchronized (mMethodMap) {
640             return mSettings.getEnabledInputMethodListLocked();
641         }
642     }
643 
644     private HashMap<InputMethodInfo, List<InputMethodSubtype>>
getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked()645             getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked() {
646         HashMap<InputMethodInfo, List<InputMethodSubtype>> enabledInputMethodAndSubtypes =
647                 new HashMap<InputMethodInfo, List<InputMethodSubtype>>();
648         for (InputMethodInfo imi: getEnabledInputMethodList()) {
649             enabledInputMethodAndSubtypes.put(
650                     imi, getEnabledInputMethodSubtypeListLocked(imi, true));
651         }
652         return enabledInputMethodAndSubtypes;
653     }
654 
getEnabledInputMethodSubtypeListLocked(InputMethodInfo imi, boolean allowsImplicitlySelectedSubtypes)655     public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(InputMethodInfo imi,
656             boolean allowsImplicitlySelectedSubtypes) {
657         if (imi == null && mCurMethodId != null) {
658             imi = mMethodMap.get(mCurMethodId);
659         }
660         List<InputMethodSubtype> enabledSubtypes =
661                 mSettings.getEnabledInputMethodSubtypeListLocked(imi);
662         if (allowsImplicitlySelectedSubtypes && enabledSubtypes.isEmpty()) {
663             enabledSubtypes = getImplicitlyApplicableSubtypesLocked(mRes, imi);
664         }
665         return InputMethodSubtype.sort(mContext, 0, imi, enabledSubtypes);
666     }
667 
668     @Override
getEnabledInputMethodSubtypeList(InputMethodInfo imi, boolean allowsImplicitlySelectedSubtypes)669     public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(InputMethodInfo imi,
670             boolean allowsImplicitlySelectedSubtypes) {
671         synchronized (mMethodMap) {
672             return getEnabledInputMethodSubtypeListLocked(imi, allowsImplicitlySelectedSubtypes);
673         }
674     }
675 
676     @Override
addClient(IInputMethodClient client, IInputContext inputContext, int uid, int pid)677     public void addClient(IInputMethodClient client,
678             IInputContext inputContext, int uid, int pid) {
679         synchronized (mMethodMap) {
680             mClients.put(client.asBinder(), new ClientState(client,
681                     inputContext, uid, pid));
682         }
683     }
684 
685     @Override
removeClient(IInputMethodClient client)686     public void removeClient(IInputMethodClient client) {
687         synchronized (mMethodMap) {
688             mClients.remove(client.asBinder());
689         }
690     }
691 
executeOrSendMessage(IInterface target, Message msg)692     void executeOrSendMessage(IInterface target, Message msg) {
693          if (target.asBinder() instanceof Binder) {
694              mCaller.sendMessage(msg);
695          } else {
696              handleMessage(msg);
697              msg.recycle();
698          }
699     }
700 
unbindCurrentClientLocked()701     void unbindCurrentClientLocked() {
702         if (mCurClient != null) {
703             if (DEBUG) Slog.v(TAG, "unbindCurrentInputLocked: client = "
704                     + mCurClient.client.asBinder());
705             if (mBoundToMethod) {
706                 mBoundToMethod = false;
707                 if (mCurMethod != null) {
708                     executeOrSendMessage(mCurMethod, mCaller.obtainMessageO(
709                             MSG_UNBIND_INPUT, mCurMethod));
710                 }
711             }
712             executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
713                     MSG_UNBIND_METHOD, mCurSeq, mCurClient.client));
714             mCurClient.sessionRequested = false;
715 
716             // Call setActive(false) on the old client
717             try {
718                 mCurClient.client.setActive(false);
719             } catch (RemoteException e) {
720                 Slog.w(TAG, "Got RemoteException sending setActive(false) notification to pid "
721                         + mCurClient.pid + " uid " + mCurClient.uid);
722             }
723             mCurClient = null;
724 
725             hideInputMethodMenuLocked();
726         }
727     }
728 
getImeShowFlags()729     private int getImeShowFlags() {
730         int flags = 0;
731         if (mShowForced) {
732             flags |= InputMethod.SHOW_FORCED
733                     | InputMethod.SHOW_EXPLICIT;
734         } else if (mShowExplicitlyRequested) {
735             flags |= InputMethod.SHOW_EXPLICIT;
736         }
737         return flags;
738     }
739 
getAppShowFlags()740     private int getAppShowFlags() {
741         int flags = 0;
742         if (mShowForced) {
743             flags |= InputMethodManager.SHOW_FORCED;
744         } else if (!mShowExplicitlyRequested) {
745             flags |= InputMethodManager.SHOW_IMPLICIT;
746         }
747         return flags;
748     }
749 
attachNewInputLocked(boolean initial, boolean needResult)750     InputBindResult attachNewInputLocked(boolean initial, boolean needResult) {
751         if (!mBoundToMethod) {
752             executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
753                     MSG_BIND_INPUT, mCurMethod, mCurClient.binding));
754             mBoundToMethod = true;
755         }
756         final SessionState session = mCurClient.curSession;
757         if (initial) {
758             executeOrSendMessage(session.method, mCaller.obtainMessageOOO(
759                     MSG_START_INPUT, session, mCurInputContext, mCurAttribute));
760         } else {
761             executeOrSendMessage(session.method, mCaller.obtainMessageOOO(
762                     MSG_RESTART_INPUT, session, mCurInputContext, mCurAttribute));
763         }
764         if (mShowRequested) {
765             if (DEBUG) Slog.v(TAG, "Attach new input asks to show input");
766             showCurrentInputLocked(getAppShowFlags(), null);
767         }
768         return needResult
769                 ? new InputBindResult(session.session, mCurId, mCurSeq)
770                 : null;
771     }
772 
startInputLocked(IInputMethodClient client, IInputContext inputContext, EditorInfo attribute, boolean initial, boolean needResult)773     InputBindResult startInputLocked(IInputMethodClient client,
774             IInputContext inputContext, EditorInfo attribute,
775             boolean initial, boolean needResult) {
776         // If no method is currently selected, do nothing.
777         if (mCurMethodId == null) {
778             return mNoBinding;
779         }
780 
781         ClientState cs = mClients.get(client.asBinder());
782         if (cs == null) {
783             throw new IllegalArgumentException("unknown client "
784                     + client.asBinder());
785         }
786 
787         try {
788             if (!mIWindowManager.inputMethodClientHasFocus(cs.client)) {
789                 // Check with the window manager to make sure this client actually
790                 // has a window with focus.  If not, reject.  This is thread safe
791                 // because if the focus changes some time before or after, the
792                 // next client receiving focus that has any interest in input will
793                 // be calling through here after that change happens.
794                 Slog.w(TAG, "Starting input on non-focused client " + cs.client
795                         + " (uid=" + cs.uid + " pid=" + cs.pid + ")");
796                 return null;
797             }
798         } catch (RemoteException e) {
799         }
800 
801         if (mCurClient != cs) {
802             // If the client is changing, we need to switch over to the new
803             // one.
804             unbindCurrentClientLocked();
805             if (DEBUG) Slog.v(TAG, "switching to client: client = "
806                     + cs.client.asBinder());
807 
808             // If the screen is on, inform the new client it is active
809             if (mScreenOn) {
810                 try {
811                     cs.client.setActive(mScreenOn);
812                 } catch (RemoteException e) {
813                     Slog.w(TAG, "Got RemoteException sending setActive notification to pid "
814                             + cs.pid + " uid " + cs.uid);
815                 }
816             }
817         }
818 
819         // Bump up the sequence for this client and attach it.
820         mCurSeq++;
821         if (mCurSeq <= 0) mCurSeq = 1;
822         mCurClient = cs;
823         mCurInputContext = inputContext;
824         mCurAttribute = attribute;
825 
826         // Check if the input method is changing.
827         if (mCurId != null && mCurId.equals(mCurMethodId)) {
828             if (cs.curSession != null) {
829                 // Fast case: if we are already connected to the input method,
830                 // then just return it.
831                 return attachNewInputLocked(initial, needResult);
832             }
833             if (mHaveConnection) {
834                 if (mCurMethod != null) {
835                     if (!cs.sessionRequested) {
836                         cs.sessionRequested = true;
837                         if (DEBUG) Slog.v(TAG, "Creating new session for client " + cs);
838                         executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
839                                 MSG_CREATE_SESSION, mCurMethod,
840                                 new MethodCallback(mCurMethod)));
841                     }
842                     // Return to client, and we will get back with it when
843                     // we have had a session made for it.
844                     return new InputBindResult(null, mCurId, mCurSeq);
845                 } else if (SystemClock.uptimeMillis()
846                         < (mLastBindTime+TIME_TO_RECONNECT)) {
847                     // In this case we have connected to the service, but
848                     // don't yet have its interface.  If it hasn't been too
849                     // long since we did the connection, we'll return to
850                     // the client and wait to get the service interface so
851                     // we can report back.  If it has been too long, we want
852                     // to fall through so we can try a disconnect/reconnect
853                     // to see if we can get back in touch with the service.
854                     return new InputBindResult(null, mCurId, mCurSeq);
855                 } else {
856                     EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME,
857                             mCurMethodId, SystemClock.uptimeMillis()-mLastBindTime, 0);
858                 }
859             }
860         }
861 
862         return startInputInnerLocked();
863     }
864 
startInputInnerLocked()865     InputBindResult startInputInnerLocked() {
866         if (mCurMethodId == null) {
867             return mNoBinding;
868         }
869 
870         if (!mSystemReady) {
871             // If the system is not yet ready, we shouldn't be running third
872             // party code.
873             return new InputBindResult(null, mCurMethodId, mCurSeq);
874         }
875 
876         InputMethodInfo info = mMethodMap.get(mCurMethodId);
877         if (info == null) {
878             throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
879         }
880 
881         unbindCurrentMethodLocked(false);
882 
883         mCurIntent = new Intent(InputMethod.SERVICE_INTERFACE);
884         mCurIntent.setComponent(info.getComponent());
885         mCurIntent.putExtra(Intent.EXTRA_CLIENT_LABEL,
886                 com.android.internal.R.string.input_method_binding_label);
887         mCurIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
888                 mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), 0));
889         if (mContext.bindService(mCurIntent, this, Context.BIND_AUTO_CREATE)) {
890             mLastBindTime = SystemClock.uptimeMillis();
891             mHaveConnection = true;
892             mCurId = info.getId();
893             mCurToken = new Binder();
894             try {
895                 if (DEBUG) Slog.v(TAG, "Adding window token: " + mCurToken);
896                 mIWindowManager.addWindowToken(mCurToken,
897                         WindowManager.LayoutParams.TYPE_INPUT_METHOD);
898             } catch (RemoteException e) {
899             }
900             return new InputBindResult(null, mCurId, mCurSeq);
901         } else {
902             mCurIntent = null;
903             Slog.w(TAG, "Failure connecting to input method service: "
904                     + mCurIntent);
905         }
906         return null;
907     }
908 
909     @Override
startInput(IInputMethodClient client, IInputContext inputContext, EditorInfo attribute, boolean initial, boolean needResult)910     public InputBindResult startInput(IInputMethodClient client,
911             IInputContext inputContext, EditorInfo attribute,
912             boolean initial, boolean needResult) {
913         synchronized (mMethodMap) {
914             final long ident = Binder.clearCallingIdentity();
915             try {
916                 return startInputLocked(client, inputContext, attribute,
917                         initial, needResult);
918             } finally {
919                 Binder.restoreCallingIdentity(ident);
920             }
921         }
922     }
923 
924     @Override
finishInput(IInputMethodClient client)925     public void finishInput(IInputMethodClient client) {
926     }
927 
928     @Override
onServiceConnected(ComponentName name, IBinder service)929     public void onServiceConnected(ComponentName name, IBinder service) {
930         synchronized (mMethodMap) {
931             if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
932                 mCurMethod = IInputMethod.Stub.asInterface(service);
933                 if (mCurToken == null) {
934                     Slog.w(TAG, "Service connected without a token!");
935                     unbindCurrentMethodLocked(false);
936                     return;
937                 }
938                 if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
939                 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
940                         MSG_ATTACH_TOKEN, mCurMethod, mCurToken));
941                 if (mCurClient != null) {
942                     if (DEBUG) Slog.v(TAG, "Creating first session while with client "
943                             + mCurClient);
944                     executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
945                             MSG_CREATE_SESSION, mCurMethod,
946                             new MethodCallback(mCurMethod)));
947                 }
948             }
949         }
950     }
951 
onSessionCreated(IInputMethod method, IInputMethodSession session)952     void onSessionCreated(IInputMethod method, IInputMethodSession session) {
953         synchronized (mMethodMap) {
954             if (mCurMethod != null && method != null
955                     && mCurMethod.asBinder() == method.asBinder()) {
956                 if (mCurClient != null) {
957                     mCurClient.curSession = new SessionState(mCurClient,
958                             method, session);
959                     mCurClient.sessionRequested = false;
960                     InputBindResult res = attachNewInputLocked(true, true);
961                     if (res.method != null) {
962                         executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO(
963                                 MSG_BIND_METHOD, mCurClient.client, res));
964                     }
965                 }
966             }
967         }
968     }
969 
unbindCurrentMethodLocked(boolean reportToClient)970     void unbindCurrentMethodLocked(boolean reportToClient) {
971         if (mHaveConnection) {
972             mContext.unbindService(this);
973             mHaveConnection = false;
974         }
975 
976         if (mCurToken != null) {
977             try {
978                 if (DEBUG) Slog.v(TAG, "Removing window token: " + mCurToken);
979                 mIWindowManager.removeWindowToken(mCurToken);
980             } catch (RemoteException e) {
981             }
982             mCurToken = null;
983         }
984 
985         mCurId = null;
986         clearCurMethodLocked();
987 
988         if (reportToClient && mCurClient != null) {
989             executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
990                     MSG_UNBIND_METHOD, mCurSeq, mCurClient.client));
991         }
992     }
993 
finishSession(SessionState sessionState)994     private void finishSession(SessionState sessionState) {
995         if (sessionState != null && sessionState.session != null) {
996             try {
997                 sessionState.session.finishSession();
998             } catch (RemoteException e) {
999                 Slog.w(TAG, "Session failed to close due to remote exception", e);
1000                 mImeWindowVis = 0;
1001                 updateImeWindowStatusLocked();
1002             }
1003         }
1004     }
1005 
clearCurMethodLocked()1006     void clearCurMethodLocked() {
1007         if (mCurMethod != null) {
1008             for (ClientState cs : mClients.values()) {
1009                 cs.sessionRequested = false;
1010                 finishSession(cs.curSession);
1011                 cs.curSession = null;
1012             }
1013 
1014             finishSession(mEnabledSession);
1015             mEnabledSession = null;
1016             mCurMethod = null;
1017         }
1018         if (mStatusBar != null) {
1019             mStatusBar.setIconVisibility("ime", false);
1020         }
1021     }
1022 
1023     @Override
onServiceDisconnected(ComponentName name)1024     public void onServiceDisconnected(ComponentName name) {
1025         synchronized (mMethodMap) {
1026             if (DEBUG) Slog.v(TAG, "Service disconnected: " + name
1027                     + " mCurIntent=" + mCurIntent);
1028             if (mCurMethod != null && mCurIntent != null
1029                     && name.equals(mCurIntent.getComponent())) {
1030                 clearCurMethodLocked();
1031                 // We consider this to be a new bind attempt, since the system
1032                 // should now try to restart the service for us.
1033                 mLastBindTime = SystemClock.uptimeMillis();
1034                 mShowRequested = mInputShown;
1035                 mInputShown = false;
1036                 if (mCurClient != null) {
1037                     executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
1038                             MSG_UNBIND_METHOD, mCurSeq, mCurClient.client));
1039                 }
1040             }
1041         }
1042     }
1043 
1044     @Override
updateStatusIcon(IBinder token, String packageName, int iconId)1045     public void updateStatusIcon(IBinder token, String packageName, int iconId) {
1046         int uid = Binder.getCallingUid();
1047         long ident = Binder.clearCallingIdentity();
1048         try {
1049             if (token == null || mCurToken != token) {
1050                 Slog.w(TAG, "Ignoring setInputMethod of uid " + uid + " token: " + token);
1051                 return;
1052             }
1053 
1054             synchronized (mMethodMap) {
1055                 if (iconId == 0) {
1056                     if (DEBUG) Slog.d(TAG, "hide the small icon for the input method");
1057                     if (mStatusBar != null) {
1058                         mStatusBar.setIconVisibility("ime", false);
1059                     }
1060                 } else if (packageName != null) {
1061                     if (DEBUG) Slog.d(TAG, "show a small icon for the input method");
1062                     CharSequence contentDescription = null;
1063                     try {
1064                         PackageManager packageManager = mContext.getPackageManager();
1065                         contentDescription = packageManager.getApplicationLabel(
1066                                 packageManager.getApplicationInfo(packageName, 0));
1067                     } catch (NameNotFoundException nnfe) {
1068                         /* ignore */
1069                     }
1070                     if (mStatusBar != null) {
1071                         mStatusBar.setIcon("ime", packageName, iconId, 0,
1072                                 contentDescription  != null
1073                                         ? contentDescription.toString() : null);
1074                         mStatusBar.setIconVisibility("ime", true);
1075                     }
1076                 }
1077             }
1078         } finally {
1079             Binder.restoreCallingIdentity(ident);
1080         }
1081     }
1082 
needsToShowImeSwitchOngoingNotification()1083     private boolean needsToShowImeSwitchOngoingNotification() {
1084         if (!mShowOngoingImeSwitcherForPhones) return false;
1085         synchronized (mMethodMap) {
1086             List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked();
1087             final int N = imis.size();
1088             if (N > 2) return true;
1089             if (N < 1) return false;
1090             int nonAuxCount = 0;
1091             int auxCount = 0;
1092             InputMethodSubtype nonAuxSubtype = null;
1093             InputMethodSubtype auxSubtype = null;
1094             for(int i = 0; i < N; ++i) {
1095                 final InputMethodInfo imi = imis.get(i);
1096                 final List<InputMethodSubtype> subtypes = getEnabledInputMethodSubtypeListLocked(
1097                         imi, true);
1098                 final int subtypeCount = subtypes.size();
1099                 if (subtypeCount == 0) {
1100                     ++nonAuxCount;
1101                 } else {
1102                     for (int j = 0; j < subtypeCount; ++j) {
1103                         final InputMethodSubtype subtype = subtypes.get(j);
1104                         if (!subtype.isAuxiliary()) {
1105                             ++nonAuxCount;
1106                             nonAuxSubtype = subtype;
1107                         } else {
1108                             ++auxCount;
1109                             auxSubtype = subtype;
1110                         }
1111                     }
1112                 }
1113             }
1114             if (nonAuxCount > 1 || auxCount > 1) {
1115                 return true;
1116             } else if (nonAuxCount == 1 && auxCount == 1) {
1117                 if (nonAuxSubtype != null && auxSubtype != null
1118                         && (nonAuxSubtype.getLocale().equals(auxSubtype.getLocale())
1119                                 || auxSubtype.overridesImplicitlyEnabledSubtype()
1120                                 || nonAuxSubtype.overridesImplicitlyEnabledSubtype())
1121                         && nonAuxSubtype.containsExtraValueKey(TAG_TRY_SUPPRESSING_IME_SWITCHER)) {
1122                     return false;
1123                 }
1124                 return true;
1125             }
1126             return false;
1127         }
1128     }
1129 
1130     @SuppressWarnings("deprecation")
1131     @Override
setImeWindowStatus(IBinder token, int vis, int backDisposition)1132     public void setImeWindowStatus(IBinder token, int vis, int backDisposition) {
1133         int uid = Binder.getCallingUid();
1134         long ident = Binder.clearCallingIdentity();
1135         try {
1136             if (token == null || mCurToken != token) {
1137                 Slog.w(TAG, "Ignoring setImeWindowStatus of uid " + uid + " token: " + token);
1138                 return;
1139             }
1140 
1141             synchronized (mMethodMap) {
1142                 mImeWindowVis = vis;
1143                 mBackDisposition = backDisposition;
1144                 if (mStatusBar != null) {
1145                     mStatusBar.setImeWindowStatus(token, vis, backDisposition);
1146                 }
1147                 final boolean iconVisibility = (vis & InputMethodService.IME_ACTIVE) != 0;
1148                 final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
1149                 if (imi != null && iconVisibility && needsToShowImeSwitchOngoingNotification()) {
1150                     final PackageManager pm = mContext.getPackageManager();
1151                     final CharSequence title = mRes.getText(
1152                             com.android.internal.R.string.select_input_method);
1153                     final CharSequence imiLabel = imi.loadLabel(pm);
1154                     final CharSequence summary = mCurrentSubtype != null
1155                             ? TextUtils.concat(mCurrentSubtype.getDisplayName(mContext,
1156                                         imi.getPackageName(), imi.getServiceInfo().applicationInfo),
1157                                                 (TextUtils.isEmpty(imiLabel) ?
1158                                                         "" : " - " + imiLabel))
1159                             : imiLabel;
1160 
1161                     mImeSwitcherNotification.setLatestEventInfo(
1162                             mContext, title, summary, mImeSwitchPendingIntent);
1163                     if (mNotificationManager != null) {
1164                         mNotificationManager.notify(
1165                                 com.android.internal.R.string.select_input_method,
1166                                 mImeSwitcherNotification);
1167                         mNotificationShown = true;
1168                     }
1169                 } else {
1170                     if (mNotificationShown && mNotificationManager != null) {
1171                         mNotificationManager.cancel(
1172                                 com.android.internal.R.string.select_input_method);
1173                         mNotificationShown = false;
1174                     }
1175                 }
1176             }
1177         } finally {
1178             Binder.restoreCallingIdentity(ident);
1179         }
1180     }
1181 
1182     @Override
registerSuggestionSpansForNotification(SuggestionSpan[] spans)1183     public void registerSuggestionSpansForNotification(SuggestionSpan[] spans) {
1184         synchronized (mMethodMap) {
1185             final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId);
1186             for (int i = 0; i < spans.length; ++i) {
1187                 SuggestionSpan ss = spans[i];
1188                 if (!TextUtils.isEmpty(ss.getNotificationTargetClassName())) {
1189                     mSecureSuggestionSpans.put(ss, currentImi);
1190                     final InputMethodInfo targetImi = mSecureSuggestionSpans.get(ss);
1191                 }
1192             }
1193         }
1194     }
1195 
1196     @Override
notifySuggestionPicked(SuggestionSpan span, String originalString, int index)1197     public boolean notifySuggestionPicked(SuggestionSpan span, String originalString, int index) {
1198         synchronized (mMethodMap) {
1199             final InputMethodInfo targetImi = mSecureSuggestionSpans.get(span);
1200             // TODO: Do not send the intent if the process of the targetImi is already dead.
1201             if (targetImi != null) {
1202                 final String[] suggestions = span.getSuggestions();
1203                 if (index < 0 || index >= suggestions.length) return false;
1204                 final String className = span.getNotificationTargetClassName();
1205                 final Intent intent = new Intent();
1206                 // Ensures that only a class in the original IME package will receive the
1207                 // notification.
1208                 intent.setClassName(targetImi.getPackageName(), className);
1209                 intent.setAction(SuggestionSpan.ACTION_SUGGESTION_PICKED);
1210                 intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_BEFORE, originalString);
1211                 intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_AFTER, suggestions[index]);
1212                 intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_HASHCODE, span.hashCode());
1213                 mContext.sendBroadcast(intent);
1214                 return true;
1215             }
1216         }
1217         return false;
1218     }
1219 
updateFromSettingsLocked()1220     void updateFromSettingsLocked() {
1221         // We are assuming that whoever is changing DEFAULT_INPUT_METHOD and
1222         // ENABLED_INPUT_METHODS is taking care of keeping them correctly in
1223         // sync, so we will never have a DEFAULT_INPUT_METHOD that is not
1224         // enabled.
1225         String id = Settings.Secure.getString(mContext.getContentResolver(),
1226                 Settings.Secure.DEFAULT_INPUT_METHOD);
1227         // There is no input method selected, try to choose new applicable input method.
1228         if (TextUtils.isEmpty(id) && chooseNewDefaultIMELocked()) {
1229             id = Settings.Secure.getString(mContext.getContentResolver(),
1230                     Settings.Secure.DEFAULT_INPUT_METHOD);
1231         }
1232         if (!TextUtils.isEmpty(id)) {
1233             try {
1234                 setInputMethodLocked(id, getSelectedInputMethodSubtypeId(id));
1235             } catch (IllegalArgumentException e) {
1236                 Slog.w(TAG, "Unknown input method from prefs: " + id, e);
1237                 mCurMethodId = null;
1238                 unbindCurrentMethodLocked(true);
1239             }
1240             mShortcutInputMethodsAndSubtypes.clear();
1241         } else {
1242             // There is no longer an input method set, so stop any current one.
1243             mCurMethodId = null;
1244             unbindCurrentMethodLocked(true);
1245         }
1246     }
1247 
setInputMethodLocked(String id, int subtypeId)1248     /* package */ void setInputMethodLocked(String id, int subtypeId) {
1249         InputMethodInfo info = mMethodMap.get(id);
1250         if (info == null) {
1251             throw new IllegalArgumentException("Unknown id: " + id);
1252         }
1253 
1254         if (id.equals(mCurMethodId)) {
1255             InputMethodSubtype subtype = null;
1256             if (subtypeId >= 0 && subtypeId < info.getSubtypeCount()) {
1257                 subtype = info.getSubtypeAt(subtypeId);
1258             }
1259             if (subtype != mCurrentSubtype) {
1260                 synchronized (mMethodMap) {
1261                     if (subtype != null) {
1262                         setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true);
1263                     }
1264                     if (mCurMethod != null) {
1265                         try {
1266                             final Configuration conf = mRes.getConfiguration();
1267                             final boolean haveHardKeyboard = conf.keyboard
1268                                     != Configuration.KEYBOARD_NOKEYS;
1269                             final boolean hardKeyShown = haveHardKeyboard
1270                                     && conf.hardKeyboardHidden
1271                                             != Configuration.HARDKEYBOARDHIDDEN_YES;
1272                             mImeWindowVis = (mInputShown || hardKeyShown) ? (
1273                                     InputMethodService.IME_ACTIVE | InputMethodService.IME_VISIBLE)
1274                                     : 0;
1275                             updateImeWindowStatusLocked();
1276                             // If subtype is null, try to find the most applicable one from
1277                             // getCurrentInputMethodSubtype.
1278                             if (subtype == null) {
1279                                 subtype = getCurrentInputMethodSubtype();
1280                             }
1281                             mCurMethod.changeInputMethodSubtype(subtype);
1282                         } catch (RemoteException e) {
1283                             return;
1284                         }
1285                     }
1286                 }
1287             }
1288             return;
1289         }
1290 
1291         final long ident = Binder.clearCallingIdentity();
1292         try {
1293             // Set a subtype to this input method.
1294             // subtypeId the name of a subtype which will be set.
1295             setSelectedInputMethodAndSubtypeLocked(info, subtypeId, false);
1296             // mCurMethodId should be updated after setSelectedInputMethodAndSubtypeLocked()
1297             // because mCurMethodId is stored as a history in
1298             // setSelectedInputMethodAndSubtypeLocked().
1299             mCurMethodId = id;
1300 
1301             if (ActivityManagerNative.isSystemReady()) {
1302                 Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED);
1303                 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
1304                 intent.putExtra("input_method_id", id);
1305                 mContext.sendBroadcast(intent);
1306             }
1307             unbindCurrentClientLocked();
1308         } finally {
1309             Binder.restoreCallingIdentity(ident);
1310         }
1311     }
1312 
1313     @Override
showSoftInput(IInputMethodClient client, int flags, ResultReceiver resultReceiver)1314     public boolean showSoftInput(IInputMethodClient client, int flags,
1315             ResultReceiver resultReceiver) {
1316         int uid = Binder.getCallingUid();
1317         long ident = Binder.clearCallingIdentity();
1318         try {
1319             synchronized (mMethodMap) {
1320                 if (mCurClient == null || client == null
1321                         || mCurClient.client.asBinder() != client.asBinder()) {
1322                     try {
1323                         // We need to check if this is the current client with
1324                         // focus in the window manager, to allow this call to
1325                         // be made before input is started in it.
1326                         if (!mIWindowManager.inputMethodClientHasFocus(client)) {
1327                             Slog.w(TAG, "Ignoring showSoftInput of uid " + uid + ": " + client);
1328                             return false;
1329                         }
1330                     } catch (RemoteException e) {
1331                         return false;
1332                     }
1333                 }
1334 
1335                 if (DEBUG) Slog.v(TAG, "Client requesting input be shown");
1336                 return showCurrentInputLocked(flags, resultReceiver);
1337             }
1338         } finally {
1339             Binder.restoreCallingIdentity(ident);
1340         }
1341     }
1342 
showCurrentInputLocked(int flags, ResultReceiver resultReceiver)1343     boolean showCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
1344         mShowRequested = true;
1345         if ((flags&InputMethodManager.SHOW_IMPLICIT) == 0) {
1346             mShowExplicitlyRequested = true;
1347         }
1348         if ((flags&InputMethodManager.SHOW_FORCED) != 0) {
1349             mShowExplicitlyRequested = true;
1350             mShowForced = true;
1351         }
1352 
1353         if (!mSystemReady) {
1354             return false;
1355         }
1356 
1357         boolean res = false;
1358         if (mCurMethod != null) {
1359             executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO(
1360                     MSG_SHOW_SOFT_INPUT, getImeShowFlags(), mCurMethod,
1361                     resultReceiver));
1362             mInputShown = true;
1363             res = true;
1364         } else if (mHaveConnection && SystemClock.uptimeMillis()
1365                 >= (mLastBindTime+TIME_TO_RECONNECT)) {
1366             // The client has asked to have the input method shown, but
1367             // we have been sitting here too long with a connection to the
1368             // service and no interface received, so let's disconnect/connect
1369             // to try to prod things along.
1370             EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, mCurMethodId,
1371                     SystemClock.uptimeMillis()-mLastBindTime,1);
1372             Slog.w(TAG, "Force disconnect/connect to the IME in showCurrentInputLocked()");
1373             mContext.unbindService(this);
1374             mContext.bindService(mCurIntent, this, Context.BIND_AUTO_CREATE);
1375         }
1376 
1377         return res;
1378     }
1379 
1380     @Override
hideSoftInput(IInputMethodClient client, int flags, ResultReceiver resultReceiver)1381     public boolean hideSoftInput(IInputMethodClient client, int flags,
1382             ResultReceiver resultReceiver) {
1383         int uid = Binder.getCallingUid();
1384         long ident = Binder.clearCallingIdentity();
1385         try {
1386             synchronized (mMethodMap) {
1387                 if (mCurClient == null || client == null
1388                         || mCurClient.client.asBinder() != client.asBinder()) {
1389                     try {
1390                         // We need to check if this is the current client with
1391                         // focus in the window manager, to allow this call to
1392                         // be made before input is started in it.
1393                         if (!mIWindowManager.inputMethodClientHasFocus(client)) {
1394                             if (DEBUG) Slog.w(TAG, "Ignoring hideSoftInput of uid "
1395                                     + uid + ": " + client);
1396                             mImeWindowVis = 0;
1397                             updateImeWindowStatusLocked();
1398                             return false;
1399                         }
1400                     } catch (RemoteException e) {
1401                         mImeWindowVis = 0;
1402                         updateImeWindowStatusLocked();
1403                         return false;
1404                     }
1405                 }
1406 
1407                 if (DEBUG) Slog.v(TAG, "Client requesting input be hidden");
1408                 return hideCurrentInputLocked(flags, resultReceiver);
1409             }
1410         } finally {
1411             Binder.restoreCallingIdentity(ident);
1412         }
1413     }
1414 
hideCurrentInputLocked(int flags, ResultReceiver resultReceiver)1415     boolean hideCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
1416         if ((flags&InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
1417                 && (mShowExplicitlyRequested || mShowForced)) {
1418             if (DEBUG) Slog.v(TAG,
1419                     "Not hiding: explicit show not cancelled by non-explicit hide");
1420             return false;
1421         }
1422         if (mShowForced && (flags&InputMethodManager.HIDE_NOT_ALWAYS) != 0) {
1423             if (DEBUG) Slog.v(TAG,
1424                     "Not hiding: forced show not cancelled by not-always hide");
1425             return false;
1426         }
1427         boolean res;
1428         if (mInputShown && mCurMethod != null) {
1429             executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
1430                     MSG_HIDE_SOFT_INPUT, mCurMethod, resultReceiver));
1431             res = true;
1432         } else {
1433             res = false;
1434         }
1435         mInputShown = false;
1436         mShowRequested = false;
1437         mShowExplicitlyRequested = false;
1438         mShowForced = false;
1439         return res;
1440     }
1441 
1442     @Override
windowGainedFocus(IInputMethodClient client, IBinder windowToken, boolean viewHasFocus, boolean isTextEditor, int softInputMode, boolean first, int windowFlags)1443     public void windowGainedFocus(IInputMethodClient client, IBinder windowToken,
1444             boolean viewHasFocus, boolean isTextEditor, int softInputMode,
1445             boolean first, int windowFlags) {
1446         long ident = Binder.clearCallingIdentity();
1447         try {
1448             synchronized (mMethodMap) {
1449                 if (DEBUG) Slog.v(TAG, "windowGainedFocus: " + client.asBinder()
1450                         + " viewHasFocus=" + viewHasFocus
1451                         + " isTextEditor=" + isTextEditor
1452                         + " softInputMode=#" + Integer.toHexString(softInputMode)
1453                         + " first=" + first + " flags=#"
1454                         + Integer.toHexString(windowFlags));
1455 
1456                 if (mCurClient == null || client == null
1457                         || mCurClient.client.asBinder() != client.asBinder()) {
1458                     try {
1459                         // We need to check if this is the current client with
1460                         // focus in the window manager, to allow this call to
1461                         // be made before input is started in it.
1462                         if (!mIWindowManager.inputMethodClientHasFocus(client)) {
1463                             Slog.w(TAG, "Client not active, ignoring focus gain of: " + client);
1464                             return;
1465                         }
1466                     } catch (RemoteException e) {
1467                     }
1468                 }
1469 
1470                 if (mCurFocusedWindow == windowToken) {
1471                     Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client);
1472                     return;
1473                 }
1474                 mCurFocusedWindow = windowToken;
1475 
1476                 // Should we auto-show the IME even if the caller has not
1477                 // specified what should be done with it?
1478                 // We only do this automatically if the window can resize
1479                 // to accommodate the IME (so what the user sees will give
1480                 // them good context without input information being obscured
1481                 // by the IME) or if running on a large screen where there
1482                 // is more room for the target window + IME.
1483                 final boolean doAutoShow =
1484                         (softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
1485                                 == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
1486                         || mRes.getConfiguration().isLayoutSizeAtLeast(
1487                                 Configuration.SCREENLAYOUT_SIZE_LARGE);
1488 
1489                 switch (softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) {
1490                     case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED:
1491                         if (!isTextEditor || !doAutoShow) {
1492                             if (WindowManager.LayoutParams.mayUseInputMethod(windowFlags)) {
1493                                 // There is no focus view, and this window will
1494                                 // be behind any soft input window, so hide the
1495                                 // soft input window if it is shown.
1496                                 if (DEBUG) Slog.v(TAG, "Unspecified window will hide input");
1497                                 hideCurrentInputLocked(InputMethodManager.HIDE_NOT_ALWAYS, null);
1498                             }
1499                         } else if (isTextEditor && doAutoShow && (softInputMode &
1500                                 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
1501                             // There is a focus view, and we are navigating forward
1502                             // into the window, so show the input window for the user.
1503                             // We only do this automatically if the window an resize
1504                             // to accomodate the IME (so what the user sees will give
1505                             // them good context without input information being obscured
1506                             // by the IME) or if running on a large screen where there
1507                             // is more room for the target window + IME.
1508                             if (DEBUG) Slog.v(TAG, "Unspecified window will show input");
1509                             showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
1510                         }
1511                         break;
1512                     case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED:
1513                         // Do nothing.
1514                         break;
1515                     case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN:
1516                         if ((softInputMode &
1517                                 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
1518                             if (DEBUG) Slog.v(TAG, "Window asks to hide input going forward");
1519                             hideCurrentInputLocked(0, null);
1520                         }
1521                         break;
1522                     case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN:
1523                         if (DEBUG) Slog.v(TAG, "Window asks to hide input");
1524                         hideCurrentInputLocked(0, null);
1525                         break;
1526                     case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE:
1527                         if ((softInputMode &
1528                                 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
1529                             if (DEBUG) Slog.v(TAG, "Window asks to show input going forward");
1530                             showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
1531                         }
1532                         break;
1533                     case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE:
1534                         if (DEBUG) Slog.v(TAG, "Window asks to always show input");
1535                         showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
1536                         break;
1537                 }
1538             }
1539         } finally {
1540             Binder.restoreCallingIdentity(ident);
1541         }
1542     }
1543 
1544     @Override
showInputMethodPickerFromClient(IInputMethodClient client)1545     public void showInputMethodPickerFromClient(IInputMethodClient client) {
1546         synchronized (mMethodMap) {
1547             if (mCurClient == null || client == null
1548                     || mCurClient.client.asBinder() != client.asBinder()) {
1549                 Slog.w(TAG, "Ignoring showInputMethodPickerFromClient of uid "
1550                         + Binder.getCallingUid() + ": " + client);
1551             }
1552 
1553             // Always call subtype picker, because subtype picker is a superset of input method
1554             // picker.
1555             mHandler.sendEmptyMessage(MSG_SHOW_IM_SUBTYPE_PICKER);
1556         }
1557     }
1558 
1559     @Override
setInputMethod(IBinder token, String id)1560     public void setInputMethod(IBinder token, String id) {
1561         setInputMethodWithSubtypeId(token, id, NOT_A_SUBTYPE_ID);
1562     }
1563 
1564     @Override
setInputMethodAndSubtype(IBinder token, String id, InputMethodSubtype subtype)1565     public void setInputMethodAndSubtype(IBinder token, String id, InputMethodSubtype subtype) {
1566         synchronized (mMethodMap) {
1567             if (subtype != null) {
1568                 setInputMethodWithSubtypeId(token, id, getSubtypeIdFromHashCode(
1569                         mMethodMap.get(id), subtype.hashCode()));
1570             } else {
1571                 setInputMethod(token, id);
1572             }
1573         }
1574     }
1575 
1576     @Override
showInputMethodAndSubtypeEnablerFromClient( IInputMethodClient client, String inputMethodId)1577     public void showInputMethodAndSubtypeEnablerFromClient(
1578             IInputMethodClient client, String inputMethodId) {
1579         synchronized (mMethodMap) {
1580             if (mCurClient == null || client == null
1581                 || mCurClient.client.asBinder() != client.asBinder()) {
1582                 Slog.w(TAG, "Ignoring showInputMethodAndSubtypeEnablerFromClient of: " + client);
1583             }
1584             executeOrSendMessage(mCurMethod, mCaller.obtainMessageO(
1585                     MSG_SHOW_IM_SUBTYPE_ENABLER, inputMethodId));
1586         }
1587     }
1588 
1589     @Override
switchToLastInputMethod(IBinder token)1590     public boolean switchToLastInputMethod(IBinder token) {
1591         synchronized (mMethodMap) {
1592             final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
1593             final InputMethodInfo lastImi;
1594             if (lastIme != null) {
1595                 lastImi = mMethodMap.get(lastIme.first);
1596             } else {
1597                 lastImi = null;
1598             }
1599             String targetLastImiId = null;
1600             int subtypeId = NOT_A_SUBTYPE_ID;
1601             if (lastIme != null && lastImi != null) {
1602                 final boolean imiIdIsSame = lastImi.getId().equals(mCurMethodId);
1603                 final int lastSubtypeHash = Integer.valueOf(lastIme.second);
1604                 final int currentSubtypeHash = mCurrentSubtype == null ? NOT_A_SUBTYPE_ID
1605                         : mCurrentSubtype.hashCode();
1606                 // If the last IME is the same as the current IME and the last subtype is not
1607                 // defined, there is no need to switch to the last IME.
1608                 if (!imiIdIsSame || lastSubtypeHash != currentSubtypeHash) {
1609                     targetLastImiId = lastIme.first;
1610                     subtypeId = getSubtypeIdFromHashCode(lastImi, lastSubtypeHash);
1611                 }
1612             }
1613 
1614             if (TextUtils.isEmpty(targetLastImiId) && !canAddToLastInputMethod(mCurrentSubtype)) {
1615                 // This is a safety net. If the currentSubtype can't be added to the history
1616                 // and the framework couldn't find the last ime, we will make the last ime be
1617                 // the most applicable enabled keyboard subtype of the system imes.
1618                 final List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
1619                 if (enabled != null) {
1620                     final int N = enabled.size();
1621                     final String locale = mCurrentSubtype == null
1622                             ? mRes.getConfiguration().locale.toString()
1623                             : mCurrentSubtype.getLocale();
1624                     for (int i = 0; i < N; ++i) {
1625                         final InputMethodInfo imi = enabled.get(i);
1626                         if (imi.getSubtypeCount() > 0 && isSystemIme(imi)) {
1627                             InputMethodSubtype keyboardSubtype =
1628                                     findLastResortApplicableSubtypeLocked(mRes, getSubtypes(imi),
1629                                             SUBTYPE_MODE_KEYBOARD, locale, true);
1630                             if (keyboardSubtype != null) {
1631                                 targetLastImiId = imi.getId();
1632                                 subtypeId = getSubtypeIdFromHashCode(
1633                                         imi, keyboardSubtype.hashCode());
1634                                 if(keyboardSubtype.getLocale().equals(locale)) {
1635                                     break;
1636                                 }
1637                             }
1638                         }
1639                     }
1640                 }
1641             }
1642 
1643             if (!TextUtils.isEmpty(targetLastImiId)) {
1644                 if (DEBUG) {
1645                     Slog.d(TAG, "Switch to: " + lastImi.getId() + ", " + lastIme.second
1646                             + ", from: " + mCurMethodId + ", " + subtypeId);
1647                 }
1648                 setInputMethodWithSubtypeId(token, targetLastImiId, subtypeId);
1649                 return true;
1650             } else {
1651                 return false;
1652             }
1653         }
1654     }
1655 
1656     @Override
getLastInputMethodSubtype()1657     public InputMethodSubtype getLastInputMethodSubtype() {
1658         synchronized (mMethodMap) {
1659             final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
1660             // TODO: Handle the case of the last IME with no subtypes
1661             if (lastIme == null || TextUtils.isEmpty(lastIme.first)
1662                     || TextUtils.isEmpty(lastIme.second)) return null;
1663             final InputMethodInfo lastImi = mMethodMap.get(lastIme.first);
1664             if (lastImi == null) return null;
1665             try {
1666                 final int lastSubtypeHash = Integer.valueOf(lastIme.second);
1667                 final int lastSubtypeId = getSubtypeIdFromHashCode(lastImi, lastSubtypeHash);
1668                 if (lastSubtypeId < 0 || lastSubtypeId >= lastImi.getSubtypeCount()) {
1669                     return null;
1670                 }
1671                 return lastImi.getSubtypeAt(lastSubtypeId);
1672             } catch (NumberFormatException e) {
1673                 return null;
1674             }
1675         }
1676     }
1677 
1678     @Override
setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes)1679     public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) {
1680         // By this IPC call, only a process which shares the same uid with the IME can add
1681         // additional input method subtypes to the IME.
1682         if (TextUtils.isEmpty(imiId) || subtypes == null || subtypes.length == 0) return;
1683         synchronized (mMethodMap) {
1684             final InputMethodInfo imi = mMethodMap.get(imiId);
1685             if (imi == null) return;
1686             final PackageManager pm = mContext.getPackageManager();
1687             final String[] packageInfos = pm.getPackagesForUid(Binder.getCallingUid());
1688             if (packageInfos != null) {
1689                 final int packageNum = packageInfos.length;
1690                 for (int i = 0; i < packageNum; ++i) {
1691                     if (packageInfos[i].equals(imi.getPackageName())) {
1692                         mFileManager.addInputMethodSubtypes(imi, subtypes);
1693                         final long ident = Binder.clearCallingIdentity();
1694                         try {
1695                             buildInputMethodListLocked(mMethodList, mMethodMap);
1696                         } finally {
1697                             Binder.restoreCallingIdentity(ident);
1698                         }
1699                         return;
1700                     }
1701                 }
1702             }
1703         }
1704         return;
1705     }
1706 
setInputMethodWithSubtypeId(IBinder token, String id, int subtypeId)1707     private void setInputMethodWithSubtypeId(IBinder token, String id, int subtypeId) {
1708         synchronized (mMethodMap) {
1709             if (token == null) {
1710                 if (mContext.checkCallingOrSelfPermission(
1711                         android.Manifest.permission.WRITE_SECURE_SETTINGS)
1712                         != PackageManager.PERMISSION_GRANTED) {
1713                     throw new SecurityException(
1714                             "Using null token requires permission "
1715                             + android.Manifest.permission.WRITE_SECURE_SETTINGS);
1716                 }
1717             } else if (mCurToken != token) {
1718                 Slog.w(TAG, "Ignoring setInputMethod of uid " + Binder.getCallingUid()
1719                         + " token: " + token);
1720                 return;
1721             }
1722 
1723             final long ident = Binder.clearCallingIdentity();
1724             try {
1725                 setInputMethodLocked(id, subtypeId);
1726             } finally {
1727                 Binder.restoreCallingIdentity(ident);
1728             }
1729         }
1730     }
1731 
1732     @Override
hideMySoftInput(IBinder token, int flags)1733     public void hideMySoftInput(IBinder token, int flags) {
1734         synchronized (mMethodMap) {
1735             if (token == null || mCurToken != token) {
1736                 if (DEBUG) Slog.w(TAG, "Ignoring hideInputMethod of uid "
1737                         + Binder.getCallingUid() + " token: " + token);
1738                 return;
1739             }
1740             long ident = Binder.clearCallingIdentity();
1741             try {
1742                 hideCurrentInputLocked(flags, null);
1743             } finally {
1744                 Binder.restoreCallingIdentity(ident);
1745             }
1746         }
1747     }
1748 
1749     @Override
showMySoftInput(IBinder token, int flags)1750     public void showMySoftInput(IBinder token, int flags) {
1751         synchronized (mMethodMap) {
1752             if (token == null || mCurToken != token) {
1753                 Slog.w(TAG, "Ignoring showMySoftInput of uid "
1754                         + Binder.getCallingUid() + " token: " + token);
1755                 return;
1756             }
1757             long ident = Binder.clearCallingIdentity();
1758             try {
1759                 showCurrentInputLocked(flags, null);
1760             } finally {
1761                 Binder.restoreCallingIdentity(ident);
1762             }
1763         }
1764     }
1765 
setEnabledSessionInMainThread(SessionState session)1766     void setEnabledSessionInMainThread(SessionState session) {
1767         if (mEnabledSession != session) {
1768             if (mEnabledSession != null) {
1769                 try {
1770                     if (DEBUG) Slog.v(TAG, "Disabling: " + mEnabledSession);
1771                     mEnabledSession.method.setSessionEnabled(
1772                             mEnabledSession.session, false);
1773                 } catch (RemoteException e) {
1774                 }
1775             }
1776             mEnabledSession = session;
1777             try {
1778                 if (DEBUG) Slog.v(TAG, "Enabling: " + mEnabledSession);
1779                 session.method.setSessionEnabled(
1780                         session.session, true);
1781             } catch (RemoteException e) {
1782             }
1783         }
1784     }
1785 
1786     @Override
handleMessage(Message msg)1787     public boolean handleMessage(Message msg) {
1788         HandlerCaller.SomeArgs args;
1789         switch (msg.what) {
1790             case MSG_SHOW_IM_PICKER:
1791                 showInputMethodMenu();
1792                 return true;
1793 
1794             case MSG_SHOW_IM_SUBTYPE_PICKER:
1795                 showInputMethodSubtypeMenu();
1796                 return true;
1797 
1798             case MSG_SHOW_IM_SUBTYPE_ENABLER:
1799                 args = (HandlerCaller.SomeArgs)msg.obj;
1800                 showInputMethodAndSubtypeEnabler((String)args.arg1);
1801                 return true;
1802 
1803             case MSG_SHOW_IM_CONFIG:
1804                 showConfigureInputMethods();
1805                 return true;
1806 
1807             // ---------------------------------------------------------
1808 
1809             case MSG_UNBIND_INPUT:
1810                 try {
1811                     ((IInputMethod)msg.obj).unbindInput();
1812                 } catch (RemoteException e) {
1813                     // There is nothing interesting about the method dying.
1814                 }
1815                 return true;
1816             case MSG_BIND_INPUT:
1817                 args = (HandlerCaller.SomeArgs)msg.obj;
1818                 try {
1819                     ((IInputMethod)args.arg1).bindInput((InputBinding)args.arg2);
1820                 } catch (RemoteException e) {
1821                 }
1822                 return true;
1823             case MSG_SHOW_SOFT_INPUT:
1824                 args = (HandlerCaller.SomeArgs)msg.obj;
1825                 try {
1826                     ((IInputMethod)args.arg1).showSoftInput(msg.arg1,
1827                             (ResultReceiver)args.arg2);
1828                 } catch (RemoteException e) {
1829                 }
1830                 return true;
1831             case MSG_HIDE_SOFT_INPUT:
1832                 args = (HandlerCaller.SomeArgs)msg.obj;
1833                 try {
1834                     ((IInputMethod)args.arg1).hideSoftInput(0,
1835                             (ResultReceiver)args.arg2);
1836                 } catch (RemoteException e) {
1837                 }
1838                 return true;
1839             case MSG_ATTACH_TOKEN:
1840                 args = (HandlerCaller.SomeArgs)msg.obj;
1841                 try {
1842                     if (DEBUG) Slog.v(TAG, "Sending attach of token: " + args.arg2);
1843                     ((IInputMethod)args.arg1).attachToken((IBinder)args.arg2);
1844                 } catch (RemoteException e) {
1845                 }
1846                 return true;
1847             case MSG_CREATE_SESSION:
1848                 args = (HandlerCaller.SomeArgs)msg.obj;
1849                 try {
1850                     ((IInputMethod)args.arg1).createSession(
1851                             (IInputMethodCallback)args.arg2);
1852                 } catch (RemoteException e) {
1853                 }
1854                 return true;
1855             // ---------------------------------------------------------
1856 
1857             case MSG_START_INPUT:
1858                 args = (HandlerCaller.SomeArgs)msg.obj;
1859                 try {
1860                     SessionState session = (SessionState)args.arg1;
1861                     setEnabledSessionInMainThread(session);
1862                     session.method.startInput((IInputContext)args.arg2,
1863                             (EditorInfo)args.arg3);
1864                 } catch (RemoteException e) {
1865                 }
1866                 return true;
1867             case MSG_RESTART_INPUT:
1868                 args = (HandlerCaller.SomeArgs)msg.obj;
1869                 try {
1870                     SessionState session = (SessionState)args.arg1;
1871                     setEnabledSessionInMainThread(session);
1872                     session.method.restartInput((IInputContext)args.arg2,
1873                             (EditorInfo)args.arg3);
1874                 } catch (RemoteException e) {
1875                 }
1876                 return true;
1877 
1878             // ---------------------------------------------------------
1879 
1880             case MSG_UNBIND_METHOD:
1881                 try {
1882                     ((IInputMethodClient)msg.obj).onUnbindMethod(msg.arg1);
1883                 } catch (RemoteException e) {
1884                     // There is nothing interesting about the last client dying.
1885                 }
1886                 return true;
1887             case MSG_BIND_METHOD:
1888                 args = (HandlerCaller.SomeArgs)msg.obj;
1889                 try {
1890                     ((IInputMethodClient)args.arg1).onBindMethod(
1891                             (InputBindResult)args.arg2);
1892                 } catch (RemoteException e) {
1893                     Slog.w(TAG, "Client died receiving input method " + args.arg2);
1894                 }
1895                 return true;
1896         }
1897         return false;
1898     }
1899 
isSystemIme(InputMethodInfo inputMethod)1900     private boolean isSystemIme(InputMethodInfo inputMethod) {
1901         return (inputMethod.getServiceInfo().applicationInfo.flags
1902                 & ApplicationInfo.FLAG_SYSTEM) != 0;
1903     }
1904 
getSubtypes(InputMethodInfo imi)1905     private static ArrayList<InputMethodSubtype> getSubtypes(InputMethodInfo imi) {
1906         ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
1907         final int subtypeCount = imi.getSubtypeCount();
1908         for (int i = 0; i < subtypeCount; ++i) {
1909             subtypes.add(imi.getSubtypeAt(i));
1910         }
1911         return subtypes;
1912     }
1913 
1914 
getOverridingImplicitlyEnabledSubtypes( InputMethodInfo imi, String mode)1915     private static ArrayList<InputMethodSubtype> getOverridingImplicitlyEnabledSubtypes(
1916             InputMethodInfo imi, String mode) {
1917         ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
1918         final int subtypeCount = imi.getSubtypeCount();
1919         for (int i = 0; i < subtypeCount; ++i) {
1920             final InputMethodSubtype subtype = imi.getSubtypeAt(i);
1921             if (subtype.overridesImplicitlyEnabledSubtype() && subtype.getMode().equals(mode)) {
1922                 subtypes.add(subtype);
1923             }
1924         }
1925         return subtypes;
1926     }
1927 
getMostApplicableDefaultIMELocked()1928     private InputMethodInfo getMostApplicableDefaultIMELocked() {
1929         List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
1930         if (enabled != null && enabled.size() > 0) {
1931             // We'd prefer to fall back on a system IME, since that is safer.
1932             int i=enabled.size();
1933             while (i > 0) {
1934                 i--;
1935                 final InputMethodInfo imi = enabled.get(i);
1936                 if (isSystemIme(imi) && !imi.isAuxiliaryIme()) {
1937                     break;
1938                 }
1939             }
1940             return enabled.get(i);
1941         }
1942         return null;
1943     }
1944 
chooseNewDefaultIMELocked()1945     private boolean chooseNewDefaultIMELocked() {
1946         final InputMethodInfo imi = getMostApplicableDefaultIMELocked();
1947         if (imi != null) {
1948             if (DEBUG) {
1949                 Slog.d(TAG, "New default IME was selected: " + imi.getId());
1950             }
1951             resetSelectedInputMethodAndSubtypeLocked(imi.getId());
1952             return true;
1953         }
1954 
1955         return false;
1956     }
1957 
buildInputMethodListLocked(ArrayList<InputMethodInfo> list, HashMap<String, InputMethodInfo> map)1958     void buildInputMethodListLocked(ArrayList<InputMethodInfo> list,
1959             HashMap<String, InputMethodInfo> map) {
1960         list.clear();
1961         map.clear();
1962 
1963         PackageManager pm = mContext.getPackageManager();
1964         final Configuration config = mRes.getConfiguration();
1965         final boolean haveHardKeyboard = config.keyboard == Configuration.KEYBOARD_QWERTY;
1966         String disabledSysImes = Settings.Secure.getString(mContext.getContentResolver(),
1967                 Secure.DISABLED_SYSTEM_INPUT_METHODS);
1968         if (disabledSysImes == null) disabledSysImes = "";
1969 
1970         List<ResolveInfo> services = pm.queryIntentServices(
1971                 new Intent(InputMethod.SERVICE_INTERFACE),
1972                 PackageManager.GET_META_DATA);
1973 
1974         final HashMap<String, List<InputMethodSubtype>> additionalSubtypes =
1975                 mFileManager.getAllAdditionalInputMethodSubtypes();
1976         for (int i = 0; i < services.size(); ++i) {
1977             ResolveInfo ri = services.get(i);
1978             ServiceInfo si = ri.serviceInfo;
1979             ComponentName compName = new ComponentName(si.packageName, si.name);
1980             if (!android.Manifest.permission.BIND_INPUT_METHOD.equals(
1981                     si.permission)) {
1982                 Slog.w(TAG, "Skipping input method " + compName
1983                         + ": it does not require the permission "
1984                         + android.Manifest.permission.BIND_INPUT_METHOD);
1985                 continue;
1986             }
1987 
1988             if (DEBUG) Slog.d(TAG, "Checking " + compName);
1989 
1990             try {
1991                 InputMethodInfo p = new InputMethodInfo(mContext, ri, additionalSubtypes);
1992                 list.add(p);
1993                 final String id = p.getId();
1994                 map.put(id, p);
1995 
1996                 // System IMEs are enabled by default, unless there's a hard keyboard
1997                 // and the system IME was explicitly disabled
1998                 if (isSystemIme(p) && (!haveHardKeyboard || disabledSysImes.indexOf(id) < 0)) {
1999                     setInputMethodEnabledLocked(id, true);
2000                 }
2001 
2002                 if (DEBUG) {
2003                     Slog.d(TAG, "Found a third-party input method " + p);
2004                 }
2005 
2006             } catch (XmlPullParserException e) {
2007                 Slog.w(TAG, "Unable to load input method " + compName, e);
2008             } catch (IOException e) {
2009                 Slog.w(TAG, "Unable to load input method " + compName, e);
2010             }
2011         }
2012 
2013         String defaultIme = Settings.Secure.getString(mContext
2014                 .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
2015         if (!TextUtils.isEmpty(defaultIme) && !map.containsKey(defaultIme)) {
2016             if (chooseNewDefaultIMELocked()) {
2017                 updateFromSettingsLocked();
2018             }
2019         }
2020     }
2021 
2022     // ----------------------------------------------------------------------
2023 
showInputMethodMenu()2024     private void showInputMethodMenu() {
2025         showInputMethodMenuInternal(false);
2026     }
2027 
showInputMethodSubtypeMenu()2028     private void showInputMethodSubtypeMenu() {
2029         showInputMethodMenuInternal(true);
2030     }
2031 
showInputMethodAndSubtypeEnabler(String inputMethodId)2032     private void showInputMethodAndSubtypeEnabler(String inputMethodId) {
2033         Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS);
2034         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
2035                 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
2036                 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
2037         if (!TextUtils.isEmpty(inputMethodId)) {
2038             intent.putExtra(Settings.EXTRA_INPUT_METHOD_ID, inputMethodId);
2039         }
2040         mContext.startActivity(intent);
2041     }
2042 
showConfigureInputMethods()2043     private void showConfigureInputMethods() {
2044         Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS);
2045         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
2046                 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
2047                 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
2048         mContext.startActivity(intent);
2049     }
2050 
showInputMethodMenuInternal(boolean showSubtypes)2051     private void showInputMethodMenuInternal(boolean showSubtypes) {
2052         if (DEBUG) Slog.v(TAG, "Show switching menu");
2053 
2054         final Context context = mContext;
2055         final PackageManager pm = context.getPackageManager();
2056         final boolean isScreenLocked = mKeyguardManager != null
2057                 && mKeyguardManager.isKeyguardLocked() && mKeyguardManager.isKeyguardSecure();
2058 
2059         String lastInputMethodId = Settings.Secure.getString(context
2060                 .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
2061         int lastInputMethodSubtypeId = getSelectedInputMethodSubtypeId(lastInputMethodId);
2062         if (DEBUG) Slog.v(TAG, "Current IME: " + lastInputMethodId);
2063 
2064         synchronized (mMethodMap) {
2065             final HashMap<InputMethodInfo, List<InputMethodSubtype>> immis =
2066                     getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked();
2067             if (immis == null || immis.size() == 0) {
2068                 return;
2069             }
2070 
2071             hideInputMethodMenuLocked();
2072 
2073             final TreeMap<InputMethodInfo, List<InputMethodSubtype>> sortedImmis =
2074                     new TreeMap<InputMethodInfo, List<InputMethodSubtype>>(
2075                             new Comparator<InputMethodInfo>() {
2076                                 @Override
2077                                 public int compare(InputMethodInfo imi1, InputMethodInfo imi2) {
2078                                     if (imi2 == null) return 0;
2079                                     if (imi1 == null) return 1;
2080                                     if (pm == null) {
2081                                         return imi1.getId().compareTo(imi2.getId());
2082                                     }
2083                                     CharSequence imiId1 = imi1.loadLabel(pm) + "/" + imi1.getId();
2084                                     CharSequence imiId2 = imi2.loadLabel(pm) + "/" + imi2.getId();
2085                                     return imiId1.toString().compareTo(imiId2.toString());
2086                                 }
2087                             });
2088 
2089             sortedImmis.putAll(immis);
2090 
2091             final ArrayList<ImeSubtypeListItem> imList = new ArrayList<ImeSubtypeListItem>();
2092 
2093             for (InputMethodInfo imi : sortedImmis.keySet()) {
2094                 if (imi == null) continue;
2095                 List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypeList = immis.get(imi);
2096                 HashSet<String> enabledSubtypeSet = new HashSet<String>();
2097                 for (InputMethodSubtype subtype: explicitlyOrImplicitlyEnabledSubtypeList) {
2098                     enabledSubtypeSet.add(String.valueOf(subtype.hashCode()));
2099                 }
2100                 ArrayList<InputMethodSubtype> subtypes = getSubtypes(imi);
2101                 final CharSequence imeLabel = imi.loadLabel(pm);
2102                 if (showSubtypes && enabledSubtypeSet.size() > 0) {
2103                     final int subtypeCount = imi.getSubtypeCount();
2104                     if (DEBUG) {
2105                         Slog.v(TAG, "Add subtypes: " + subtypeCount + ", " + imi.getId());
2106                     }
2107                     for (int j = 0; j < subtypeCount; ++j) {
2108                         final InputMethodSubtype subtype = imi.getSubtypeAt(j);
2109                         final String subtypeHashCode = String.valueOf(subtype.hashCode());
2110                         // We show all enabled IMEs and subtypes when an IME is shown.
2111                         if (enabledSubtypeSet.contains(subtypeHashCode)
2112                                 && ((mInputShown && !isScreenLocked) || !subtype.isAuxiliary())) {
2113                             final CharSequence subtypeLabel =
2114                                     subtype.overridesImplicitlyEnabledSubtype() ? null
2115                                             : subtype.getDisplayName(context, imi.getPackageName(),
2116                                                     imi.getServiceInfo().applicationInfo);
2117                             imList.add(new ImeSubtypeListItem(imeLabel, subtypeLabel, imi, j));
2118 
2119                             // Removing this subtype from enabledSubtypeSet because we no longer
2120                             // need to add an entry of this subtype to imList to avoid duplicated
2121                             // entries.
2122                             enabledSubtypeSet.remove(subtypeHashCode);
2123                         }
2124                     }
2125                 } else {
2126                     imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_ID));
2127                 }
2128             }
2129 
2130             final int N = imList.size();
2131             mIms = new InputMethodInfo[N];
2132             mSubtypeIds = new int[N];
2133             int checkedItem = 0;
2134             for (int i = 0; i < N; ++i) {
2135                 final ImeSubtypeListItem item = imList.get(i);
2136                 mIms[i] = item.mImi;
2137                 mSubtypeIds[i] = item.mSubtypeId;
2138                 if (mIms[i].getId().equals(lastInputMethodId)) {
2139                     int subtypeId = mSubtypeIds[i];
2140                     if ((subtypeId == NOT_A_SUBTYPE_ID)
2141                             || (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID && subtypeId == 0)
2142                             || (subtypeId == lastInputMethodSubtypeId)) {
2143                         checkedItem = i;
2144                     }
2145                 }
2146             }
2147 
2148             final TypedArray a = context.obtainStyledAttributes(null,
2149                     com.android.internal.R.styleable.DialogPreference,
2150                     com.android.internal.R.attr.alertDialogStyle, 0);
2151             mDialogBuilder = new AlertDialog.Builder(context)
2152                     .setTitle(com.android.internal.R.string.select_input_method)
2153                     .setOnCancelListener(new OnCancelListener() {
2154                         @Override
2155                         public void onCancel(DialogInterface dialog) {
2156                             hideInputMethodMenu();
2157                         }
2158                     })
2159                     .setIcon(a.getDrawable(
2160                             com.android.internal.R.styleable.DialogPreference_dialogTitle));
2161             a.recycle();
2162 
2163             final ImeSubtypeListAdapter adapter = new ImeSubtypeListAdapter(context,
2164                     com.android.internal.R.layout.simple_list_item_2_single_choice, imList,
2165                     checkedItem);
2166 
2167             mDialogBuilder.setSingleChoiceItems(adapter, checkedItem,
2168                     new AlertDialog.OnClickListener() {
2169                         @Override
2170                         public void onClick(DialogInterface dialog, int which) {
2171                             synchronized (mMethodMap) {
2172                                 if (mIms == null || mIms.length <= which
2173                                         || mSubtypeIds == null || mSubtypeIds.length <= which) {
2174                                     return;
2175                                 }
2176                                 InputMethodInfo im = mIms[which];
2177                                 int subtypeId = mSubtypeIds[which];
2178                                 hideInputMethodMenu();
2179                                 if (im != null) {
2180                                     if ((subtypeId < 0)
2181                                             || (subtypeId >= im.getSubtypeCount())) {
2182                                         subtypeId = NOT_A_SUBTYPE_ID;
2183                                     }
2184                                     setInputMethodLocked(im.getId(), subtypeId);
2185                                 }
2186                             }
2187                         }
2188                     });
2189 
2190             if (showSubtypes && !isScreenLocked) {
2191                 mDialogBuilder.setPositiveButton(
2192                         com.android.internal.R.string.configure_input_methods,
2193                         new DialogInterface.OnClickListener() {
2194                             @Override
2195                             public void onClick(DialogInterface dialog, int whichButton) {
2196                                 showConfigureInputMethods();
2197                             }
2198                         });
2199             }
2200             mSwitchingDialog = mDialogBuilder.create();
2201             mSwitchingDialog.setCanceledOnTouchOutside(true);
2202             mSwitchingDialog.getWindow().setType(
2203                     WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG);
2204             mSwitchingDialog.getWindow().getAttributes().setTitle("Select input method");
2205             mSwitchingDialog.show();
2206         }
2207     }
2208 
2209     private static class ImeSubtypeListItem {
2210         public final CharSequence mImeName;
2211         public final CharSequence mSubtypeName;
2212         public final InputMethodInfo mImi;
2213         public final int mSubtypeId;
ImeSubtypeListItem(CharSequence imeName, CharSequence subtypeName, InputMethodInfo imi, int subtypeId)2214         public ImeSubtypeListItem(CharSequence imeName, CharSequence subtypeName,
2215                 InputMethodInfo imi, int subtypeId) {
2216             mImeName = imeName;
2217             mSubtypeName = subtypeName;
2218             mImi = imi;
2219             mSubtypeId = subtypeId;
2220         }
2221     }
2222 
2223     private static class ImeSubtypeListAdapter extends ArrayAdapter<ImeSubtypeListItem> {
2224         private final LayoutInflater mInflater;
2225         private final int mTextViewResourceId;
2226         private final List<ImeSubtypeListItem> mItemsList;
2227         private final int mCheckedItem;
ImeSubtypeListAdapter(Context context, int textViewResourceId, List<ImeSubtypeListItem> itemsList, int checkedItem)2228         public ImeSubtypeListAdapter(Context context, int textViewResourceId,
2229                 List<ImeSubtypeListItem> itemsList, int checkedItem) {
2230             super(context, textViewResourceId, itemsList);
2231             mTextViewResourceId = textViewResourceId;
2232             mItemsList = itemsList;
2233             mCheckedItem = checkedItem;
2234             mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
2235         }
2236 
2237         @Override
getView(int position, View convertView, ViewGroup parent)2238         public View getView(int position, View convertView, ViewGroup parent) {
2239             final View view = convertView != null ? convertView
2240                     : mInflater.inflate(mTextViewResourceId, null);
2241             if (position < 0 || position >= mItemsList.size()) return view;
2242             final ImeSubtypeListItem item = mItemsList.get(position);
2243             final CharSequence imeName = item.mImeName;
2244             final CharSequence subtypeName = item.mSubtypeName;
2245             final TextView firstTextView = (TextView)view.findViewById(android.R.id.text1);
2246             final TextView secondTextView = (TextView)view.findViewById(android.R.id.text2);
2247             if (TextUtils.isEmpty(subtypeName)) {
2248                 firstTextView.setText(imeName);
2249                 secondTextView.setVisibility(View.GONE);
2250             } else {
2251                 firstTextView.setText(subtypeName);
2252                 secondTextView.setText(imeName);
2253                 secondTextView.setVisibility(View.VISIBLE);
2254             }
2255             final RadioButton radioButton =
2256                     (RadioButton)view.findViewById(com.android.internal.R.id.radio);
2257             radioButton.setChecked(position == mCheckedItem);
2258             return view;
2259         }
2260     }
2261 
hideInputMethodMenu()2262     void hideInputMethodMenu() {
2263         synchronized (mMethodMap) {
2264             hideInputMethodMenuLocked();
2265         }
2266     }
2267 
hideInputMethodMenuLocked()2268     void hideInputMethodMenuLocked() {
2269         if (DEBUG) Slog.v(TAG, "Hide switching menu");
2270 
2271         if (mSwitchingDialog != null) {
2272             mSwitchingDialog.dismiss();
2273             mSwitchingDialog = null;
2274         }
2275 
2276         mDialogBuilder = null;
2277         mIms = null;
2278     }
2279 
2280     // ----------------------------------------------------------------------
2281 
2282     @Override
setInputMethodEnabled(String id, boolean enabled)2283     public boolean setInputMethodEnabled(String id, boolean enabled) {
2284         synchronized (mMethodMap) {
2285             if (mContext.checkCallingOrSelfPermission(
2286                     android.Manifest.permission.WRITE_SECURE_SETTINGS)
2287                     != PackageManager.PERMISSION_GRANTED) {
2288                 throw new SecurityException(
2289                         "Requires permission "
2290                         + android.Manifest.permission.WRITE_SECURE_SETTINGS);
2291             }
2292 
2293             long ident = Binder.clearCallingIdentity();
2294             try {
2295                 return setInputMethodEnabledLocked(id, enabled);
2296             } finally {
2297                 Binder.restoreCallingIdentity(ident);
2298             }
2299         }
2300     }
2301 
setInputMethodEnabledLocked(String id, boolean enabled)2302     boolean setInputMethodEnabledLocked(String id, boolean enabled) {
2303         // Make sure this is a valid input method.
2304         InputMethodInfo imm = mMethodMap.get(id);
2305         if (imm == null) {
2306             throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
2307         }
2308 
2309         List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings
2310                 .getEnabledInputMethodsAndSubtypeListLocked();
2311 
2312         if (enabled) {
2313             for (Pair<String, ArrayList<String>> pair: enabledInputMethodsList) {
2314                 if (pair.first.equals(id)) {
2315                     // We are enabling this input method, but it is already enabled.
2316                     // Nothing to do. The previous state was enabled.
2317                     return true;
2318                 }
2319             }
2320             mSettings.appendAndPutEnabledInputMethodLocked(id, false);
2321             // Previous state was disabled.
2322             return false;
2323         } else {
2324             StringBuilder builder = new StringBuilder();
2325             if (mSettings.buildAndPutEnabledInputMethodsStrRemovingIdLocked(
2326                     builder, enabledInputMethodsList, id)) {
2327                 // Disabled input method is currently selected, switch to another one.
2328                 String selId = Settings.Secure.getString(mContext.getContentResolver(),
2329                         Settings.Secure.DEFAULT_INPUT_METHOD);
2330                 if (id.equals(selId) && !chooseNewDefaultIMELocked()) {
2331                     Slog.i(TAG, "Can't find new IME, unsetting the current input method.");
2332                     resetSelectedInputMethodAndSubtypeLocked("");
2333                 }
2334                 // Previous state was enabled.
2335                 return true;
2336             } else {
2337                 // We are disabling the input method but it is already disabled.
2338                 // Nothing to do.  The previous state was disabled.
2339                 return false;
2340             }
2341         }
2342     }
2343 
canAddToLastInputMethod(InputMethodSubtype subtype)2344     private boolean canAddToLastInputMethod(InputMethodSubtype subtype) {
2345         if (subtype == null) return true;
2346         return !subtype.isAuxiliary();
2347     }
2348 
saveCurrentInputMethodAndSubtypeToHistory()2349     private void saveCurrentInputMethodAndSubtypeToHistory() {
2350         String subtypeId = NOT_A_SUBTYPE_ID_STR;
2351         if (mCurrentSubtype != null) {
2352             subtypeId = String.valueOf(mCurrentSubtype.hashCode());
2353         }
2354         if (canAddToLastInputMethod(mCurrentSubtype)) {
2355             mSettings.addSubtypeToHistory(mCurMethodId, subtypeId);
2356         }
2357     }
2358 
setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId, boolean setSubtypeOnly)2359     private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId,
2360             boolean setSubtypeOnly) {
2361         // Update the history of InputMethod and Subtype
2362         saveCurrentInputMethodAndSubtypeToHistory();
2363 
2364         // Set Subtype here
2365         if (imi == null || subtypeId < 0) {
2366             mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
2367             mCurrentSubtype = null;
2368         } else {
2369             if (subtypeId < imi.getSubtypeCount()) {
2370                 InputMethodSubtype subtype = imi.getSubtypeAt(subtypeId);
2371                 mSettings.putSelectedSubtype(subtype.hashCode());
2372                 mCurrentSubtype = subtype;
2373             } else {
2374                 mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
2375                 mCurrentSubtype = null;
2376             }
2377         }
2378 
2379         if (!setSubtypeOnly) {
2380             // Set InputMethod here
2381             mSettings.putSelectedInputMethod(imi != null ? imi.getId() : "");
2382         }
2383     }
2384 
resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme)2385     private void resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme) {
2386         InputMethodInfo imi = mMethodMap.get(newDefaultIme);
2387         int lastSubtypeId = NOT_A_SUBTYPE_ID;
2388         // newDefaultIme is empty when there is no candidate for the selected IME.
2389         if (imi != null && !TextUtils.isEmpty(newDefaultIme)) {
2390             String subtypeHashCode = mSettings.getLastSubtypeForInputMethodLocked(newDefaultIme);
2391             if (subtypeHashCode != null) {
2392                 try {
2393                     lastSubtypeId = getSubtypeIdFromHashCode(
2394                             imi, Integer.valueOf(subtypeHashCode));
2395                 } catch (NumberFormatException e) {
2396                     Slog.w(TAG, "HashCode for subtype looks broken: " + subtypeHashCode, e);
2397                 }
2398             }
2399         }
2400         setSelectedInputMethodAndSubtypeLocked(imi, lastSubtypeId, false);
2401     }
2402 
getSelectedInputMethodSubtypeId(String id)2403     private int getSelectedInputMethodSubtypeId(String id) {
2404         InputMethodInfo imi = mMethodMap.get(id);
2405         if (imi == null) {
2406             return NOT_A_SUBTYPE_ID;
2407         }
2408         int subtypeId;
2409         try {
2410             subtypeId = Settings.Secure.getInt(mContext.getContentResolver(),
2411                     Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE);
2412         } catch (SettingNotFoundException e) {
2413             return NOT_A_SUBTYPE_ID;
2414         }
2415         return getSubtypeIdFromHashCode(imi, subtypeId);
2416     }
2417 
getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode)2418     private int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) {
2419         if (imi != null) {
2420             final int subtypeCount = imi.getSubtypeCount();
2421             for (int i = 0; i < subtypeCount; ++i) {
2422                 InputMethodSubtype ims = imi.getSubtypeAt(i);
2423                 if (subtypeHashCode == ims.hashCode()) {
2424                     return i;
2425                 }
2426             }
2427         }
2428         return NOT_A_SUBTYPE_ID;
2429     }
2430 
getImplicitlyApplicableSubtypesLocked( Resources res, InputMethodInfo imi)2431     private static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLocked(
2432             Resources res, InputMethodInfo imi) {
2433         final List<InputMethodSubtype> subtypes = getSubtypes(imi);
2434         final String systemLocale = res.getConfiguration().locale.toString();
2435         if (TextUtils.isEmpty(systemLocale)) return new ArrayList<InputMethodSubtype>();
2436         final HashMap<String, InputMethodSubtype> applicableModeAndSubtypesMap =
2437                 new HashMap<String, InputMethodSubtype>();
2438         final int N = subtypes.size();
2439         boolean containsKeyboardSubtype = false;
2440         for (int i = 0; i < N; ++i) {
2441             // scan overriding implicitly enabled subtypes.
2442             InputMethodSubtype subtype = subtypes.get(i);
2443             if (subtype.overridesImplicitlyEnabledSubtype()) {
2444                 final String mode = subtype.getMode();
2445                 if (!applicableModeAndSubtypesMap.containsKey(mode)) {
2446                     applicableModeAndSubtypesMap.put(mode, subtype);
2447                 }
2448             }
2449         }
2450         if (applicableModeAndSubtypesMap.size() > 0) {
2451             return new ArrayList<InputMethodSubtype>(applicableModeAndSubtypesMap.values());
2452         }
2453         for (int i = 0; i < N; ++i) {
2454             final InputMethodSubtype subtype = subtypes.get(i);
2455             final String locale = subtype.getLocale();
2456             final String mode = subtype.getMode();
2457             // When system locale starts with subtype's locale, that subtype will be applicable
2458             // for system locale
2459             // For instance, it's clearly applicable for cases like system locale = en_US and
2460             // subtype = en, but it is not necessarily considered applicable for cases like system
2461             // locale = en and subtype = en_US.
2462             // We just call systemLocale.startsWith(locale) in this function because there is no
2463             // need to find applicable subtypes aggressively unlike
2464             // findLastResortApplicableSubtypeLocked.
2465             if (systemLocale.startsWith(locale)) {
2466                 final InputMethodSubtype applicableSubtype = applicableModeAndSubtypesMap.get(mode);
2467                 // If more applicable subtypes are contained, skip.
2468                 if (applicableSubtype != null) {
2469                     if (systemLocale.equals(applicableSubtype.getLocale())) continue;
2470                     if (!systemLocale.equals(locale)) continue;
2471                 }
2472                 applicableModeAndSubtypesMap.put(mode, subtype);
2473                 if (!containsKeyboardSubtype
2474                         && SUBTYPE_MODE_KEYBOARD.equalsIgnoreCase(subtype.getMode())) {
2475                     containsKeyboardSubtype = true;
2476                 }
2477             }
2478         }
2479         final ArrayList<InputMethodSubtype> applicableSubtypes = new ArrayList<InputMethodSubtype>(
2480                 applicableModeAndSubtypesMap.values());
2481         if (!containsKeyboardSubtype) {
2482             InputMethodSubtype lastResortKeyboardSubtype = findLastResortApplicableSubtypeLocked(
2483                     res, subtypes, SUBTYPE_MODE_KEYBOARD, systemLocale, true);
2484             if (lastResortKeyboardSubtype != null) {
2485                 applicableSubtypes.add(lastResortKeyboardSubtype);
2486             }
2487         }
2488         return applicableSubtypes;
2489     }
2490 
2491     /**
2492      * If there are no selected subtypes, tries finding the most applicable one according to the
2493      * given locale.
2494      * @param subtypes this function will search the most applicable subtype in subtypes
2495      * @param mode subtypes will be filtered by mode
2496      * @param locale subtypes will be filtered by locale
2497      * @param canIgnoreLocaleAsLastResort if this function can't find the most applicable subtype,
2498      * it will return the first subtype matched with mode
2499      * @return the most applicable subtypeId
2500      */
findLastResortApplicableSubtypeLocked( Resources res, List<InputMethodSubtype> subtypes, String mode, String locale, boolean canIgnoreLocaleAsLastResort)2501     private static InputMethodSubtype findLastResortApplicableSubtypeLocked(
2502             Resources res, List<InputMethodSubtype> subtypes, String mode, String locale,
2503             boolean canIgnoreLocaleAsLastResort) {
2504         if (subtypes == null || subtypes.size() == 0) {
2505             return null;
2506         }
2507         if (TextUtils.isEmpty(locale)) {
2508             locale = res.getConfiguration().locale.toString();
2509         }
2510         final String language = locale.substring(0, 2);
2511         boolean partialMatchFound = false;
2512         InputMethodSubtype applicableSubtype = null;
2513         InputMethodSubtype firstMatchedModeSubtype = null;
2514         final int N = subtypes.size();
2515         for (int i = 0; i < N; ++i) {
2516             InputMethodSubtype subtype = subtypes.get(i);
2517             final String subtypeLocale = subtype.getLocale();
2518             // An applicable subtype should match "mode". If mode is null, mode will be ignored,
2519             // and all subtypes with all modes can be candidates.
2520             if (mode == null || subtypes.get(i).getMode().equalsIgnoreCase(mode)) {
2521                 if (firstMatchedModeSubtype == null) {
2522                     firstMatchedModeSubtype = subtype;
2523                 }
2524                 if (locale.equals(subtypeLocale)) {
2525                     // Exact match (e.g. system locale is "en_US" and subtype locale is "en_US")
2526                     applicableSubtype = subtype;
2527                     break;
2528                 } else if (!partialMatchFound && subtypeLocale.startsWith(language)) {
2529                     // Partial match (e.g. system locale is "en_US" and subtype locale is "en")
2530                     applicableSubtype = subtype;
2531                     partialMatchFound = true;
2532                 }
2533             }
2534         }
2535 
2536         if (applicableSubtype == null && canIgnoreLocaleAsLastResort) {
2537             return firstMatchedModeSubtype;
2538         }
2539 
2540         // The first subtype applicable to the system locale will be defined as the most applicable
2541         // subtype.
2542         if (DEBUG) {
2543             if (applicableSubtype != null) {
2544                 Slog.d(TAG, "Applicable InputMethodSubtype was found: "
2545                         + applicableSubtype.getMode() + "," + applicableSubtype.getLocale());
2546             }
2547         }
2548         return applicableSubtype;
2549     }
2550 
2551     // If there are no selected shortcuts, tries finding the most applicable ones.
2552     private Pair<InputMethodInfo, InputMethodSubtype>
findLastResortApplicableShortcutInputMethodAndSubtypeLocked(String mode)2553             findLastResortApplicableShortcutInputMethodAndSubtypeLocked(String mode) {
2554         List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked();
2555         InputMethodInfo mostApplicableIMI = null;
2556         InputMethodSubtype mostApplicableSubtype = null;
2557         boolean foundInSystemIME = false;
2558 
2559         // Search applicable subtype for each InputMethodInfo
2560         for (InputMethodInfo imi: imis) {
2561             final String imiId = imi.getId();
2562             if (foundInSystemIME && !imiId.equals(mCurMethodId)) {
2563                 continue;
2564             }
2565             InputMethodSubtype subtype = null;
2566             final List<InputMethodSubtype> enabledSubtypes =
2567                     getEnabledInputMethodSubtypeList(imi, true);
2568             // 1. Search by the current subtype's locale from enabledSubtypes.
2569             if (mCurrentSubtype != null) {
2570                 subtype = findLastResortApplicableSubtypeLocked(
2571                         mRes, enabledSubtypes, mode, mCurrentSubtype.getLocale(), false);
2572             }
2573             // 2. Search by the system locale from enabledSubtypes.
2574             // 3. Search the first enabled subtype matched with mode from enabledSubtypes.
2575             if (subtype == null) {
2576                 subtype = findLastResortApplicableSubtypeLocked(
2577                         mRes, enabledSubtypes, mode, null, true);
2578             }
2579             final ArrayList<InputMethodSubtype> overridingImplicitlyEnabledSubtypes =
2580                     getOverridingImplicitlyEnabledSubtypes(imi, mode);
2581             final ArrayList<InputMethodSubtype> subtypesForSearch =
2582                     overridingImplicitlyEnabledSubtypes.isEmpty()
2583                             ? getSubtypes(imi) : overridingImplicitlyEnabledSubtypes;
2584             // 4. Search by the current subtype's locale from all subtypes.
2585             if (subtype == null && mCurrentSubtype != null) {
2586                 subtype = findLastResortApplicableSubtypeLocked(
2587                         mRes, subtypesForSearch, mode, mCurrentSubtype.getLocale(), false);
2588             }
2589             // 5. Search by the system locale from all subtypes.
2590             // 6. Search the first enabled subtype matched with mode from all subtypes.
2591             if (subtype == null) {
2592                 subtype = findLastResortApplicableSubtypeLocked(
2593                         mRes, subtypesForSearch, mode, null, true);
2594             }
2595             if (subtype != null) {
2596                 if (imiId.equals(mCurMethodId)) {
2597                     // The current input method is the most applicable IME.
2598                     mostApplicableIMI = imi;
2599                     mostApplicableSubtype = subtype;
2600                     break;
2601                 } else if (!foundInSystemIME) {
2602                     // The system input method is 2nd applicable IME.
2603                     mostApplicableIMI = imi;
2604                     mostApplicableSubtype = subtype;
2605                     if ((imi.getServiceInfo().applicationInfo.flags
2606                             & ApplicationInfo.FLAG_SYSTEM) != 0) {
2607                         foundInSystemIME = true;
2608                     }
2609                 }
2610             }
2611         }
2612         if (DEBUG) {
2613             if (mostApplicableIMI != null) {
2614                 Slog.w(TAG, "Most applicable shortcut input method was:"
2615                         + mostApplicableIMI.getId());
2616                 if (mostApplicableSubtype != null) {
2617                     Slog.w(TAG, "Most applicable shortcut input method subtype was:"
2618                             + "," + mostApplicableSubtype.getMode() + ","
2619                             + mostApplicableSubtype.getLocale());
2620                 }
2621             }
2622         }
2623         if (mostApplicableIMI != null) {
2624             return new Pair<InputMethodInfo, InputMethodSubtype> (mostApplicableIMI,
2625                     mostApplicableSubtype);
2626         } else {
2627             return null;
2628         }
2629     }
2630 
2631     /**
2632      * @return Return the current subtype of this input method.
2633      */
2634     @Override
getCurrentInputMethodSubtype()2635     public InputMethodSubtype getCurrentInputMethodSubtype() {
2636         boolean subtypeIsSelected = false;
2637         try {
2638             subtypeIsSelected = Settings.Secure.getInt(mContext.getContentResolver(),
2639                     Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE) != NOT_A_SUBTYPE_ID;
2640         } catch (SettingNotFoundException e) {
2641         }
2642         synchronized (mMethodMap) {
2643             if (!subtypeIsSelected || mCurrentSubtype == null) {
2644                 String lastInputMethodId = Settings.Secure.getString(
2645                         mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
2646                 int subtypeId = getSelectedInputMethodSubtypeId(lastInputMethodId);
2647                 if (subtypeId == NOT_A_SUBTYPE_ID) {
2648                     InputMethodInfo imi = mMethodMap.get(lastInputMethodId);
2649                     if (imi != null) {
2650                         // If there are no selected subtypes, the framework will try to find
2651                         // the most applicable subtype from explicitly or implicitly enabled
2652                         // subtypes.
2653                         List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes =
2654                                 getEnabledInputMethodSubtypeList(imi, true);
2655                         // If there is only one explicitly or implicitly enabled subtype,
2656                         // just returns it.
2657                         if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
2658                             mCurrentSubtype = explicitlyOrImplicitlyEnabledSubtypes.get(0);
2659                         } else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) {
2660                             mCurrentSubtype = findLastResortApplicableSubtypeLocked(
2661                                     mRes, explicitlyOrImplicitlyEnabledSubtypes,
2662                                     SUBTYPE_MODE_KEYBOARD, null, true);
2663                             if (mCurrentSubtype == null) {
2664                                 mCurrentSubtype = findLastResortApplicableSubtypeLocked(
2665                                         mRes, explicitlyOrImplicitlyEnabledSubtypes, null, null,
2666                                         true);
2667                             }
2668                         }
2669                     }
2670                 } else {
2671                     mCurrentSubtype =
2672                             getSubtypes(mMethodMap.get(lastInputMethodId)).get(subtypeId);
2673                 }
2674             }
2675             return mCurrentSubtype;
2676         }
2677     }
2678 
addShortcutInputMethodAndSubtypes(InputMethodInfo imi, InputMethodSubtype subtype)2679     private void addShortcutInputMethodAndSubtypes(InputMethodInfo imi,
2680             InputMethodSubtype subtype) {
2681         if (mShortcutInputMethodsAndSubtypes.containsKey(imi)) {
2682             mShortcutInputMethodsAndSubtypes.get(imi).add(subtype);
2683         } else {
2684             ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
2685             subtypes.add(subtype);
2686             mShortcutInputMethodsAndSubtypes.put(imi, subtypes);
2687         }
2688     }
2689 
2690     // TODO: We should change the return type from List to List<Parcelable>
2691     @SuppressWarnings("rawtypes")
2692     @Override
getShortcutInputMethodsAndSubtypes()2693     public List getShortcutInputMethodsAndSubtypes() {
2694         synchronized (mMethodMap) {
2695             ArrayList<Object> ret = new ArrayList<Object>();
2696             if (mShortcutInputMethodsAndSubtypes.size() == 0) {
2697                 // If there are no selected shortcut subtypes, the framework will try to find
2698                 // the most applicable subtype from all subtypes whose mode is
2699                 // SUBTYPE_MODE_VOICE. This is an exceptional case, so we will hardcode the mode.
2700                 Pair<InputMethodInfo, InputMethodSubtype> info =
2701                     findLastResortApplicableShortcutInputMethodAndSubtypeLocked(
2702                             SUBTYPE_MODE_VOICE);
2703                 if (info != null) {
2704                     ret.add(info.first);
2705                     ret.add(info.second);
2706                 }
2707                 return ret;
2708             }
2709             for (InputMethodInfo imi: mShortcutInputMethodsAndSubtypes.keySet()) {
2710                 ret.add(imi);
2711                 for (InputMethodSubtype subtype: mShortcutInputMethodsAndSubtypes.get(imi)) {
2712                     ret.add(subtype);
2713                 }
2714             }
2715             return ret;
2716         }
2717     }
2718 
2719     @Override
setCurrentInputMethodSubtype(InputMethodSubtype subtype)2720     public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
2721         synchronized (mMethodMap) {
2722             if (subtype != null && mCurMethodId != null) {
2723                 InputMethodInfo imi = mMethodMap.get(mCurMethodId);
2724                 int subtypeId = getSubtypeIdFromHashCode(imi, subtype.hashCode());
2725                 if (subtypeId != NOT_A_SUBTYPE_ID) {
2726                     setInputMethodLocked(mCurMethodId, subtypeId);
2727                     return true;
2728                 }
2729             }
2730             return false;
2731         }
2732     }
2733 
2734     /**
2735      * Utility class for putting and getting settings for InputMethod
2736      * TODO: Move all putters and getters of settings to this class.
2737      */
2738     private static class InputMethodSettings {
2739         // The string for enabled input method is saved as follows:
2740         // example: ("ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0")
2741         private static final char INPUT_METHOD_SEPARATER = ':';
2742         private static final char INPUT_METHOD_SUBTYPE_SEPARATER = ';';
2743         private final TextUtils.SimpleStringSplitter mInputMethodSplitter =
2744                 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATER);
2745 
2746         private final TextUtils.SimpleStringSplitter mSubtypeSplitter =
2747                 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATER);
2748 
2749         private final Resources mRes;
2750         private final ContentResolver mResolver;
2751         private final HashMap<String, InputMethodInfo> mMethodMap;
2752         private final ArrayList<InputMethodInfo> mMethodList;
2753 
2754         private String mEnabledInputMethodsStrCache;
2755 
buildEnabledInputMethodsSettingString( StringBuilder builder, Pair<String, ArrayList<String>> pair)2756         private static void buildEnabledInputMethodsSettingString(
2757                 StringBuilder builder, Pair<String, ArrayList<String>> pair) {
2758             String id = pair.first;
2759             ArrayList<String> subtypes = pair.second;
2760             builder.append(id);
2761             // Inputmethod and subtypes are saved in the settings as follows:
2762             // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
2763             for (String subtypeId: subtypes) {
2764                 builder.append(INPUT_METHOD_SUBTYPE_SEPARATER).append(subtypeId);
2765             }
2766         }
2767 
InputMethodSettings( Resources res, ContentResolver resolver, HashMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList)2768         public InputMethodSettings(
2769                 Resources res, ContentResolver resolver,
2770                 HashMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList) {
2771             mRes = res;
2772             mResolver = resolver;
2773             mMethodMap = methodMap;
2774             mMethodList = methodList;
2775         }
2776 
getEnabledInputMethodListLocked()2777         public List<InputMethodInfo> getEnabledInputMethodListLocked() {
2778             return createEnabledInputMethodListLocked(
2779                     getEnabledInputMethodsAndSubtypeListLocked());
2780         }
2781 
2782         public List<Pair<InputMethodInfo, ArrayList<String>>>
getEnabledInputMethodAndSubtypeHashCodeListLocked()2783                 getEnabledInputMethodAndSubtypeHashCodeListLocked() {
2784             return createEnabledInputMethodAndSubtypeHashCodeListLocked(
2785                     getEnabledInputMethodsAndSubtypeListLocked());
2786         }
2787 
getEnabledInputMethodSubtypeListLocked( InputMethodInfo imi)2788         public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
2789                 InputMethodInfo imi) {
2790             List<Pair<String, ArrayList<String>>> imsList =
2791                     getEnabledInputMethodsAndSubtypeListLocked();
2792             ArrayList<InputMethodSubtype> enabledSubtypes =
2793                     new ArrayList<InputMethodSubtype>();
2794             if (imi != null) {
2795                 for (Pair<String, ArrayList<String>> imsPair : imsList) {
2796                     InputMethodInfo info = mMethodMap.get(imsPair.first);
2797                     if (info != null && info.getId().equals(imi.getId())) {
2798                         final int subtypeCount = info.getSubtypeCount();
2799                         for (int i = 0; i < subtypeCount; ++i) {
2800                             InputMethodSubtype ims = info.getSubtypeAt(i);
2801                             for (String s: imsPair.second) {
2802                                 if (String.valueOf(ims.hashCode()).equals(s)) {
2803                                     enabledSubtypes.add(ims);
2804                                 }
2805                             }
2806                         }
2807                         break;
2808                     }
2809                 }
2810             }
2811             return enabledSubtypes;
2812         }
2813 
2814         // At the initial boot, the settings for input methods are not set,
2815         // so we need to enable IME in that case.
enableAllIMEsIfThereIsNoEnabledIME()2816         public void enableAllIMEsIfThereIsNoEnabledIME() {
2817             if (TextUtils.isEmpty(getEnabledInputMethodsStr())) {
2818                 StringBuilder sb = new StringBuilder();
2819                 final int N = mMethodList.size();
2820                 for (int i = 0; i < N; i++) {
2821                     InputMethodInfo imi = mMethodList.get(i);
2822                     Slog.i(TAG, "Adding: " + imi.getId());
2823                     if (i > 0) sb.append(':');
2824                     sb.append(imi.getId());
2825                 }
2826                 putEnabledInputMethodsStr(sb.toString());
2827             }
2828         }
2829 
getEnabledInputMethodsAndSubtypeListLocked()2830         private List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() {
2831             ArrayList<Pair<String, ArrayList<String>>> imsList
2832                     = new ArrayList<Pair<String, ArrayList<String>>>();
2833             final String enabledInputMethodsStr = getEnabledInputMethodsStr();
2834             if (TextUtils.isEmpty(enabledInputMethodsStr)) {
2835                 return imsList;
2836             }
2837             mInputMethodSplitter.setString(enabledInputMethodsStr);
2838             while (mInputMethodSplitter.hasNext()) {
2839                 String nextImsStr = mInputMethodSplitter.next();
2840                 mSubtypeSplitter.setString(nextImsStr);
2841                 if (mSubtypeSplitter.hasNext()) {
2842                     ArrayList<String> subtypeHashes = new ArrayList<String>();
2843                     // The first element is ime id.
2844                     String imeId = mSubtypeSplitter.next();
2845                     while (mSubtypeSplitter.hasNext()) {
2846                         subtypeHashes.add(mSubtypeSplitter.next());
2847                     }
2848                     imsList.add(new Pair<String, ArrayList<String>>(imeId, subtypeHashes));
2849                 }
2850             }
2851             return imsList;
2852         }
2853 
appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr)2854         public void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) {
2855             if (reloadInputMethodStr) {
2856                 getEnabledInputMethodsStr();
2857             }
2858             if (TextUtils.isEmpty(mEnabledInputMethodsStrCache)) {
2859                 // Add in the newly enabled input method.
2860                 putEnabledInputMethodsStr(id);
2861             } else {
2862                 putEnabledInputMethodsStr(
2863                         mEnabledInputMethodsStrCache + INPUT_METHOD_SEPARATER + id);
2864             }
2865         }
2866 
2867         /**
2868          * Build and put a string of EnabledInputMethods with removing specified Id.
2869          * @return the specified id was removed or not.
2870          */
buildAndPutEnabledInputMethodsStrRemovingIdLocked( StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id)2871         public boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked(
2872                 StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) {
2873             boolean isRemoved = false;
2874             boolean needsAppendSeparator = false;
2875             for (Pair<String, ArrayList<String>> ims: imsList) {
2876                 String curId = ims.first;
2877                 if (curId.equals(id)) {
2878                     // We are disabling this input method, and it is
2879                     // currently enabled.  Skip it to remove from the
2880                     // new list.
2881                     isRemoved = true;
2882                 } else {
2883                     if (needsAppendSeparator) {
2884                         builder.append(INPUT_METHOD_SEPARATER);
2885                     } else {
2886                         needsAppendSeparator = true;
2887                     }
2888                     buildEnabledInputMethodsSettingString(builder, ims);
2889                 }
2890             }
2891             if (isRemoved) {
2892                 // Update the setting with the new list of input methods.
2893                 putEnabledInputMethodsStr(builder.toString());
2894             }
2895             return isRemoved;
2896         }
2897 
createEnabledInputMethodListLocked( List<Pair<String, ArrayList<String>>> imsList)2898         private List<InputMethodInfo> createEnabledInputMethodListLocked(
2899                 List<Pair<String, ArrayList<String>>> imsList) {
2900             final ArrayList<InputMethodInfo> res = new ArrayList<InputMethodInfo>();
2901             for (Pair<String, ArrayList<String>> ims: imsList) {
2902                 InputMethodInfo info = mMethodMap.get(ims.first);
2903                 if (info != null) {
2904                     res.add(info);
2905                 }
2906             }
2907             return res;
2908         }
2909 
2910         private List<Pair<InputMethodInfo, ArrayList<String>>>
createEnabledInputMethodAndSubtypeHashCodeListLocked( List<Pair<String, ArrayList<String>>> imsList)2911                 createEnabledInputMethodAndSubtypeHashCodeListLocked(
2912                         List<Pair<String, ArrayList<String>>> imsList) {
2913             final ArrayList<Pair<InputMethodInfo, ArrayList<String>>> res
2914                     = new ArrayList<Pair<InputMethodInfo, ArrayList<String>>>();
2915             for (Pair<String, ArrayList<String>> ims : imsList) {
2916                 InputMethodInfo info = mMethodMap.get(ims.first);
2917                 if (info != null) {
2918                     res.add(new Pair<InputMethodInfo, ArrayList<String>>(info, ims.second));
2919                 }
2920             }
2921             return res;
2922         }
2923 
putEnabledInputMethodsStr(String str)2924         private void putEnabledInputMethodsStr(String str) {
2925             Settings.Secure.putString(mResolver, Settings.Secure.ENABLED_INPUT_METHODS, str);
2926             mEnabledInputMethodsStrCache = str;
2927         }
2928 
getEnabledInputMethodsStr()2929         private String getEnabledInputMethodsStr() {
2930             mEnabledInputMethodsStrCache = Settings.Secure.getString(
2931                     mResolver, Settings.Secure.ENABLED_INPUT_METHODS);
2932             if (DEBUG) {
2933                 Slog.d(TAG, "getEnabledInputMethodsStr: " + mEnabledInputMethodsStrCache);
2934             }
2935             return mEnabledInputMethodsStrCache;
2936         }
2937 
saveSubtypeHistory( List<Pair<String, String>> savedImes, String newImeId, String newSubtypeId)2938         private void saveSubtypeHistory(
2939                 List<Pair<String, String>> savedImes, String newImeId, String newSubtypeId) {
2940             StringBuilder builder = new StringBuilder();
2941             boolean isImeAdded = false;
2942             if (!TextUtils.isEmpty(newImeId) && !TextUtils.isEmpty(newSubtypeId)) {
2943                 builder.append(newImeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append(
2944                         newSubtypeId);
2945                 isImeAdded = true;
2946             }
2947             for (Pair<String, String> ime: savedImes) {
2948                 String imeId = ime.first;
2949                 String subtypeId = ime.second;
2950                 if (TextUtils.isEmpty(subtypeId)) {
2951                     subtypeId = NOT_A_SUBTYPE_ID_STR;
2952                 }
2953                 if (isImeAdded) {
2954                     builder.append(INPUT_METHOD_SEPARATER);
2955                 } else {
2956                     isImeAdded = true;
2957                 }
2958                 builder.append(imeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append(
2959                         subtypeId);
2960             }
2961             // Remove the last INPUT_METHOD_SEPARATER
2962             putSubtypeHistoryStr(builder.toString());
2963         }
2964 
addSubtypeToHistory(String imeId, String subtypeId)2965         public void addSubtypeToHistory(String imeId, String subtypeId) {
2966             List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
2967             for (Pair<String, String> ime: subtypeHistory) {
2968                 if (ime.first.equals(imeId)) {
2969                     if (DEBUG) {
2970                         Slog.v(TAG, "Subtype found in the history: " + imeId + ", "
2971                                 + ime.second);
2972                     }
2973                     // We should break here
2974                     subtypeHistory.remove(ime);
2975                     break;
2976                 }
2977             }
2978             if (DEBUG) {
2979                 Slog.v(TAG, "Add subtype to the history: " + imeId + ", " + subtypeId);
2980             }
2981             saveSubtypeHistory(subtypeHistory, imeId, subtypeId);
2982         }
2983 
putSubtypeHistoryStr(String str)2984         private void putSubtypeHistoryStr(String str) {
2985             if (DEBUG) {
2986                 Slog.d(TAG, "putSubtypeHistoryStr: " + str);
2987             }
2988             Settings.Secure.putString(
2989                     mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, str);
2990         }
2991 
getLastInputMethodAndSubtypeLocked()2992         public Pair<String, String> getLastInputMethodAndSubtypeLocked() {
2993             // Gets the first one from the history
2994             return getLastSubtypeForInputMethodLockedInternal(null);
2995         }
2996 
getLastSubtypeForInputMethodLocked(String imeId)2997         public String getLastSubtypeForInputMethodLocked(String imeId) {
2998             Pair<String, String> ime = getLastSubtypeForInputMethodLockedInternal(imeId);
2999             if (ime != null) {
3000                 return ime.second;
3001             } else {
3002                 return null;
3003             }
3004         }
3005 
getLastSubtypeForInputMethodLockedInternal(String imeId)3006         private Pair<String, String> getLastSubtypeForInputMethodLockedInternal(String imeId) {
3007             List<Pair<String, ArrayList<String>>> enabledImes =
3008                     getEnabledInputMethodsAndSubtypeListLocked();
3009             List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
3010             for (Pair<String, String> imeAndSubtype : subtypeHistory) {
3011                 final String imeInTheHistory = imeAndSubtype.first;
3012                 // If imeId is empty, returns the first IME and subtype in the history
3013                 if (TextUtils.isEmpty(imeId) || imeInTheHistory.equals(imeId)) {
3014                     final String subtypeInTheHistory = imeAndSubtype.second;
3015                     final String subtypeHashCode =
3016                             getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(
3017                                     enabledImes, imeInTheHistory, subtypeInTheHistory);
3018                     if (!TextUtils.isEmpty(subtypeHashCode)) {
3019                         if (DEBUG) {
3020                             Slog.d(TAG, "Enabled subtype found in the history: " + subtypeHashCode);
3021                         }
3022                         return new Pair<String, String>(imeInTheHistory, subtypeHashCode);
3023                     }
3024                 }
3025             }
3026             if (DEBUG) {
3027                 Slog.d(TAG, "No enabled IME found in the history");
3028             }
3029             return null;
3030         }
3031 
getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(List<Pair<String, ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode)3032         private String getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(List<Pair<String,
3033                 ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode) {
3034             for (Pair<String, ArrayList<String>> enabledIme: enabledImes) {
3035                 if (enabledIme.first.equals(imeId)) {
3036                     final ArrayList<String> explicitlyEnabledSubtypes = enabledIme.second;
3037                     if (explicitlyEnabledSubtypes.size() == 0) {
3038                         // If there are no explicitly enabled subtypes, applicable subtypes are
3039                         // enabled implicitly.
3040                         InputMethodInfo imi = mMethodMap.get(imeId);
3041                         // If IME is enabled and no subtypes are enabled, applicable subtypes
3042                         // are enabled implicitly, so needs to treat them to be enabled.
3043                         if (imi != null && imi.getSubtypeCount() > 0) {
3044                             List<InputMethodSubtype> implicitlySelectedSubtypes =
3045                                     getImplicitlyApplicableSubtypesLocked(mRes, imi);
3046                             if (implicitlySelectedSubtypes != null) {
3047                                 final int N = implicitlySelectedSubtypes.size();
3048                                 for (int i = 0; i < N; ++i) {
3049                                     final InputMethodSubtype st = implicitlySelectedSubtypes.get(i);
3050                                     if (String.valueOf(st.hashCode()).equals(subtypeHashCode)) {
3051                                         return subtypeHashCode;
3052                                     }
3053                                 }
3054                             }
3055                         }
3056                     } else {
3057                         for (String s: explicitlyEnabledSubtypes) {
3058                             if (s.equals(subtypeHashCode)) {
3059                                 // If both imeId and subtypeId are enabled, return subtypeId.
3060                                 return s;
3061                             }
3062                         }
3063                     }
3064                     // If imeId was enabled but subtypeId was disabled.
3065                     return NOT_A_SUBTYPE_ID_STR;
3066                 }
3067             }
3068             // If both imeId and subtypeId are disabled, return null
3069             return null;
3070         }
3071 
loadInputMethodAndSubtypeHistoryLocked()3072         private List<Pair<String, String>> loadInputMethodAndSubtypeHistoryLocked() {
3073             ArrayList<Pair<String, String>> imsList = new ArrayList<Pair<String, String>>();
3074             final String subtypeHistoryStr = getSubtypeHistoryStr();
3075             if (TextUtils.isEmpty(subtypeHistoryStr)) {
3076                 return imsList;
3077             }
3078             mInputMethodSplitter.setString(subtypeHistoryStr);
3079             while (mInputMethodSplitter.hasNext()) {
3080                 String nextImsStr = mInputMethodSplitter.next();
3081                 mSubtypeSplitter.setString(nextImsStr);
3082                 if (mSubtypeSplitter.hasNext()) {
3083                     String subtypeId = NOT_A_SUBTYPE_ID_STR;
3084                     // The first element is ime id.
3085                     String imeId = mSubtypeSplitter.next();
3086                     while (mSubtypeSplitter.hasNext()) {
3087                         subtypeId = mSubtypeSplitter.next();
3088                         break;
3089                     }
3090                     imsList.add(new Pair<String, String>(imeId, subtypeId));
3091                 }
3092             }
3093             return imsList;
3094         }
3095 
getSubtypeHistoryStr()3096         private String getSubtypeHistoryStr() {
3097             if (DEBUG) {
3098                 Slog.d(TAG, "getSubtypeHistoryStr: " + Settings.Secure.getString(
3099                         mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY));
3100             }
3101             return Settings.Secure.getString(
3102                     mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY);
3103         }
3104 
putSelectedInputMethod(String imeId)3105         public void putSelectedInputMethod(String imeId) {
3106             Settings.Secure.putString(mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, imeId);
3107         }
3108 
putSelectedSubtype(int subtypeId)3109         public void putSelectedSubtype(int subtypeId) {
3110             Settings.Secure.putInt(
3111                     mResolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, subtypeId);
3112         }
3113     }
3114 
3115     private static class InputMethodFileManager {
3116         private static final String SYSTEM_PATH = "system";
3117         private static final String INPUT_METHOD_PATH = "inputmethod";
3118         private static final String ADDITIONAL_SUBTYPES_FILE_NAME = "subtypes.xml";
3119         private static final String NODE_SUBTYPES = "subtypes";
3120         private static final String NODE_SUBTYPE = "subtype";
3121         private static final String NODE_IMI = "imi";
3122         private static final String ATTR_ID = "id";
3123         private static final String ATTR_LABEL = "label";
3124         private static final String ATTR_ICON = "icon";
3125         private static final String ATTR_IME_SUBTYPE_LOCALE = "imeSubtypeLocale";
3126         private static final String ATTR_IME_SUBTYPE_MODE = "imeSubtypeMode";
3127         private static final String ATTR_IME_SUBTYPE_EXTRA_VALUE = "imeSubtypeExtraValue";
3128         private static final String ATTR_IS_AUXILIARY = "isAuxiliary";
3129         private final AtomicFile mAdditionalInputMethodSubtypeFile;
3130         private final HashMap<String, InputMethodInfo> mMethodMap;
3131         private final HashMap<String, List<InputMethodSubtype>> mSubtypesMap =
3132                 new HashMap<String, List<InputMethodSubtype>>();
InputMethodFileManager(HashMap<String, InputMethodInfo> methodMap)3133         public InputMethodFileManager(HashMap<String, InputMethodInfo> methodMap) {
3134             if (methodMap == null) {
3135                 throw new NullPointerException("methodMap is null");
3136             }
3137             mMethodMap = methodMap;
3138             final File systemDir = new File(Environment.getDataDirectory(), SYSTEM_PATH);
3139             final File inputMethodDir = new File(systemDir, INPUT_METHOD_PATH);
3140             if (!inputMethodDir.mkdirs()) {
3141                 Slog.w(TAG, "Couldn't create dir.: " + inputMethodDir.getAbsolutePath());
3142             }
3143             final File subtypeFile = new File(inputMethodDir, ADDITIONAL_SUBTYPES_FILE_NAME);
3144             mAdditionalInputMethodSubtypeFile = new AtomicFile(subtypeFile);
3145             if (!subtypeFile.exists()) {
3146                 // If "subtypes.xml" doesn't exist, create a blank file.
3147                 writeAdditionalInputMethodSubtypes(mSubtypesMap, mAdditionalInputMethodSubtypeFile,
3148                         methodMap);
3149             } else {
3150                 readAdditionalInputMethodSubtypes(mSubtypesMap, mAdditionalInputMethodSubtypeFile);
3151             }
3152         }
3153 
deleteAllInputMethodSubtypes(String imiId)3154         private void deleteAllInputMethodSubtypes(String imiId) {
3155             synchronized (mMethodMap) {
3156                 mSubtypesMap.remove(imiId);
3157                 writeAdditionalInputMethodSubtypes(mSubtypesMap, mAdditionalInputMethodSubtypeFile,
3158                         mMethodMap);
3159             }
3160         }
3161 
addInputMethodSubtypes( InputMethodInfo imi, InputMethodSubtype[] additionalSubtypes)3162         public void addInputMethodSubtypes(
3163                 InputMethodInfo imi, InputMethodSubtype[] additionalSubtypes) {
3164             synchronized (mMethodMap) {
3165                 final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
3166                 final int N = additionalSubtypes.length;
3167                 for (int i = 0; i < N; ++i) {
3168                     final InputMethodSubtype subtype = additionalSubtypes[i];
3169                     if (!subtypes.contains(subtype)) {
3170                         subtypes.add(subtype);
3171                     }
3172                 }
3173                 mSubtypesMap.put(imi.getId(), subtypes);
3174                 writeAdditionalInputMethodSubtypes(mSubtypesMap, mAdditionalInputMethodSubtypeFile,
3175                         mMethodMap);
3176             }
3177         }
3178 
getAllAdditionalInputMethodSubtypes()3179         public HashMap<String, List<InputMethodSubtype>> getAllAdditionalInputMethodSubtypes() {
3180             synchronized (mMethodMap) {
3181                 return mSubtypesMap;
3182             }
3183         }
3184 
writeAdditionalInputMethodSubtypes( HashMap<String, List<InputMethodSubtype>> allSubtypes, AtomicFile subtypesFile, HashMap<String, InputMethodInfo> methodMap)3185         private static void writeAdditionalInputMethodSubtypes(
3186                 HashMap<String, List<InputMethodSubtype>> allSubtypes, AtomicFile subtypesFile,
3187                 HashMap<String, InputMethodInfo> methodMap) {
3188             // Safety net for the case that this function is called before methodMap is set.
3189             final boolean isSetMethodMap = methodMap != null && methodMap.size() > 0;
3190             FileOutputStream fos = null;
3191             try {
3192                 fos = subtypesFile.startWrite();
3193                 final XmlSerializer out = new FastXmlSerializer();
3194                 out.setOutput(fos, "utf-8");
3195                 out.startDocument(null, true);
3196                 out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
3197                 out.startTag(null, NODE_SUBTYPES);
3198                 for (String imiId : allSubtypes.keySet()) {
3199                     if (isSetMethodMap && !methodMap.containsKey(imiId)) {
3200                         Slog.w(TAG, "IME uninstalled or not valid.: " + imiId);
3201                         continue;
3202                     }
3203                     out.startTag(null, NODE_IMI);
3204                     out.attribute(null, ATTR_ID, imiId);
3205                     final List<InputMethodSubtype> subtypesList = allSubtypes.get(imiId);
3206                     final int N = subtypesList.size();
3207                     for (int i = 0; i < N; ++i) {
3208                         final InputMethodSubtype subtype = subtypesList.get(i);
3209                         out.startTag(null, NODE_SUBTYPE);
3210                         out.attribute(null, ATTR_ICON, String.valueOf(subtype.getIconResId()));
3211                         out.attribute(null, ATTR_LABEL, String.valueOf(subtype.getNameResId()));
3212                         out.attribute(null, ATTR_IME_SUBTYPE_LOCALE, subtype.getLocale());
3213                         out.attribute(null, ATTR_IME_SUBTYPE_MODE, subtype.getMode());
3214                         out.attribute(null, ATTR_IME_SUBTYPE_EXTRA_VALUE, subtype.getExtraValue());
3215                         out.attribute(null, ATTR_IS_AUXILIARY,
3216                                 String.valueOf(subtype.isAuxiliary() ? 1 : 0));
3217                         out.endTag(null, NODE_SUBTYPE);
3218                     }
3219                     out.endTag(null, NODE_IMI);
3220                 }
3221                 out.endTag(null, NODE_SUBTYPES);
3222                 out.endDocument();
3223                 subtypesFile.finishWrite(fos);
3224             } catch (java.io.IOException e) {
3225                 Slog.w(TAG, "Error writing subtypes", e);
3226                 if (fos != null) {
3227                     subtypesFile.failWrite(fos);
3228                 }
3229             }
3230         }
3231 
readAdditionalInputMethodSubtypes( HashMap<String, List<InputMethodSubtype>> allSubtypes, AtomicFile subtypesFile)3232         private static void readAdditionalInputMethodSubtypes(
3233                 HashMap<String, List<InputMethodSubtype>> allSubtypes, AtomicFile subtypesFile) {
3234             if (allSubtypes == null || subtypesFile == null) return;
3235             allSubtypes.clear();
3236             FileInputStream fis = null;
3237             try {
3238                 fis = subtypesFile.openRead();
3239                 final XmlPullParser parser = Xml.newPullParser();
3240                 parser.setInput(fis, null);
3241                 int type = parser.getEventType();
3242                 // Skip parsing until START_TAG
3243                 while ((type = parser.next()) != XmlPullParser.START_TAG
3244                         && type != XmlPullParser.END_DOCUMENT) {}
3245                 String firstNodeName = parser.getName();
3246                 if (!NODE_SUBTYPES.equals(firstNodeName)) {
3247                     throw new XmlPullParserException("Xml doesn't start with subtypes");
3248                 }
3249                 final int depth =parser.getDepth();
3250                 String currentImiId = null;
3251                 ArrayList<InputMethodSubtype> tempSubtypesArray = null;
3252                 while (((type = parser.next()) != XmlPullParser.END_TAG
3253                         || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
3254                     if (type != XmlPullParser.START_TAG)
3255                         continue;
3256                     final String nodeName = parser.getName();
3257                     if (NODE_IMI.equals(nodeName)) {
3258                         currentImiId = parser.getAttributeValue(null, ATTR_ID);
3259                         if (TextUtils.isEmpty(currentImiId)) {
3260                             Slog.w(TAG, "Invalid imi id found in subtypes.xml");
3261                             continue;
3262                         }
3263                         tempSubtypesArray = new ArrayList<InputMethodSubtype>();
3264                         allSubtypes.put(currentImiId, tempSubtypesArray);
3265                     } else if (NODE_SUBTYPE.equals(nodeName)) {
3266                         if (TextUtils.isEmpty(currentImiId) || tempSubtypesArray == null) {
3267                             Slog.w(TAG, "IME uninstalled or not valid.: " + currentImiId);
3268                             continue;
3269                         }
3270                         final int icon = Integer.valueOf(
3271                                 parser.getAttributeValue(null, ATTR_ICON));
3272                         final int label = Integer.valueOf(
3273                                 parser.getAttributeValue(null, ATTR_LABEL));
3274                         final String imeSubtypeLocale =
3275                                 parser.getAttributeValue(null, ATTR_IME_SUBTYPE_LOCALE);
3276                         final String imeSubtypeMode =
3277                                 parser.getAttributeValue(null, ATTR_IME_SUBTYPE_MODE);
3278                         final String imeSubtypeExtraValue =
3279                                 parser.getAttributeValue(null, ATTR_IME_SUBTYPE_EXTRA_VALUE);
3280                         final boolean isAuxiliary = "1".equals(String.valueOf(
3281                                 parser.getAttributeValue(null, ATTR_IS_AUXILIARY)));
3282                         final InputMethodSubtype subtype =
3283                                 new InputMethodSubtype(label, icon, imeSubtypeLocale,
3284                                         imeSubtypeMode, imeSubtypeExtraValue, isAuxiliary);
3285                         tempSubtypesArray.add(subtype);
3286                     }
3287                 }
3288             } catch (XmlPullParserException e) {
3289                 Slog.w(TAG, "Error reading subtypes: " + e);
3290                 return;
3291             } catch (java.io.IOException e) {
3292                 Slog.w(TAG, "Error reading subtypes: " + e);
3293                 return;
3294             } catch (NumberFormatException e) {
3295                 Slog.w(TAG, "Error reading subtypes: " + e);
3296                 return;
3297             } finally {
3298                 if (fis != null) {
3299                     try {
3300                         fis.close();
3301                     } catch (java.io.IOException e1) {
3302                         Slog.w(TAG, "Failed to close.");
3303                     }
3304                 }
3305             }
3306         }
3307     }
3308 
3309     // ----------------------------------------------------------------------
3310 
3311     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)3312     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
3313         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
3314                 != PackageManager.PERMISSION_GRANTED) {
3315 
3316             pw.println("Permission Denial: can't dump InputMethodManager from from pid="
3317                     + Binder.getCallingPid()
3318                     + ", uid=" + Binder.getCallingUid());
3319             return;
3320         }
3321 
3322         IInputMethod method;
3323         ClientState client;
3324 
3325         final Printer p = new PrintWriterPrinter(pw);
3326 
3327         synchronized (mMethodMap) {
3328             p.println("Current Input Method Manager state:");
3329             int N = mMethodList.size();
3330             p.println("  Input Methods:");
3331             for (int i=0; i<N; i++) {
3332                 InputMethodInfo info = mMethodList.get(i);
3333                 p.println("  InputMethod #" + i + ":");
3334                 info.dump(p, "    ");
3335             }
3336             p.println("  Clients:");
3337             for (ClientState ci : mClients.values()) {
3338                 p.println("  Client " + ci + ":");
3339                 p.println("    client=" + ci.client);
3340                 p.println("    inputContext=" + ci.inputContext);
3341                 p.println("    sessionRequested=" + ci.sessionRequested);
3342                 p.println("    curSession=" + ci.curSession);
3343             }
3344             p.println("  mCurMethodId=" + mCurMethodId);
3345             client = mCurClient;
3346             p.println("  mCurClient=" + client + " mCurSeq=" + mCurSeq);
3347             p.println("  mCurFocusedWindow=" + mCurFocusedWindow);
3348             p.println("  mCurId=" + mCurId + " mHaveConnect=" + mHaveConnection
3349                     + " mBoundToMethod=" + mBoundToMethod);
3350             p.println("  mCurToken=" + mCurToken);
3351             p.println("  mCurIntent=" + mCurIntent);
3352             method = mCurMethod;
3353             p.println("  mCurMethod=" + mCurMethod);
3354             p.println("  mEnabledSession=" + mEnabledSession);
3355             p.println("  mShowRequested=" + mShowRequested
3356                     + " mShowExplicitlyRequested=" + mShowExplicitlyRequested
3357                     + " mShowForced=" + mShowForced
3358                     + " mInputShown=" + mInputShown);
3359             p.println("  mSystemReady=" + mSystemReady + " mScreenOn=" + mScreenOn);
3360         }
3361 
3362         p.println(" ");
3363         if (client != null) {
3364             pw.flush();
3365             try {
3366                 client.client.asBinder().dump(fd, args);
3367             } catch (RemoteException e) {
3368                 p.println("Input method client dead: " + e);
3369             }
3370         } else {
3371             p.println("No input method client.");
3372         }
3373 
3374         p.println(" ");
3375         if (method != null) {
3376             pw.flush();
3377             try {
3378                 method.asBinder().dump(fd, args);
3379             } catch (RemoteException e) {
3380                 p.println("Input method service dead: " + e);
3381             }
3382         } else {
3383             p.println("No input method service.");
3384         }
3385     }
3386 }
3387