Home | History | Annotate | Download | only in server
      1 /*
      2  * Copyright (C) 2006-2008 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
      5  * use this file except in compliance with the License. You may obtain a copy of
      6  * the License at
      7  *
      8  * http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
     12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
     13  * License for the specific language governing permissions and limitations under
     14  * the License.
     15  */
     16 
     17 package com.android.server;
     18 
     19 import com.android.internal.content.PackageMonitor;
     20 import com.android.internal.os.HandlerCaller;
     21 import com.android.internal.view.IInputContext;
     22 import com.android.internal.view.IInputMethod;
     23 import com.android.internal.view.IInputMethodCallback;
     24 import com.android.internal.view.IInputMethodClient;
     25 import com.android.internal.view.IInputMethodManager;
     26 import com.android.internal.view.IInputMethodSession;
     27 import com.android.internal.view.InputBindResult;
     28 
     29 import com.android.server.StatusBarManagerService;
     30 
     31 import org.xmlpull.v1.XmlPullParserException;
     32 
     33 import android.app.ActivityManagerNative;
     34 import android.app.AlertDialog;
     35 import android.app.PendingIntent;
     36 import android.content.ComponentName;
     37 import android.content.ContentResolver;
     38 import android.content.Context;
     39 import android.content.DialogInterface;
     40 import android.content.IntentFilter;
     41 import android.content.DialogInterface.OnCancelListener;
     42 import android.content.Intent;
     43 import android.content.ServiceConnection;
     44 import android.content.pm.ApplicationInfo;
     45 import android.content.pm.PackageManager;
     46 import android.content.pm.ResolveInfo;
     47 import android.content.pm.ServiceInfo;
     48 import android.content.res.Configuration;
     49 import android.content.res.Resources;
     50 import android.content.res.TypedArray;
     51 import android.database.ContentObserver;
     52 import android.os.Binder;
     53 import android.os.Handler;
     54 import android.os.IBinder;
     55 import android.os.IInterface;
     56 import android.os.Message;
     57 import android.os.Parcel;
     58 import android.os.RemoteException;
     59 import android.os.ResultReceiver;
     60 import android.os.ServiceManager;
     61 import android.os.SystemClock;
     62 import android.provider.Settings;
     63 import android.provider.Settings.Secure;
     64 import android.text.TextUtils;
     65 import android.util.EventLog;
     66 import android.util.Slog;
     67 import android.util.PrintWriterPrinter;
     68 import android.util.Printer;
     69 import android.view.IWindowManager;
     70 import android.view.WindowManager;
     71 import android.view.inputmethod.InputBinding;
     72 import android.view.inputmethod.InputMethod;
     73 import android.view.inputmethod.InputMethodInfo;
     74 import android.view.inputmethod.InputMethodManager;
     75 import android.view.inputmethod.EditorInfo;
     76 
     77 import java.io.FileDescriptor;
     78 import java.io.IOException;
     79 import java.io.PrintWriter;
     80 import java.text.Collator;
     81 import java.util.ArrayList;
     82 import java.util.HashMap;
     83 import java.util.List;
     84 import java.util.Map;
     85 import java.util.TreeMap;
     86 
     87 /**
     88  * This class provides a system service that manages input methods.
     89  */
     90 public class InputMethodManagerService extends IInputMethodManager.Stub
     91         implements ServiceConnection, Handler.Callback {
     92     static final boolean DEBUG = false;
     93     static final String TAG = "InputManagerService";
     94 
     95     static final int MSG_SHOW_IM_PICKER = 1;
     96 
     97     static final int MSG_UNBIND_INPUT = 1000;
     98     static final int MSG_BIND_INPUT = 1010;
     99     static final int MSG_SHOW_SOFT_INPUT = 1020;
    100     static final int MSG_HIDE_SOFT_INPUT = 1030;
    101     static final int MSG_ATTACH_TOKEN = 1040;
    102     static final int MSG_CREATE_SESSION = 1050;
    103 
    104     static final int MSG_START_INPUT = 2000;
    105     static final int MSG_RESTART_INPUT = 2010;
    106 
    107     static final int MSG_UNBIND_METHOD = 3000;
    108     static final int MSG_BIND_METHOD = 3010;
    109 
    110     static final long TIME_TO_RECONNECT = 10*1000;
    111 
    112     final Context mContext;
    113     final Handler mHandler;
    114     final SettingsObserver mSettingsObserver;
    115     final StatusBarManagerService mStatusBar;
    116     final IWindowManager mIWindowManager;
    117     final HandlerCaller mCaller;
    118 
    119     final InputBindResult mNoBinding = new InputBindResult(null, null, -1);
    120 
    121     // All known input methods.  mMethodMap also serves as the global
    122     // lock for this class.
    123     final ArrayList<InputMethodInfo> mMethodList
    124             = new ArrayList<InputMethodInfo>();
    125     final HashMap<String, InputMethodInfo> mMethodMap
    126             = new HashMap<String, InputMethodInfo>();
    127 
    128     final TextUtils.SimpleStringSplitter mStringColonSplitter
    129             = new TextUtils.SimpleStringSplitter(':');
    130 
    131     class SessionState {
    132         final ClientState client;
    133         final IInputMethod method;
    134         final IInputMethodSession session;
    135 
    136         @Override
    137         public String toString() {
    138             return "SessionState{uid " + client.uid + " pid " + client.pid
    139                     + " method " + Integer.toHexString(
    140                             System.identityHashCode(method))
    141                     + " session " + Integer.toHexString(
    142                             System.identityHashCode(session))
    143                     + "}";
    144         }
    145 
    146         SessionState(ClientState _client, IInputMethod _method,
    147                 IInputMethodSession _session) {
    148             client = _client;
    149             method = _method;
    150             session = _session;
    151         }
    152     }
    153 
    154     class ClientState {
    155         final IInputMethodClient client;
    156         final IInputContext inputContext;
    157         final int uid;
    158         final int pid;
    159         final InputBinding binding;
    160 
    161         boolean sessionRequested;
    162         SessionState curSession;
    163 
    164         @Override
    165         public String toString() {
    166             return "ClientState{" + Integer.toHexString(
    167                     System.identityHashCode(this)) + " uid " + uid
    168                     + " pid " + pid + "}";
    169         }
    170 
    171         ClientState(IInputMethodClient _client, IInputContext _inputContext,
    172                 int _uid, int _pid) {
    173             client = _client;
    174             inputContext = _inputContext;
    175             uid = _uid;
    176             pid = _pid;
    177             binding = new InputBinding(null, inputContext.asBinder(), uid, pid);
    178         }
    179     }
    180 
    181     final HashMap<IBinder, ClientState> mClients
    182             = new HashMap<IBinder, ClientState>();
    183 
    184     /**
    185      * Set once the system is ready to run third party code.
    186      */
    187     boolean mSystemReady;
    188 
    189     /**
    190      * Id of the currently selected input method.
    191      */
    192     String mCurMethodId;
    193 
    194     /**
    195      * The current binding sequence number, incremented every time there is
    196      * a new bind performed.
    197      */
    198     int mCurSeq;
    199 
    200     /**
    201      * The client that is currently bound to an input method.
    202      */
    203     ClientState mCurClient;
    204 
    205     /**
    206      * The last window token that gained focus.
    207      */
    208     IBinder mCurFocusedWindow;
    209 
    210     /**
    211      * The input context last provided by the current client.
    212      */
    213     IInputContext mCurInputContext;
    214 
    215     /**
    216      * The attributes last provided by the current client.
    217      */
    218     EditorInfo mCurAttribute;
    219 
    220     /**
    221      * The input method ID of the input method service that we are currently
    222      * connected to or in the process of connecting to.
    223      */
    224     String mCurId;
    225 
    226     /**
    227      * Set to true if our ServiceConnection is currently actively bound to
    228      * a service (whether or not we have gotten its IBinder back yet).
    229      */
    230     boolean mHaveConnection;
    231 
    232     /**
    233      * Set if the client has asked for the input method to be shown.
    234      */
    235     boolean mShowRequested;
    236 
    237     /**
    238      * Set if we were explicitly told to show the input method.
    239      */
    240     boolean mShowExplicitlyRequested;
    241 
    242     /**
    243      * Set if we were forced to be shown.
    244      */
    245     boolean mShowForced;
    246 
    247     /**
    248      * Set if we last told the input method to show itself.
    249      */
    250     boolean mInputShown;
    251 
    252     /**
    253      * The Intent used to connect to the current input method.
    254      */
    255     Intent mCurIntent;
    256 
    257     /**
    258      * The token we have made for the currently active input method, to
    259      * identify it in the future.
    260      */
    261     IBinder mCurToken;
    262 
    263     /**
    264      * If non-null, this is the input method service we are currently connected
    265      * to.
    266      */
    267     IInputMethod mCurMethod;
    268 
    269     /**
    270      * Time that we last initiated a bind to the input method, to determine
    271      * if we should try to disconnect and reconnect to it.
    272      */
    273     long mLastBindTime;
    274 
    275     /**
    276      * Have we called mCurMethod.bindInput()?
    277      */
    278     boolean mBoundToMethod;
    279 
    280     /**
    281      * Currently enabled session.  Only touched by service thread, not
    282      * protected by a lock.
    283      */
    284     SessionState mEnabledSession;
    285 
    286     /**
    287      * True if the screen is on.  The value is true initially.
    288      */
    289     boolean mScreenOn = true;
    290 
    291     AlertDialog.Builder mDialogBuilder;
    292     AlertDialog mSwitchingDialog;
    293     InputMethodInfo[] mIms;
    294     CharSequence[] mItems;
    295 
    296     class SettingsObserver extends ContentObserver {
    297         SettingsObserver(Handler handler) {
    298             super(handler);
    299             ContentResolver resolver = mContext.getContentResolver();
    300             resolver.registerContentObserver(Settings.Secure.getUriFor(
    301                     Settings.Secure.DEFAULT_INPUT_METHOD), false, this);
    302         }
    303 
    304         @Override public void onChange(boolean selfChange) {
    305             synchronized (mMethodMap) {
    306                 updateFromSettingsLocked();
    307             }
    308         }
    309     }
    310 
    311     class ScreenOnOffReceiver extends android.content.BroadcastReceiver {
    312         @Override
    313         public void onReceive(Context context, Intent intent) {
    314             if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
    315                 mScreenOn = true;
    316             } else if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
    317                 mScreenOn = false;
    318             } else if (intent.getAction().equals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) {
    319                 hideInputMethodMenu();
    320                 return;
    321             } else {
    322                 Slog.w(TAG, "Unexpected intent " + intent);
    323             }
    324 
    325             // Inform the current client of the change in active status
    326             try {
    327                 if (mCurClient != null && mCurClient.client != null) {
    328                     mCurClient.client.setActive(mScreenOn);
    329                 }
    330             } catch (RemoteException e) {
    331                 Slog.w(TAG, "Got RemoteException sending 'screen on/off' notification to pid "
    332                         + mCurClient.pid + " uid " + mCurClient.uid);
    333             }
    334         }
    335     }
    336 
    337     class MyPackageMonitor extends PackageMonitor {
    338 
    339         @Override
    340         public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
    341             synchronized (mMethodMap) {
    342                 String curInputMethodId = Settings.Secure.getString(mContext
    343                         .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
    344                 final int N = mMethodList.size();
    345                 if (curInputMethodId != null) {
    346                     for (int i=0; i<N; i++) {
    347                         InputMethodInfo imi = mMethodList.get(i);
    348                         if (imi.getId().equals(curInputMethodId)) {
    349                             for (String pkg : packages) {
    350                                 if (imi.getPackageName().equals(pkg)) {
    351                                     if (!doit) {
    352                                         return true;
    353                                     }
    354 
    355                                     Settings.Secure.putString(mContext.getContentResolver(),
    356                                             Settings.Secure.DEFAULT_INPUT_METHOD, "");
    357                                     chooseNewDefaultIMELocked();
    358                                     return true;
    359                                 }
    360                             }
    361                         }
    362                     }
    363                 }
    364             }
    365             return false;
    366         }
    367 
    368         @Override
    369         public void onSomePackagesChanged() {
    370             synchronized (mMethodMap) {
    371                 InputMethodInfo curIm = null;
    372                 String curInputMethodId = Settings.Secure.getString(mContext
    373                         .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
    374                 final int N = mMethodList.size();
    375                 if (curInputMethodId != null) {
    376                     for (int i=0; i<N; i++) {
    377                         InputMethodInfo imi = mMethodList.get(i);
    378                         if (imi.getId().equals(curInputMethodId)) {
    379                             curIm = imi;
    380                         }
    381                         int change = isPackageDisappearing(imi.getPackageName());
    382                         if (change == PACKAGE_TEMPORARY_CHANGE
    383                                 || change == PACKAGE_PERMANENT_CHANGE) {
    384                             Slog.i(TAG, "Input method uninstalled, disabling: "
    385                                     + imi.getComponent());
    386                             setInputMethodEnabledLocked(imi.getId(), false);
    387                         }
    388                     }
    389                 }
    390 
    391                 buildInputMethodListLocked(mMethodList, mMethodMap);
    392 
    393                 boolean changed = false;
    394 
    395                 if (curIm != null) {
    396                     int change = isPackageDisappearing(curIm.getPackageName());
    397                     if (change == PACKAGE_TEMPORARY_CHANGE
    398                             || change == PACKAGE_PERMANENT_CHANGE) {
    399                         ServiceInfo si = null;
    400                         try {
    401                             si = mContext.getPackageManager().getServiceInfo(
    402                                     curIm.getComponent(), 0);
    403                         } catch (PackageManager.NameNotFoundException ex) {
    404                         }
    405                         if (si == null) {
    406                             // Uh oh, current input method is no longer around!
    407                             // Pick another one...
    408                             Slog.i(TAG, "Current input method removed: " + curInputMethodId);
    409                             if (!chooseNewDefaultIMELocked()) {
    410                                 changed = true;
    411                                 curIm = null;
    412                                 curInputMethodId = "";
    413                                 Slog.i(TAG, "Unsetting current input method");
    414                                 Settings.Secure.putString(mContext.getContentResolver(),
    415                                         Settings.Secure.DEFAULT_INPUT_METHOD,
    416                                         curInputMethodId);
    417                             }
    418                         }
    419                     }
    420                 }
    421 
    422                 if (curIm == null) {
    423                     // We currently don't have a default input method... is
    424                     // one now available?
    425                     changed = chooseNewDefaultIMELocked();
    426                 }
    427 
    428                 if (changed) {
    429                     updateFromSettingsLocked();
    430                 }
    431             }
    432         }
    433     }
    434 
    435     class MethodCallback extends IInputMethodCallback.Stub {
    436         final IInputMethod mMethod;
    437 
    438         MethodCallback(IInputMethod method) {
    439             mMethod = method;
    440         }
    441 
    442         public void finishedEvent(int seq, boolean handled) throws RemoteException {
    443         }
    444 
    445         public void sessionCreated(IInputMethodSession session) throws RemoteException {
    446             onSessionCreated(mMethod, session);
    447         }
    448     }
    449 
    450     public InputMethodManagerService(Context context, StatusBarManagerService statusBar) {
    451         mContext = context;
    452         mHandler = new Handler(this);
    453         mIWindowManager = IWindowManager.Stub.asInterface(
    454                 ServiceManager.getService(Context.WINDOW_SERVICE));
    455         mCaller = new HandlerCaller(context, new HandlerCaller.Callback() {
    456             public void executeMessage(Message msg) {
    457                 handleMessage(msg);
    458             }
    459         });
    460 
    461         (new MyPackageMonitor()).register(mContext, true);
    462 
    463         IntentFilter screenOnOffFilt = new IntentFilter();
    464         screenOnOffFilt.addAction(Intent.ACTION_SCREEN_ON);
    465         screenOnOffFilt.addAction(Intent.ACTION_SCREEN_OFF);
    466         screenOnOffFilt.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
    467         mContext.registerReceiver(new ScreenOnOffReceiver(), screenOnOffFilt);
    468 
    469         mStatusBar = statusBar;
    470         statusBar.setIconVisibility("ime", false);
    471 
    472         buildInputMethodListLocked(mMethodList, mMethodMap);
    473 
    474         final String enabledStr = Settings.Secure.getString(
    475                 mContext.getContentResolver(),
    476                 Settings.Secure.ENABLED_INPUT_METHODS);
    477         Slog.i(TAG, "Enabled input methods: " + enabledStr);
    478         final String defaultIme = Settings.Secure.getString(mContext
    479                 .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
    480         if (enabledStr == null || TextUtils.isEmpty(defaultIme)) {
    481             Slog.i(TAG, "Enabled input methods or default IME has not been set, enabling all");
    482             InputMethodInfo defIm = null;
    483             StringBuilder sb = new StringBuilder(256);
    484             final int N = mMethodList.size();
    485             for (int i=0; i<N; i++) {
    486                 InputMethodInfo imi = mMethodList.get(i);
    487                 Slog.i(TAG, "Adding: " + imi.getId());
    488                 if (i > 0) sb.append(':');
    489                 sb.append(imi.getId());
    490                 if (defIm == null && imi.getIsDefaultResourceId() != 0) {
    491                     try {
    492                         Resources res = mContext.createPackageContext(
    493                                 imi.getPackageName(), 0).getResources();
    494                         if (res.getBoolean(imi.getIsDefaultResourceId())) {
    495                             defIm = imi;
    496                             Slog.i(TAG, "Selected default: " + imi.getId());
    497                         }
    498                     } catch (PackageManager.NameNotFoundException ex) {
    499                     } catch (Resources.NotFoundException ex) {
    500                     }
    501                 }
    502             }
    503             if (defIm == null && N > 0) {
    504                 defIm = mMethodList.get(0);
    505                 Slog.i(TAG, "No default found, using " + defIm.getId());
    506             }
    507             Settings.Secure.putString(mContext.getContentResolver(),
    508                     Settings.Secure.ENABLED_INPUT_METHODS, sb.toString());
    509             if (defIm != null) {
    510                 Settings.Secure.putString(mContext.getContentResolver(),
    511                         Settings.Secure.DEFAULT_INPUT_METHOD, defIm.getId());
    512             }
    513         }
    514 
    515         mSettingsObserver = new SettingsObserver(mHandler);
    516         updateFromSettingsLocked();
    517     }
    518 
    519     @Override
    520     public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
    521             throws RemoteException {
    522         try {
    523             return super.onTransact(code, data, reply, flags);
    524         } catch (RuntimeException e) {
    525             // The input method manager only throws security exceptions, so let's
    526             // log all others.
    527             if (!(e instanceof SecurityException)) {
    528                 Slog.e(TAG, "Input Method Manager Crash", e);
    529             }
    530             throw e;
    531         }
    532     }
    533 
    534     public void systemReady() {
    535         synchronized (mMethodMap) {
    536             if (!mSystemReady) {
    537                 mSystemReady = true;
    538                 try {
    539                     startInputInnerLocked();
    540                 } catch (RuntimeException e) {
    541                     Slog.w(TAG, "Unexpected exception", e);
    542                 }
    543             }
    544         }
    545     }
    546 
    547     public List<InputMethodInfo> getInputMethodList() {
    548         synchronized (mMethodMap) {
    549             return new ArrayList<InputMethodInfo>(mMethodList);
    550         }
    551     }
    552 
    553     public List<InputMethodInfo> getEnabledInputMethodList() {
    554         synchronized (mMethodMap) {
    555             return getEnabledInputMethodListLocked();
    556         }
    557     }
    558 
    559     List<InputMethodInfo> getEnabledInputMethodListLocked() {
    560         final ArrayList<InputMethodInfo> res = new ArrayList<InputMethodInfo>();
    561 
    562         final String enabledStr = Settings.Secure.getString(
    563                 mContext.getContentResolver(),
    564                 Settings.Secure.ENABLED_INPUT_METHODS);
    565         if (enabledStr != null) {
    566             final TextUtils.SimpleStringSplitter splitter = mStringColonSplitter;
    567             splitter.setString(enabledStr);
    568 
    569             while (splitter.hasNext()) {
    570                 InputMethodInfo info = mMethodMap.get(splitter.next());
    571                 if (info != null) {
    572                     res.add(info);
    573                 }
    574             }
    575         }
    576 
    577         return res;
    578     }
    579 
    580     public void addClient(IInputMethodClient client,
    581             IInputContext inputContext, int uid, int pid) {
    582         synchronized (mMethodMap) {
    583             mClients.put(client.asBinder(), new ClientState(client,
    584                     inputContext, uid, pid));
    585         }
    586     }
    587 
    588     public void removeClient(IInputMethodClient client) {
    589         synchronized (mMethodMap) {
    590             mClients.remove(client.asBinder());
    591         }
    592     }
    593 
    594     void executeOrSendMessage(IInterface target, Message msg) {
    595          if (target.asBinder() instanceof Binder) {
    596              mCaller.sendMessage(msg);
    597          } else {
    598              handleMessage(msg);
    599              msg.recycle();
    600          }
    601     }
    602 
    603     void unbindCurrentClientLocked() {
    604         if (mCurClient != null) {
    605             if (DEBUG) Slog.v(TAG, "unbindCurrentInputLocked: client = "
    606                     + mCurClient.client.asBinder());
    607             if (mBoundToMethod) {
    608                 mBoundToMethod = false;
    609                 if (mCurMethod != null) {
    610                     executeOrSendMessage(mCurMethod, mCaller.obtainMessageO(
    611                             MSG_UNBIND_INPUT, mCurMethod));
    612                 }
    613             }
    614             executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
    615                     MSG_UNBIND_METHOD, mCurSeq, mCurClient.client));
    616             mCurClient.sessionRequested = false;
    617 
    618             // Call setActive(false) on the old client
    619             try {
    620                 mCurClient.client.setActive(false);
    621             } catch (RemoteException e) {
    622                 Slog.w(TAG, "Got RemoteException sending setActive(false) notification to pid "
    623                         + mCurClient.pid + " uid " + mCurClient.uid);
    624             }
    625             mCurClient = null;
    626 
    627             hideInputMethodMenuLocked();
    628         }
    629     }
    630 
    631     private int getImeShowFlags() {
    632         int flags = 0;
    633         if (mShowForced) {
    634             flags |= InputMethod.SHOW_FORCED
    635                     | InputMethod.SHOW_EXPLICIT;
    636         } else if (mShowExplicitlyRequested) {
    637             flags |= InputMethod.SHOW_EXPLICIT;
    638         }
    639         return flags;
    640     }
    641 
    642     private int getAppShowFlags() {
    643         int flags = 0;
    644         if (mShowForced) {
    645             flags |= InputMethodManager.SHOW_FORCED;
    646         } else if (!mShowExplicitlyRequested) {
    647             flags |= InputMethodManager.SHOW_IMPLICIT;
    648         }
    649         return flags;
    650     }
    651 
    652     InputBindResult attachNewInputLocked(boolean initial, boolean needResult) {
    653         if (!mBoundToMethod) {
    654             executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
    655                     MSG_BIND_INPUT, mCurMethod, mCurClient.binding));
    656             mBoundToMethod = true;
    657         }
    658         final SessionState session = mCurClient.curSession;
    659         if (initial) {
    660             executeOrSendMessage(session.method, mCaller.obtainMessageOOO(
    661                     MSG_START_INPUT, session, mCurInputContext, mCurAttribute));
    662         } else {
    663             executeOrSendMessage(session.method, mCaller.obtainMessageOOO(
    664                     MSG_RESTART_INPUT, session, mCurInputContext, mCurAttribute));
    665         }
    666         if (mShowRequested) {
    667             if (DEBUG) Slog.v(TAG, "Attach new input asks to show input");
    668             showCurrentInputLocked(getAppShowFlags(), null);
    669         }
    670         return needResult
    671                 ? new InputBindResult(session.session, mCurId, mCurSeq)
    672                 : null;
    673     }
    674 
    675     InputBindResult startInputLocked(IInputMethodClient client,
    676             IInputContext inputContext, EditorInfo attribute,
    677             boolean initial, boolean needResult) {
    678         // If no method is currently selected, do nothing.
    679         if (mCurMethodId == null) {
    680             return mNoBinding;
    681         }
    682 
    683         ClientState cs = mClients.get(client.asBinder());
    684         if (cs == null) {
    685             throw new IllegalArgumentException("unknown client "
    686                     + client.asBinder());
    687         }
    688 
    689         try {
    690             if (!mIWindowManager.inputMethodClientHasFocus(cs.client)) {
    691                 // Check with the window manager to make sure this client actually
    692                 // has a window with focus.  If not, reject.  This is thread safe
    693                 // because if the focus changes some time before or after, the
    694                 // next client receiving focus that has any interest in input will
    695                 // be calling through here after that change happens.
    696                 Slog.w(TAG, "Starting input on non-focused client " + cs.client
    697                         + " (uid=" + cs.uid + " pid=" + cs.pid + ")");
    698                 return null;
    699             }
    700         } catch (RemoteException e) {
    701         }
    702 
    703         if (mCurClient != cs) {
    704             // If the client is changing, we need to switch over to the new
    705             // one.
    706             unbindCurrentClientLocked();
    707             if (DEBUG) Slog.v(TAG, "switching to client: client = "
    708                     + cs.client.asBinder());
    709 
    710             // If the screen is on, inform the new client it is active
    711             if (mScreenOn) {
    712                 try {
    713                     cs.client.setActive(mScreenOn);
    714                 } catch (RemoteException e) {
    715                     Slog.w(TAG, "Got RemoteException sending setActive notification to pid "
    716                             + cs.pid + " uid " + cs.uid);
    717                 }
    718             }
    719         }
    720 
    721         // Bump up the sequence for this client and attach it.
    722         mCurSeq++;
    723         if (mCurSeq <= 0) mCurSeq = 1;
    724         mCurClient = cs;
    725         mCurInputContext = inputContext;
    726         mCurAttribute = attribute;
    727 
    728         // Check if the input method is changing.
    729         if (mCurId != null && mCurId.equals(mCurMethodId)) {
    730             if (cs.curSession != null) {
    731                 // Fast case: if we are already connected to the input method,
    732                 // then just return it.
    733                 return attachNewInputLocked(initial, needResult);
    734             }
    735             if (mHaveConnection) {
    736                 if (mCurMethod != null) {
    737                     if (!cs.sessionRequested) {
    738                         cs.sessionRequested = true;
    739                         if (DEBUG) Slog.v(TAG, "Creating new session for client " + cs);
    740                         executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
    741                                 MSG_CREATE_SESSION, mCurMethod,
    742                                 new MethodCallback(mCurMethod)));
    743                     }
    744                     // Return to client, and we will get back with it when
    745                     // we have had a session made for it.
    746                     return new InputBindResult(null, mCurId, mCurSeq);
    747                 } else if (SystemClock.uptimeMillis()
    748                         < (mLastBindTime+TIME_TO_RECONNECT)) {
    749                     // In this case we have connected to the service, but
    750                     // don't yet have its interface.  If it hasn't been too
    751                     // long since we did the connection, we'll return to
    752                     // the client and wait to get the service interface so
    753                     // we can report back.  If it has been too long, we want
    754                     // to fall through so we can try a disconnect/reconnect
    755                     // to see if we can get back in touch with the service.
    756                     return new InputBindResult(null, mCurId, mCurSeq);
    757                 } else {
    758                     EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME,
    759                             mCurMethodId, SystemClock.uptimeMillis()-mLastBindTime, 0);
    760                 }
    761             }
    762         }
    763 
    764         return startInputInnerLocked();
    765     }
    766 
    767     InputBindResult startInputInnerLocked() {
    768         if (mCurMethodId == null) {
    769             return mNoBinding;
    770         }
    771 
    772         if (!mSystemReady) {
    773             // If the system is not yet ready, we shouldn't be running third
    774             // party code.
    775             return new InputBindResult(null, mCurMethodId, mCurSeq);
    776         }
    777 
    778         InputMethodInfo info = mMethodMap.get(mCurMethodId);
    779         if (info == null) {
    780             throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
    781         }
    782 
    783         unbindCurrentMethodLocked(false);
    784 
    785         mCurIntent = new Intent(InputMethod.SERVICE_INTERFACE);
    786         mCurIntent.setComponent(info.getComponent());
    787         mCurIntent.putExtra(Intent.EXTRA_CLIENT_LABEL,
    788                 com.android.internal.R.string.input_method_binding_label);
    789         mCurIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
    790                 mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), 0));
    791         if (mContext.bindService(mCurIntent, this, Context.BIND_AUTO_CREATE)) {
    792             mLastBindTime = SystemClock.uptimeMillis();
    793             mHaveConnection = true;
    794             mCurId = info.getId();
    795             mCurToken = new Binder();
    796             try {
    797                 if (DEBUG) Slog.v(TAG, "Adding window token: " + mCurToken);
    798                 mIWindowManager.addWindowToken(mCurToken,
    799                         WindowManager.LayoutParams.TYPE_INPUT_METHOD);
    800             } catch (RemoteException e) {
    801             }
    802             return new InputBindResult(null, mCurId, mCurSeq);
    803         } else {
    804             mCurIntent = null;
    805             Slog.w(TAG, "Failure connecting to input method service: "
    806                     + mCurIntent);
    807         }
    808         return null;
    809     }
    810 
    811     public InputBindResult startInput(IInputMethodClient client,
    812             IInputContext inputContext, EditorInfo attribute,
    813             boolean initial, boolean needResult) {
    814         synchronized (mMethodMap) {
    815             final long ident = Binder.clearCallingIdentity();
    816             try {
    817                 return startInputLocked(client, inputContext, attribute,
    818                         initial, needResult);
    819             } finally {
    820                 Binder.restoreCallingIdentity(ident);
    821             }
    822         }
    823     }
    824 
    825     public void finishInput(IInputMethodClient client) {
    826     }
    827 
    828     public void onServiceConnected(ComponentName name, IBinder service) {
    829         synchronized (mMethodMap) {
    830             if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
    831                 mCurMethod = IInputMethod.Stub.asInterface(service);
    832                 if (mCurToken == null) {
    833                     Slog.w(TAG, "Service connected without a token!");
    834                     unbindCurrentMethodLocked(false);
    835                     return;
    836                 }
    837                 if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
    838                 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
    839                         MSG_ATTACH_TOKEN, mCurMethod, mCurToken));
    840                 if (mCurClient != null) {
    841                     if (DEBUG) Slog.v(TAG, "Creating first session while with client "
    842                             + mCurClient);
    843                     executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
    844                             MSG_CREATE_SESSION, mCurMethod,
    845                             new MethodCallback(mCurMethod)));
    846                 }
    847             }
    848         }
    849     }
    850 
    851     void onSessionCreated(IInputMethod method, IInputMethodSession session) {
    852         synchronized (mMethodMap) {
    853             if (mCurMethod != null && method != null
    854                     && mCurMethod.asBinder() == method.asBinder()) {
    855                 if (mCurClient != null) {
    856                     mCurClient.curSession = new SessionState(mCurClient,
    857                             method, session);
    858                     mCurClient.sessionRequested = false;
    859                     InputBindResult res = attachNewInputLocked(true, true);
    860                     if (res.method != null) {
    861                         executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO(
    862                                 MSG_BIND_METHOD, mCurClient.client, res));
    863                     }
    864                 }
    865             }
    866         }
    867     }
    868 
    869     void unbindCurrentMethodLocked(boolean reportToClient) {
    870         if (mHaveConnection) {
    871             mContext.unbindService(this);
    872             mHaveConnection = false;
    873         }
    874 
    875         if (mCurToken != null) {
    876             try {
    877                 if (DEBUG) Slog.v(TAG, "Removing window token: " + mCurToken);
    878                 mIWindowManager.removeWindowToken(mCurToken);
    879             } catch (RemoteException e) {
    880             }
    881             mCurToken = null;
    882         }
    883 
    884         mCurId = null;
    885         clearCurMethodLocked();
    886 
    887         if (reportToClient && mCurClient != null) {
    888             executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
    889                     MSG_UNBIND_METHOD, mCurSeq, mCurClient.client));
    890         }
    891     }
    892 
    893     private void finishSession(SessionState sessionState) {
    894         if (sessionState != null && sessionState.session != null) {
    895             try {
    896                 sessionState.session.finishSession();
    897             } catch (RemoteException e) {
    898                 Slog.w(TAG, "Session failed to close due to remote exception", e);
    899             }
    900         }
    901     }
    902 
    903     void clearCurMethodLocked() {
    904         if (mCurMethod != null) {
    905             for (ClientState cs : mClients.values()) {
    906                 cs.sessionRequested = false;
    907                 finishSession(cs.curSession);
    908                 cs.curSession = null;
    909             }
    910 
    911             finishSession(mEnabledSession);
    912             mEnabledSession = null;
    913             mCurMethod = null;
    914         }
    915         mStatusBar.setIconVisibility("ime", false);
    916     }
    917 
    918     public void onServiceDisconnected(ComponentName name) {
    919         synchronized (mMethodMap) {
    920             if (DEBUG) Slog.v(TAG, "Service disconnected: " + name
    921                     + " mCurIntent=" + mCurIntent);
    922             if (mCurMethod != null && mCurIntent != null
    923                     && name.equals(mCurIntent.getComponent())) {
    924                 clearCurMethodLocked();
    925                 // We consider this to be a new bind attempt, since the system
    926                 // should now try to restart the service for us.
    927                 mLastBindTime = SystemClock.uptimeMillis();
    928                 mShowRequested = mInputShown;
    929                 mInputShown = false;
    930                 if (mCurClient != null) {
    931                     executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
    932                             MSG_UNBIND_METHOD, mCurSeq, mCurClient.client));
    933                 }
    934             }
    935         }
    936     }
    937 
    938     public void updateStatusIcon(IBinder token, String packageName, int iconId) {
    939         int uid = Binder.getCallingUid();
    940         long ident = Binder.clearCallingIdentity();
    941         try {
    942             if (token == null || mCurToken != token) {
    943                 Slog.w(TAG, "Ignoring setInputMethod of uid " + uid + " token: " + token);
    944                 return;
    945             }
    946 
    947             synchronized (mMethodMap) {
    948                 if (iconId == 0) {
    949                     if (DEBUG) Slog.d(TAG, "hide the small icon for the input method");
    950                     mStatusBar.setIconVisibility("ime", false);
    951                 } else if (packageName != null) {
    952                     if (DEBUG) Slog.d(TAG, "show a small icon for the input method");
    953                     mStatusBar.setIcon("ime", packageName, iconId, 0);
    954                     mStatusBar.setIconVisibility("ime", true);
    955                 }
    956             }
    957         } finally {
    958             Binder.restoreCallingIdentity(ident);
    959         }
    960     }
    961 
    962     void updateFromSettingsLocked() {
    963         // We are assuming that whoever is changing DEFAULT_INPUT_METHOD and
    964         // ENABLED_INPUT_METHODS is taking care of keeping them correctly in
    965         // sync, so we will never have a DEFAULT_INPUT_METHOD that is not
    966         // enabled.
    967         String id = Settings.Secure.getString(mContext.getContentResolver(),
    968             Settings.Secure.DEFAULT_INPUT_METHOD);
    969         if (id != null && id.length() > 0) {
    970             try {
    971                 setInputMethodLocked(id);
    972             } catch (IllegalArgumentException e) {
    973                 Slog.w(TAG, "Unknown input method from prefs: " + id, e);
    974                 mCurMethodId = null;
    975                 unbindCurrentMethodLocked(true);
    976             }
    977         } else {
    978             // There is no longer an input method set, so stop any current one.
    979             mCurMethodId = null;
    980             unbindCurrentMethodLocked(true);
    981         }
    982     }
    983 
    984     void setInputMethodLocked(String id) {
    985         InputMethodInfo info = mMethodMap.get(id);
    986         if (info == null) {
    987             throw new IllegalArgumentException("Unknown id: " + id);
    988         }
    989 
    990         if (id.equals(mCurMethodId)) {
    991             return;
    992         }
    993 
    994         final long ident = Binder.clearCallingIdentity();
    995         try {
    996             mCurMethodId = id;
    997             Settings.Secure.putString(mContext.getContentResolver(),
    998                 Settings.Secure.DEFAULT_INPUT_METHOD, id);
    999 
   1000             if (ActivityManagerNative.isSystemReady()) {
   1001                 Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED);
   1002                 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
   1003                 intent.putExtra("input_method_id", id);
   1004                 mContext.sendBroadcast(intent);
   1005             }
   1006             unbindCurrentClientLocked();
   1007         } finally {
   1008             Binder.restoreCallingIdentity(ident);
   1009         }
   1010     }
   1011 
   1012     public boolean showSoftInput(IInputMethodClient client, int flags,
   1013             ResultReceiver resultReceiver) {
   1014         int uid = Binder.getCallingUid();
   1015         long ident = Binder.clearCallingIdentity();
   1016         try {
   1017             synchronized (mMethodMap) {
   1018                 if (mCurClient == null || client == null
   1019                         || mCurClient.client.asBinder() != client.asBinder()) {
   1020                     try {
   1021                         // We need to check if this is the current client with
   1022                         // focus in the window manager, to allow this call to
   1023                         // be made before input is started in it.
   1024                         if (!mIWindowManager.inputMethodClientHasFocus(client)) {
   1025                             Slog.w(TAG, "Ignoring showSoftInput of uid " + uid + ": " + client);
   1026                             return false;
   1027                         }
   1028                     } catch (RemoteException e) {
   1029                         return false;
   1030                     }
   1031                 }
   1032 
   1033                 if (DEBUG) Slog.v(TAG, "Client requesting input be shown");
   1034                 return showCurrentInputLocked(flags, resultReceiver);
   1035             }
   1036         } finally {
   1037             Binder.restoreCallingIdentity(ident);
   1038         }
   1039     }
   1040 
   1041     boolean showCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
   1042         mShowRequested = true;
   1043         if ((flags&InputMethodManager.SHOW_IMPLICIT) == 0) {
   1044             mShowExplicitlyRequested = true;
   1045         }
   1046         if ((flags&InputMethodManager.SHOW_FORCED) != 0) {
   1047             mShowExplicitlyRequested = true;
   1048             mShowForced = true;
   1049         }
   1050 
   1051         if (!mSystemReady) {
   1052             return false;
   1053         }
   1054 
   1055         boolean res = false;
   1056         if (mCurMethod != null) {
   1057             executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO(
   1058                     MSG_SHOW_SOFT_INPUT, getImeShowFlags(), mCurMethod,
   1059                     resultReceiver));
   1060             mInputShown = true;
   1061             res = true;
   1062         } else if (mHaveConnection && SystemClock.uptimeMillis()
   1063                 < (mLastBindTime+TIME_TO_RECONNECT)) {
   1064             // The client has asked to have the input method shown, but
   1065             // we have been sitting here too long with a connection to the
   1066             // service and no interface received, so let's disconnect/connect
   1067             // to try to prod things along.
   1068             EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, mCurMethodId,
   1069                     SystemClock.uptimeMillis()-mLastBindTime,1);
   1070             mContext.unbindService(this);
   1071             mContext.bindService(mCurIntent, this, Context.BIND_AUTO_CREATE);
   1072         }
   1073 
   1074         return res;
   1075     }
   1076 
   1077     public boolean hideSoftInput(IInputMethodClient client, int flags,
   1078             ResultReceiver resultReceiver) {
   1079         int uid = Binder.getCallingUid();
   1080         long ident = Binder.clearCallingIdentity();
   1081         try {
   1082             synchronized (mMethodMap) {
   1083                 if (mCurClient == null || client == null
   1084                         || mCurClient.client.asBinder() != client.asBinder()) {
   1085                     try {
   1086                         // We need to check if this is the current client with
   1087                         // focus in the window manager, to allow this call to
   1088                         // be made before input is started in it.
   1089                         if (!mIWindowManager.inputMethodClientHasFocus(client)) {
   1090                             if (DEBUG) Slog.w(TAG, "Ignoring hideSoftInput of uid "
   1091                                     + uid + ": " + client);
   1092                             return false;
   1093                         }
   1094                     } catch (RemoteException e) {
   1095                         return false;
   1096                     }
   1097                 }
   1098 
   1099                 if (DEBUG) Slog.v(TAG, "Client requesting input be hidden");
   1100                 return hideCurrentInputLocked(flags, resultReceiver);
   1101             }
   1102         } finally {
   1103             Binder.restoreCallingIdentity(ident);
   1104         }
   1105     }
   1106 
   1107     boolean hideCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
   1108         if ((flags&InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
   1109                 && (mShowExplicitlyRequested || mShowForced)) {
   1110             if (DEBUG) Slog.v(TAG,
   1111                     "Not hiding: explicit show not cancelled by non-explicit hide");
   1112             return false;
   1113         }
   1114         if (mShowForced && (flags&InputMethodManager.HIDE_NOT_ALWAYS) != 0) {
   1115             if (DEBUG) Slog.v(TAG,
   1116                     "Not hiding: forced show not cancelled by not-always hide");
   1117             return false;
   1118         }
   1119         boolean res;
   1120         if (mInputShown && mCurMethod != null) {
   1121             executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
   1122                     MSG_HIDE_SOFT_INPUT, mCurMethod, resultReceiver));
   1123             res = true;
   1124         } else {
   1125             res = false;
   1126         }
   1127         mInputShown = false;
   1128         mShowRequested = false;
   1129         mShowExplicitlyRequested = false;
   1130         mShowForced = false;
   1131         return res;
   1132     }
   1133 
   1134     public void windowGainedFocus(IInputMethodClient client, IBinder windowToken,
   1135             boolean viewHasFocus, boolean isTextEditor, int softInputMode,
   1136             boolean first, int windowFlags) {
   1137         long ident = Binder.clearCallingIdentity();
   1138         try {
   1139             synchronized (mMethodMap) {
   1140                 if (DEBUG) Slog.v(TAG, "windowGainedFocus: " + client.asBinder()
   1141                         + " viewHasFocus=" + viewHasFocus
   1142                         + " isTextEditor=" + isTextEditor
   1143                         + " softInputMode=#" + Integer.toHexString(softInputMode)
   1144                         + " first=" + first + " flags=#"
   1145                         + Integer.toHexString(windowFlags));
   1146 
   1147                 if (mCurClient == null || client == null
   1148                         || mCurClient.client.asBinder() != client.asBinder()) {
   1149                     try {
   1150                         // We need to check if this is the current client with
   1151                         // focus in the window manager, to allow this call to
   1152                         // be made before input is started in it.
   1153                         if (!mIWindowManager.inputMethodClientHasFocus(client)) {
   1154                             Slog.w(TAG, "Client not active, ignoring focus gain of: " + client);
   1155                             return;
   1156                         }
   1157                     } catch (RemoteException e) {
   1158                     }
   1159                 }
   1160 
   1161                 if (mCurFocusedWindow == windowToken) {
   1162                     Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client);
   1163                     return;
   1164                 }
   1165                 mCurFocusedWindow = windowToken;
   1166 
   1167                 switch (softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) {
   1168                     case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED:
   1169                         if (!isTextEditor || (softInputMode &
   1170                                 WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
   1171                                 != WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) {
   1172                             if (WindowManager.LayoutParams.mayUseInputMethod(windowFlags)) {
   1173                                 // There is no focus view, and this window will
   1174                                 // be behind any soft input window, so hide the
   1175                                 // soft input window if it is shown.
   1176                                 if (DEBUG) Slog.v(TAG, "Unspecified window will hide input");
   1177                                 hideCurrentInputLocked(InputMethodManager.HIDE_NOT_ALWAYS, null);
   1178                             }
   1179                         } else if (isTextEditor && (softInputMode &
   1180                                 WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
   1181                                 == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
   1182                                 && (softInputMode &
   1183                                         WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
   1184                             // There is a focus view, and we are navigating forward
   1185                             // into the window, so show the input window for the user.
   1186                             if (DEBUG) Slog.v(TAG, "Unspecified window will show input");
   1187                             showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
   1188                         }
   1189                         break;
   1190                     case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED:
   1191                         // Do nothing.
   1192                         break;
   1193                     case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN:
   1194                         if ((softInputMode &
   1195                                 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
   1196                             if (DEBUG) Slog.v(TAG, "Window asks to hide input going forward");
   1197                             hideCurrentInputLocked(0, null);
   1198                         }
   1199                         break;
   1200                     case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN:
   1201                         if (DEBUG) Slog.v(TAG, "Window asks to hide input");
   1202                         hideCurrentInputLocked(0, null);
   1203                         break;
   1204                     case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE:
   1205                         if ((softInputMode &
   1206                                 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
   1207                             if (DEBUG) Slog.v(TAG, "Window asks to show input going forward");
   1208                             showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
   1209                         }
   1210                         break;
   1211                     case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE:
   1212                         if (DEBUG) Slog.v(TAG, "Window asks to always show input");
   1213                         showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
   1214                         break;
   1215                 }
   1216             }
   1217         } finally {
   1218             Binder.restoreCallingIdentity(ident);
   1219         }
   1220     }
   1221 
   1222     public void showInputMethodPickerFromClient(IInputMethodClient client) {
   1223         synchronized (mMethodMap) {
   1224             if (mCurClient == null || client == null
   1225                     || mCurClient.client.asBinder() != client.asBinder()) {
   1226                 Slog.w(TAG, "Ignoring showInputMethodDialogFromClient of uid "
   1227                         + Binder.getCallingUid() + ": " + client);
   1228             }
   1229 
   1230             mHandler.sendEmptyMessage(MSG_SHOW_IM_PICKER);
   1231         }
   1232     }
   1233 
   1234     public void setInputMethod(IBinder token, String id) {
   1235         synchronized (mMethodMap) {
   1236             if (token == null) {
   1237                 if (mContext.checkCallingOrSelfPermission(
   1238                         android.Manifest.permission.WRITE_SECURE_SETTINGS)
   1239                         != PackageManager.PERMISSION_GRANTED) {
   1240                     throw new SecurityException(
   1241                             "Using null token requires permission "
   1242                             + android.Manifest.permission.WRITE_SECURE_SETTINGS);
   1243                 }
   1244             } else if (mCurToken != token) {
   1245                 Slog.w(TAG, "Ignoring setInputMethod of uid " + Binder.getCallingUid()
   1246                         + " token: " + token);
   1247                 return;
   1248             }
   1249 
   1250             long ident = Binder.clearCallingIdentity();
   1251             try {
   1252                 setInputMethodLocked(id);
   1253             } finally {
   1254                 Binder.restoreCallingIdentity(ident);
   1255             }
   1256         }
   1257     }
   1258 
   1259     public void hideMySoftInput(IBinder token, int flags) {
   1260         synchronized (mMethodMap) {
   1261             if (token == null || mCurToken != token) {
   1262                 if (DEBUG) Slog.w(TAG, "Ignoring hideInputMethod of uid "
   1263                         + Binder.getCallingUid() + " token: " + token);
   1264                 return;
   1265             }
   1266             long ident = Binder.clearCallingIdentity();
   1267             try {
   1268                 hideCurrentInputLocked(flags, null);
   1269             } finally {
   1270                 Binder.restoreCallingIdentity(ident);
   1271             }
   1272         }
   1273     }
   1274 
   1275     public void showMySoftInput(IBinder token, int flags) {
   1276         synchronized (mMethodMap) {
   1277             if (token == null || mCurToken != token) {
   1278                 Slog.w(TAG, "Ignoring showMySoftInput of uid "
   1279                         + Binder.getCallingUid() + " token: " + token);
   1280                 return;
   1281             }
   1282             long ident = Binder.clearCallingIdentity();
   1283             try {
   1284                 showCurrentInputLocked(flags, null);
   1285             } finally {
   1286                 Binder.restoreCallingIdentity(ident);
   1287             }
   1288         }
   1289     }
   1290 
   1291     void setEnabledSessionInMainThread(SessionState session) {
   1292         if (mEnabledSession != session) {
   1293             if (mEnabledSession != null) {
   1294                 try {
   1295                     if (DEBUG) Slog.v(TAG, "Disabling: " + mEnabledSession);
   1296                     mEnabledSession.method.setSessionEnabled(
   1297                             mEnabledSession.session, false);
   1298                 } catch (RemoteException e) {
   1299                 }
   1300             }
   1301             mEnabledSession = session;
   1302             try {
   1303                 if (DEBUG) Slog.v(TAG, "Enabling: " + mEnabledSession);
   1304                 session.method.setSessionEnabled(
   1305                         session.session, true);
   1306             } catch (RemoteException e) {
   1307             }
   1308         }
   1309     }
   1310 
   1311     public boolean handleMessage(Message msg) {
   1312         HandlerCaller.SomeArgs args;
   1313         switch (msg.what) {
   1314             case MSG_SHOW_IM_PICKER:
   1315                 showInputMethodMenu();
   1316                 return true;
   1317 
   1318             // ---------------------------------------------------------
   1319 
   1320             case MSG_UNBIND_INPUT:
   1321                 try {
   1322                     ((IInputMethod)msg.obj).unbindInput();
   1323                 } catch (RemoteException e) {
   1324                     // There is nothing interesting about the method dying.
   1325                 }
   1326                 return true;
   1327             case MSG_BIND_INPUT:
   1328                 args = (HandlerCaller.SomeArgs)msg.obj;
   1329                 try {
   1330                     ((IInputMethod)args.arg1).bindInput((InputBinding)args.arg2);
   1331                 } catch (RemoteException e) {
   1332                 }
   1333                 return true;
   1334             case MSG_SHOW_SOFT_INPUT:
   1335                 args = (HandlerCaller.SomeArgs)msg.obj;
   1336                 try {
   1337                     ((IInputMethod)args.arg1).showSoftInput(msg.arg1,
   1338                             (ResultReceiver)args.arg2);
   1339                 } catch (RemoteException e) {
   1340                 }
   1341                 return true;
   1342             case MSG_HIDE_SOFT_INPUT:
   1343                 args = (HandlerCaller.SomeArgs)msg.obj;
   1344                 try {
   1345                     ((IInputMethod)args.arg1).hideSoftInput(0,
   1346                             (ResultReceiver)args.arg2);
   1347                 } catch (RemoteException e) {
   1348                 }
   1349                 return true;
   1350             case MSG_ATTACH_TOKEN:
   1351                 args = (HandlerCaller.SomeArgs)msg.obj;
   1352                 try {
   1353                     if (DEBUG) Slog.v(TAG, "Sending attach of token: " + args.arg2);
   1354                     ((IInputMethod)args.arg1).attachToken((IBinder)args.arg2);
   1355                 } catch (RemoteException e) {
   1356                 }
   1357                 return true;
   1358             case MSG_CREATE_SESSION:
   1359                 args = (HandlerCaller.SomeArgs)msg.obj;
   1360                 try {
   1361                     ((IInputMethod)args.arg1).createSession(
   1362                             (IInputMethodCallback)args.arg2);
   1363                 } catch (RemoteException e) {
   1364                 }
   1365                 return true;
   1366             // ---------------------------------------------------------
   1367 
   1368             case MSG_START_INPUT:
   1369                 args = (HandlerCaller.SomeArgs)msg.obj;
   1370                 try {
   1371                     SessionState session = (SessionState)args.arg1;
   1372                     setEnabledSessionInMainThread(session);
   1373                     session.method.startInput((IInputContext)args.arg2,
   1374                             (EditorInfo)args.arg3);
   1375                 } catch (RemoteException e) {
   1376                 }
   1377                 return true;
   1378             case MSG_RESTART_INPUT:
   1379                 args = (HandlerCaller.SomeArgs)msg.obj;
   1380                 try {
   1381                     SessionState session = (SessionState)args.arg1;
   1382                     setEnabledSessionInMainThread(session);
   1383                     session.method.restartInput((IInputContext)args.arg2,
   1384                             (EditorInfo)args.arg3);
   1385                 } catch (RemoteException e) {
   1386                 }
   1387                 return true;
   1388 
   1389             // ---------------------------------------------------------
   1390 
   1391             case MSG_UNBIND_METHOD:
   1392                 try {
   1393                     ((IInputMethodClient)msg.obj).onUnbindMethod(msg.arg1);
   1394                 } catch (RemoteException e) {
   1395                     // There is nothing interesting about the last client dying.
   1396                 }
   1397                 return true;
   1398             case MSG_BIND_METHOD:
   1399                 args = (HandlerCaller.SomeArgs)msg.obj;
   1400                 try {
   1401                     ((IInputMethodClient)args.arg1).onBindMethod(
   1402                             (InputBindResult)args.arg2);
   1403                 } catch (RemoteException e) {
   1404                     Slog.w(TAG, "Client died receiving input method " + args.arg2);
   1405                 }
   1406                 return true;
   1407         }
   1408         return false;
   1409     }
   1410 
   1411     private boolean isSystemIme(InputMethodInfo inputMethod) {
   1412         return (inputMethod.getServiceInfo().applicationInfo.flags
   1413                 & ApplicationInfo.FLAG_SYSTEM) != 0;
   1414     }
   1415 
   1416     private boolean chooseNewDefaultIMELocked() {
   1417         List<InputMethodInfo> enabled = getEnabledInputMethodListLocked();
   1418         if (enabled != null && enabled.size() > 0) {
   1419             // We'd prefer to fall back on a system IME, since that is safer.
   1420             int i=enabled.size();
   1421             while (i > 0) {
   1422                 i--;
   1423                 if ((enabled.get(i).getServiceInfo().applicationInfo.flags
   1424                         & ApplicationInfo.FLAG_SYSTEM) != 0) {
   1425                     break;
   1426                 }
   1427             }
   1428             Settings.Secure.putString(mContext.getContentResolver(),
   1429                     Settings.Secure.DEFAULT_INPUT_METHOD,
   1430                     enabled.get(i).getId());
   1431             return true;
   1432         }
   1433 
   1434         return false;
   1435     }
   1436 
   1437     void buildInputMethodListLocked(ArrayList<InputMethodInfo> list,
   1438             HashMap<String, InputMethodInfo> map) {
   1439         list.clear();
   1440         map.clear();
   1441 
   1442         PackageManager pm = mContext.getPackageManager();
   1443         final Configuration config = mContext.getResources().getConfiguration();
   1444         final boolean haveHardKeyboard = config.keyboard == Configuration.KEYBOARD_QWERTY;
   1445         String disabledSysImes = Settings.Secure.getString(mContext.getContentResolver(),
   1446                 Secure.DISABLED_SYSTEM_INPUT_METHODS);
   1447         if (disabledSysImes == null) disabledSysImes = "";
   1448 
   1449         List<ResolveInfo> services = pm.queryIntentServices(
   1450                 new Intent(InputMethod.SERVICE_INTERFACE),
   1451                 PackageManager.GET_META_DATA);
   1452 
   1453         for (int i = 0; i < services.size(); ++i) {
   1454             ResolveInfo ri = services.get(i);
   1455             ServiceInfo si = ri.serviceInfo;
   1456             ComponentName compName = new ComponentName(si.packageName, si.name);
   1457             if (!android.Manifest.permission.BIND_INPUT_METHOD.equals(
   1458                     si.permission)) {
   1459                 Slog.w(TAG, "Skipping input method " + compName
   1460                         + ": it does not require the permission "
   1461                         + android.Manifest.permission.BIND_INPUT_METHOD);
   1462                 continue;
   1463             }
   1464 
   1465             if (DEBUG) Slog.d(TAG, "Checking " + compName);
   1466 
   1467             try {
   1468                 InputMethodInfo p = new InputMethodInfo(mContext, ri);
   1469                 list.add(p);
   1470                 final String id = p.getId();
   1471                 map.put(id, p);
   1472 
   1473                 // System IMEs are enabled by default, unless there's a hard keyboard
   1474                 // and the system IME was explicitly disabled
   1475                 if (isSystemIme(p) && (!haveHardKeyboard || disabledSysImes.indexOf(id) < 0)) {
   1476                     setInputMethodEnabledLocked(id, true);
   1477                 }
   1478 
   1479                 if (DEBUG) {
   1480                     Slog.d(TAG, "Found a third-party input method " + p);
   1481                 }
   1482 
   1483             } catch (XmlPullParserException e) {
   1484                 Slog.w(TAG, "Unable to load input method " + compName, e);
   1485             } catch (IOException e) {
   1486                 Slog.w(TAG, "Unable to load input method " + compName, e);
   1487             }
   1488         }
   1489 
   1490         String defaultIme = Settings.Secure.getString(mContext
   1491                 .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
   1492         if (!TextUtils.isEmpty(defaultIme) && !map.containsKey(defaultIme)) {
   1493             if (chooseNewDefaultIMELocked()) {
   1494                 updateFromSettingsLocked();
   1495             }
   1496         }
   1497     }
   1498 
   1499     // ----------------------------------------------------------------------
   1500 
   1501     void showInputMethodMenu() {
   1502         if (DEBUG) Slog.v(TAG, "Show switching menu");
   1503 
   1504         final Context context = mContext;
   1505 
   1506         final PackageManager pm = context.getPackageManager();
   1507 
   1508         String lastInputMethodId = Settings.Secure.getString(context
   1509                 .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
   1510         if (DEBUG) Slog.v(TAG, "Current IME: " + lastInputMethodId);
   1511 
   1512         final List<InputMethodInfo> immis = getEnabledInputMethodList();
   1513 
   1514         if (immis == null) {
   1515             return;
   1516         }
   1517 
   1518         synchronized (mMethodMap) {
   1519             hideInputMethodMenuLocked();
   1520 
   1521             int N = immis.size();
   1522 
   1523             final Map<CharSequence, InputMethodInfo> imMap =
   1524                 new TreeMap<CharSequence, InputMethodInfo>(Collator.getInstance());
   1525 
   1526             for (int i = 0; i < N; ++i) {
   1527                 InputMethodInfo property = immis.get(i);
   1528                 if (property == null) {
   1529                     continue;
   1530                 }
   1531                 imMap.put(property.loadLabel(pm), property);
   1532             }
   1533 
   1534             N = imMap.size();
   1535             mItems = imMap.keySet().toArray(new CharSequence[N]);
   1536             mIms = imMap.values().toArray(new InputMethodInfo[N]);
   1537 
   1538             int checkedItem = 0;
   1539             for (int i = 0; i < N; ++i) {
   1540                 if (mIms[i].getId().equals(lastInputMethodId)) {
   1541                     checkedItem = i;
   1542                     break;
   1543                 }
   1544             }
   1545 
   1546             AlertDialog.OnClickListener adocl = new AlertDialog.OnClickListener() {
   1547                 public void onClick(DialogInterface dialog, int which) {
   1548                     hideInputMethodMenu();
   1549                 }
   1550             };
   1551 
   1552             TypedArray a = context.obtainStyledAttributes(null,
   1553                     com.android.internal.R.styleable.DialogPreference,
   1554                     com.android.internal.R.attr.alertDialogStyle, 0);
   1555             mDialogBuilder = new AlertDialog.Builder(context)
   1556                     .setTitle(com.android.internal.R.string.select_input_method)
   1557                     .setOnCancelListener(new OnCancelListener() {
   1558                         public void onCancel(DialogInterface dialog) {
   1559                             hideInputMethodMenu();
   1560                         }
   1561                     })
   1562                     .setIcon(a.getDrawable(
   1563                             com.android.internal.R.styleable.DialogPreference_dialogTitle));
   1564             a.recycle();
   1565 
   1566             mDialogBuilder.setSingleChoiceItems(mItems, checkedItem,
   1567                     new AlertDialog.OnClickListener() {
   1568                         public void onClick(DialogInterface dialog, int which) {
   1569                             synchronized (mMethodMap) {
   1570                                 if (mIms == null || mIms.length <= which) {
   1571                                     return;
   1572                                 }
   1573                                 InputMethodInfo im = mIms[which];
   1574                                 hideInputMethodMenu();
   1575                                 if (im != null) {
   1576                                     setInputMethodLocked(im.getId());
   1577                                 }
   1578                             }
   1579                         }
   1580                     });
   1581 
   1582             mSwitchingDialog = mDialogBuilder.create();
   1583             mSwitchingDialog.getWindow().setType(
   1584                     WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG);
   1585             mSwitchingDialog.show();
   1586         }
   1587     }
   1588 
   1589     void hideInputMethodMenu() {
   1590         synchronized (mMethodMap) {
   1591             hideInputMethodMenuLocked();
   1592         }
   1593     }
   1594 
   1595     void hideInputMethodMenuLocked() {
   1596         if (DEBUG) Slog.v(TAG, "Hide switching menu");
   1597 
   1598         if (mSwitchingDialog != null) {
   1599             mSwitchingDialog.dismiss();
   1600             mSwitchingDialog = null;
   1601         }
   1602 
   1603         mDialogBuilder = null;
   1604         mItems = null;
   1605         mIms = null;
   1606     }
   1607 
   1608     // ----------------------------------------------------------------------
   1609 
   1610     public boolean setInputMethodEnabled(String id, boolean enabled) {
   1611         synchronized (mMethodMap) {
   1612             if (mContext.checkCallingOrSelfPermission(
   1613                     android.Manifest.permission.WRITE_SECURE_SETTINGS)
   1614                     != PackageManager.PERMISSION_GRANTED) {
   1615                 throw new SecurityException(
   1616                         "Requires permission "
   1617                         + android.Manifest.permission.WRITE_SECURE_SETTINGS);
   1618             }
   1619 
   1620             long ident = Binder.clearCallingIdentity();
   1621             try {
   1622                 return setInputMethodEnabledLocked(id, enabled);
   1623             } finally {
   1624                 Binder.restoreCallingIdentity(ident);
   1625             }
   1626         }
   1627     }
   1628 
   1629     boolean setInputMethodEnabledLocked(String id, boolean enabled) {
   1630         // Make sure this is a valid input method.
   1631         InputMethodInfo imm = mMethodMap.get(id);
   1632         if (imm == null) {
   1633             if (imm == null) {
   1634                 throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
   1635             }
   1636         }
   1637 
   1638         StringBuilder builder = new StringBuilder(256);
   1639 
   1640         boolean removed = false;
   1641         String firstId = null;
   1642 
   1643         // Look through the currently enabled input methods.
   1644         String enabledStr = Settings.Secure.getString(mContext.getContentResolver(),
   1645                 Settings.Secure.ENABLED_INPUT_METHODS);
   1646         if (enabledStr != null) {
   1647             final TextUtils.SimpleStringSplitter splitter = mStringColonSplitter;
   1648             splitter.setString(enabledStr);
   1649             while (splitter.hasNext()) {
   1650                 String curId = splitter.next();
   1651                 if (curId.equals(id)) {
   1652                     if (enabled) {
   1653                         // We are enabling this input method, but it is
   1654                         // already enabled.  Nothing to do.  The previous
   1655                         // state was enabled.
   1656                         return true;
   1657                     }
   1658                     // We are disabling this input method, and it is
   1659                     // currently enabled.  Skip it to remove from the
   1660                     // new list.
   1661                     removed = true;
   1662                 } else if (!enabled) {
   1663                     // We are building a new list of input methods that
   1664                     // doesn't contain the given one.
   1665                     if (firstId == null) firstId = curId;
   1666                     if (builder.length() > 0) builder.append(':');
   1667                     builder.append(curId);
   1668                 }
   1669             }
   1670         }
   1671 
   1672         if (!enabled) {
   1673             if (!removed) {
   1674                 // We are disabling the input method but it is already
   1675                 // disabled.  Nothing to do.  The previous state was
   1676                 // disabled.
   1677                 return false;
   1678             }
   1679             // Update the setting with the new list of input methods.
   1680             Settings.Secure.putString(mContext.getContentResolver(),
   1681                     Settings.Secure.ENABLED_INPUT_METHODS, builder.toString());
   1682             // We the disabled input method is currently selected, switch
   1683             // to another one.
   1684             String selId = Settings.Secure.getString(mContext.getContentResolver(),
   1685                     Settings.Secure.DEFAULT_INPUT_METHOD);
   1686             if (id.equals(selId)) {
   1687                 Settings.Secure.putString(mContext.getContentResolver(),
   1688                         Settings.Secure.DEFAULT_INPUT_METHOD,
   1689                         firstId != null ? firstId : "");
   1690             }
   1691             // Previous state was enabled.
   1692             return true;
   1693         }
   1694 
   1695         // Add in the newly enabled input method.
   1696         if (enabledStr == null || enabledStr.length() == 0) {
   1697             enabledStr = id;
   1698         } else {
   1699             enabledStr = enabledStr + ':' + id;
   1700         }
   1701 
   1702         Settings.Secure.putString(mContext.getContentResolver(),
   1703                 Settings.Secure.ENABLED_INPUT_METHODS, enabledStr);
   1704 
   1705         // Previous state was disabled.
   1706         return false;
   1707     }
   1708 
   1709     // ----------------------------------------------------------------------
   1710 
   1711     @Override
   1712     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
   1713         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
   1714                 != PackageManager.PERMISSION_GRANTED) {
   1715 
   1716             pw.println("Permission Denial: can't dump InputMethodManager from from pid="
   1717                     + Binder.getCallingPid()
   1718                     + ", uid=" + Binder.getCallingUid());
   1719             return;
   1720         }
   1721 
   1722         IInputMethod method;
   1723         ClientState client;
   1724 
   1725         final Printer p = new PrintWriterPrinter(pw);
   1726 
   1727         synchronized (mMethodMap) {
   1728             p.println("Current Input Method Manager state:");
   1729             int N = mMethodList.size();
   1730             p.println("  Input Methods:");
   1731             for (int i=0; i<N; i++) {
   1732                 InputMethodInfo info = mMethodList.get(i);
   1733                 p.println("  InputMethod #" + i + ":");
   1734                 info.dump(p, "    ");
   1735             }
   1736             p.println("  Clients:");
   1737             for (ClientState ci : mClients.values()) {
   1738                 p.println("  Client " + ci + ":");
   1739                 p.println("    client=" + ci.client);
   1740                 p.println("    inputContext=" + ci.inputContext);
   1741                 p.println("    sessionRequested=" + ci.sessionRequested);
   1742                 p.println("    curSession=" + ci.curSession);
   1743             }
   1744             p.println("  mCurMethodId=" + mCurMethodId);
   1745             client = mCurClient;
   1746             p.println("  mCurClient=" + client + " mCurSeq=" + mCurSeq);
   1747             p.println("  mCurFocusedWindow=" + mCurFocusedWindow);
   1748             p.println("  mCurId=" + mCurId + " mHaveConnect=" + mHaveConnection
   1749                     + " mBoundToMethod=" + mBoundToMethod);
   1750             p.println("  mCurToken=" + mCurToken);
   1751             p.println("  mCurIntent=" + mCurIntent);
   1752             method = mCurMethod;
   1753             p.println("  mCurMethod=" + mCurMethod);
   1754             p.println("  mEnabledSession=" + mEnabledSession);
   1755             p.println("  mShowRequested=" + mShowRequested
   1756                     + " mShowExplicitlyRequested=" + mShowExplicitlyRequested
   1757                     + " mShowForced=" + mShowForced
   1758                     + " mInputShown=" + mInputShown);
   1759             p.println("  mSystemReady=" + mSystemReady + " mScreenOn=" + mScreenOn);
   1760         }
   1761 
   1762         p.println(" ");
   1763         if (client != null) {
   1764             pw.flush();
   1765             try {
   1766                 client.client.asBinder().dump(fd, args);
   1767             } catch (RemoteException e) {
   1768                 p.println("Input method client dead: " + e);
   1769             }
   1770         } else {
   1771             p.println("No input method client.");
   1772         }
   1773 
   1774         p.println(" ");
   1775         if (method != null) {
   1776             pw.flush();
   1777             try {
   1778                 method.asBinder().dump(fd, args);
   1779             } catch (RemoteException e) {
   1780                 p.println("Input method service dead: " + e);
   1781             }
   1782         } else {
   1783             p.println("No input method service.");
   1784         }
   1785     }
   1786 }
   1787