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.status.IconData;
     30 import com.android.server.status.StatusBarService;
     31 
     32 import org.xmlpull.v1.XmlPullParserException;
     33 
     34 import android.app.ActivityManagerNative;
     35 import android.app.AlertDialog;
     36 import android.app.PendingIntent;
     37 import android.content.ComponentName;
     38 import android.content.ContentResolver;
     39 import android.content.Context;
     40 import android.content.DialogInterface;
     41 import android.content.IntentFilter;
     42 import android.content.DialogInterface.OnCancelListener;
     43 import android.content.Intent;
     44 import android.content.ServiceConnection;
     45 import android.content.pm.ApplicationInfo;
     46 import android.content.pm.PackageManager;
     47 import android.content.pm.ResolveInfo;
     48 import android.content.pm.ServiceInfo;
     49 import android.content.res.Configuration;
     50 import android.content.res.Resources;
     51 import android.content.res.TypedArray;
     52 import android.database.ContentObserver;
     53 import android.os.Binder;
     54 import android.os.Handler;
     55 import android.os.IBinder;
     56 import android.os.IInterface;
     57 import android.os.Message;
     58 import android.os.Parcel;
     59 import android.os.RemoteException;
     60 import android.os.ResultReceiver;
     61 import android.os.ServiceManager;
     62 import android.os.SystemClock;
     63 import android.provider.Settings;
     64 import android.provider.Settings.Secure;
     65 import android.text.TextUtils;
     66 import android.util.EventLog;
     67 import android.util.Slog;
     68 import android.util.PrintWriterPrinter;
     69 import android.util.Printer;
     70 import android.view.IWindowManager;
     71 import android.view.WindowManager;
     72 import android.view.inputmethod.InputBinding;
     73 import android.view.inputmethod.InputMethod;
     74 import android.view.inputmethod.InputMethodInfo;
     75 import android.view.inputmethod.InputMethodManager;
     76 import android.view.inputmethod.EditorInfo;
     77 
     78 import java.io.FileDescriptor;
     79 import java.io.IOException;
     80 import java.io.PrintWriter;
     81 import java.util.ArrayList;
     82 import java.util.HashMap;
     83 import java.util.List;
     84 
     85 /**
     86  * This class provides a system service that manages input methods.
     87  */
     88 public class InputMethodManagerService extends IInputMethodManager.Stub
     89         implements ServiceConnection, Handler.Callback {
     90     static final boolean DEBUG = false;
     91     static final String TAG = "InputManagerService";
     92 
     93     static final int MSG_SHOW_IM_PICKER = 1;
     94 
     95     static final int MSG_UNBIND_INPUT = 1000;
     96     static final int MSG_BIND_INPUT = 1010;
     97     static final int MSG_SHOW_SOFT_INPUT = 1020;
     98     static final int MSG_HIDE_SOFT_INPUT = 1030;
     99     static final int MSG_ATTACH_TOKEN = 1040;
    100     static final int MSG_CREATE_SESSION = 1050;
    101 
    102     static final int MSG_START_INPUT = 2000;
    103     static final int MSG_RESTART_INPUT = 2010;
    104 
    105     static final int MSG_UNBIND_METHOD = 3000;
    106     static final int MSG_BIND_METHOD = 3010;
    107 
    108     static final long TIME_TO_RECONNECT = 10*1000;
    109 
    110     final Context mContext;
    111     final Handler mHandler;
    112     final SettingsObserver mSettingsObserver;
    113     final StatusBarService mStatusBar;
    114     final IBinder mInputMethodIcon;
    115     final IconData mInputMethodData;
    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, StatusBarService 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         buildInputMethodListLocked(mMethodList, mMethodMap);
    470 
    471         final String enabledStr = Settings.Secure.getString(
    472                 mContext.getContentResolver(),
    473                 Settings.Secure.ENABLED_INPUT_METHODS);
    474         Slog.i(TAG, "Enabled input methods: " + enabledStr);
    475         if (enabledStr == null) {
    476             Slog.i(TAG, "Enabled input methods has not been set, enabling all");
    477             InputMethodInfo defIm = null;
    478             StringBuilder sb = new StringBuilder(256);
    479             final int N = mMethodList.size();
    480             for (int i=0; i<N; i++) {
    481                 InputMethodInfo imi = mMethodList.get(i);
    482                 Slog.i(TAG, "Adding: " + imi.getId());
    483                 if (i > 0) sb.append(':');
    484                 sb.append(imi.getId());
    485                 if (defIm == null && imi.getIsDefaultResourceId() != 0) {
    486                     try {
    487                         Resources res = mContext.createPackageContext(
    488                                 imi.getPackageName(), 0).getResources();
    489                         if (res.getBoolean(imi.getIsDefaultResourceId())) {
    490                             defIm = imi;
    491                             Slog.i(TAG, "Selected default: " + imi.getId());
    492                         }
    493                     } catch (PackageManager.NameNotFoundException ex) {
    494                     } catch (Resources.NotFoundException ex) {
    495                     }
    496                 }
    497             }
    498             if (defIm == null && N > 0) {
    499                 defIm = mMethodList.get(0);
    500                 Slog.i(TAG, "No default found, using " + defIm.getId());
    501             }
    502             Settings.Secure.putString(mContext.getContentResolver(),
    503                     Settings.Secure.ENABLED_INPUT_METHODS, sb.toString());
    504             if (defIm != null) {
    505                 Settings.Secure.putString(mContext.getContentResolver(),
    506                         Settings.Secure.DEFAULT_INPUT_METHOD, defIm.getId());
    507             }
    508         }
    509 
    510         mStatusBar = statusBar;
    511         mInputMethodData = IconData.makeIcon("ime", null, 0, 0, 0);
    512         mInputMethodIcon = statusBar.addIcon(mInputMethodData, null);
    513         statusBar.setIconVisibility(mInputMethodIcon, false);
    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     void clearCurMethodLocked() {
    894         if (mCurMethod != null) {
    895             for (ClientState cs : mClients.values()) {
    896                 cs.sessionRequested = false;
    897                 cs.curSession = null;
    898             }
    899             mCurMethod = null;
    900         }
    901         mStatusBar.setIconVisibility(mInputMethodIcon, false);
    902     }
    903 
    904     public void onServiceDisconnected(ComponentName name) {
    905         synchronized (mMethodMap) {
    906             if (DEBUG) Slog.v(TAG, "Service disconnected: " + name
    907                     + " mCurIntent=" + mCurIntent);
    908             if (mCurMethod != null && mCurIntent != null
    909                     && name.equals(mCurIntent.getComponent())) {
    910                 clearCurMethodLocked();
    911                 // We consider this to be a new bind attempt, since the system
    912                 // should now try to restart the service for us.
    913                 mLastBindTime = SystemClock.uptimeMillis();
    914                 mShowRequested = mInputShown;
    915                 mInputShown = false;
    916                 if (mCurClient != null) {
    917                     executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
    918                             MSG_UNBIND_METHOD, mCurSeq, mCurClient.client));
    919                 }
    920             }
    921         }
    922     }
    923 
    924     public void updateStatusIcon(IBinder token, String packageName, int iconId) {
    925         long ident = Binder.clearCallingIdentity();
    926         try {
    927             if (token == null || mCurToken != token) {
    928                 Slog.w(TAG, "Ignoring setInputMethod of token: " + token);
    929                 return;
    930             }
    931 
    932             synchronized (mMethodMap) {
    933                 if (iconId == 0) {
    934                     if (DEBUG) Slog.d(TAG, "hide the small icon for the input method");
    935                     mStatusBar.setIconVisibility(mInputMethodIcon, false);
    936                 } else if (packageName != null) {
    937                     if (DEBUG) Slog.d(TAG, "show a small icon for the input method");
    938                     mInputMethodData.iconId = iconId;
    939                     mInputMethodData.iconPackage = packageName;
    940                     mStatusBar.updateIcon(mInputMethodIcon, mInputMethodData, null);
    941                     mStatusBar.setIconVisibility(mInputMethodIcon, true);
    942                 }
    943             }
    944         } finally {
    945             Binder.restoreCallingIdentity(ident);
    946         }
    947     }
    948 
    949     void updateFromSettingsLocked() {
    950         // We are assuming that whoever is changing DEFAULT_INPUT_METHOD and
    951         // ENABLED_INPUT_METHODS is taking care of keeping them correctly in
    952         // sync, so we will never have a DEFAULT_INPUT_METHOD that is not
    953         // enabled.
    954         String id = Settings.Secure.getString(mContext.getContentResolver(),
    955             Settings.Secure.DEFAULT_INPUT_METHOD);
    956         if (id != null && id.length() > 0) {
    957             try {
    958                 setInputMethodLocked(id);
    959             } catch (IllegalArgumentException e) {
    960                 Slog.w(TAG, "Unknown input method from prefs: " + id, e);
    961                 mCurMethodId = null;
    962                 unbindCurrentMethodLocked(true);
    963             }
    964         } else {
    965             // There is no longer an input method set, so stop any current one.
    966             mCurMethodId = null;
    967             unbindCurrentMethodLocked(true);
    968         }
    969     }
    970 
    971     void setInputMethodLocked(String id) {
    972         InputMethodInfo info = mMethodMap.get(id);
    973         if (info == null) {
    974             throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
    975         }
    976 
    977         if (id.equals(mCurMethodId)) {
    978             return;
    979         }
    980 
    981         final long ident = Binder.clearCallingIdentity();
    982         try {
    983             mCurMethodId = id;
    984             Settings.Secure.putString(mContext.getContentResolver(),
    985                 Settings.Secure.DEFAULT_INPUT_METHOD, id);
    986 
    987             if (ActivityManagerNative.isSystemReady()) {
    988                 Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED);
    989                 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
    990                 intent.putExtra("input_method_id", id);
    991                 mContext.sendBroadcast(intent);
    992             }
    993             unbindCurrentClientLocked();
    994         } finally {
    995             Binder.restoreCallingIdentity(ident);
    996         }
    997     }
    998 
    999     public boolean showSoftInput(IInputMethodClient client, int flags,
   1000             ResultReceiver resultReceiver) {
   1001         long ident = Binder.clearCallingIdentity();
   1002         try {
   1003             synchronized (mMethodMap) {
   1004                 if (mCurClient == null || client == null
   1005                         || mCurClient.client.asBinder() != client.asBinder()) {
   1006                     try {
   1007                         // We need to check if this is the current client with
   1008                         // focus in the window manager, to allow this call to
   1009                         // be made before input is started in it.
   1010                         if (!mIWindowManager.inputMethodClientHasFocus(client)) {
   1011                             Slog.w(TAG, "Ignoring showSoftInput of: " + client);
   1012                             return false;
   1013                         }
   1014                     } catch (RemoteException e) {
   1015                         return false;
   1016                     }
   1017                 }
   1018 
   1019                 if (DEBUG) Slog.v(TAG, "Client requesting input be shown");
   1020                 return showCurrentInputLocked(flags, resultReceiver);
   1021             }
   1022         } finally {
   1023             Binder.restoreCallingIdentity(ident);
   1024         }
   1025     }
   1026 
   1027     boolean showCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
   1028         mShowRequested = true;
   1029         if ((flags&InputMethodManager.SHOW_IMPLICIT) == 0) {
   1030             mShowExplicitlyRequested = true;
   1031         }
   1032         if ((flags&InputMethodManager.SHOW_FORCED) != 0) {
   1033             mShowExplicitlyRequested = true;
   1034             mShowForced = true;
   1035         }
   1036 
   1037         if (!mSystemReady) {
   1038             return false;
   1039         }
   1040 
   1041         boolean res = false;
   1042         if (mCurMethod != null) {
   1043             executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO(
   1044                     MSG_SHOW_SOFT_INPUT, getImeShowFlags(), mCurMethod,
   1045                     resultReceiver));
   1046             mInputShown = true;
   1047             res = true;
   1048         } else if (mHaveConnection && SystemClock.uptimeMillis()
   1049                 < (mLastBindTime+TIME_TO_RECONNECT)) {
   1050             // The client has asked to have the input method shown, but
   1051             // we have been sitting here too long with a connection to the
   1052             // service and no interface received, so let's disconnect/connect
   1053             // to try to prod things along.
   1054             EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, mCurMethodId,
   1055                     SystemClock.uptimeMillis()-mLastBindTime,1);
   1056             mContext.unbindService(this);
   1057             mContext.bindService(mCurIntent, this, Context.BIND_AUTO_CREATE);
   1058         }
   1059 
   1060         return res;
   1061     }
   1062 
   1063     public boolean hideSoftInput(IInputMethodClient client, int flags,
   1064             ResultReceiver resultReceiver) {
   1065         long ident = Binder.clearCallingIdentity();
   1066         try {
   1067             synchronized (mMethodMap) {
   1068                 if (mCurClient == null || client == null
   1069                         || mCurClient.client.asBinder() != client.asBinder()) {
   1070                     try {
   1071                         // We need to check if this is the current client with
   1072                         // focus in the window manager, to allow this call to
   1073                         // be made before input is started in it.
   1074                         if (!mIWindowManager.inputMethodClientHasFocus(client)) {
   1075                             Slog.w(TAG, "Ignoring hideSoftInput of: " + client);
   1076                             return false;
   1077                         }
   1078                     } catch (RemoteException e) {
   1079                         return false;
   1080                     }
   1081                 }
   1082 
   1083                 if (DEBUG) Slog.v(TAG, "Client requesting input be hidden");
   1084                 return hideCurrentInputLocked(flags, resultReceiver);
   1085             }
   1086         } finally {
   1087             Binder.restoreCallingIdentity(ident);
   1088         }
   1089     }
   1090 
   1091     boolean hideCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
   1092         if ((flags&InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
   1093                 && (mShowExplicitlyRequested || mShowForced)) {
   1094             if (DEBUG) Slog.v(TAG,
   1095                     "Not hiding: explicit show not cancelled by non-explicit hide");
   1096             return false;
   1097         }
   1098         if (mShowForced && (flags&InputMethodManager.HIDE_NOT_ALWAYS) != 0) {
   1099             if (DEBUG) Slog.v(TAG,
   1100                     "Not hiding: forced show not cancelled by not-always hide");
   1101             return false;
   1102         }
   1103         boolean res;
   1104         if (mInputShown && mCurMethod != null) {
   1105             executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
   1106                     MSG_HIDE_SOFT_INPUT, mCurMethod, resultReceiver));
   1107             res = true;
   1108         } else {
   1109             res = false;
   1110         }
   1111         mInputShown = false;
   1112         mShowRequested = false;
   1113         mShowExplicitlyRequested = false;
   1114         mShowForced = false;
   1115         return res;
   1116     }
   1117 
   1118     public void windowGainedFocus(IInputMethodClient client, IBinder windowToken,
   1119             boolean viewHasFocus, boolean isTextEditor, int softInputMode,
   1120             boolean first, int windowFlags) {
   1121         long ident = Binder.clearCallingIdentity();
   1122         try {
   1123             synchronized (mMethodMap) {
   1124                 if (DEBUG) Slog.v(TAG, "windowGainedFocus: " + client.asBinder()
   1125                         + " viewHasFocus=" + viewHasFocus
   1126                         + " isTextEditor=" + isTextEditor
   1127                         + " softInputMode=#" + Integer.toHexString(softInputMode)
   1128                         + " first=" + first + " flags=#"
   1129                         + Integer.toHexString(windowFlags));
   1130 
   1131                 if (mCurClient == null || client == null
   1132                         || mCurClient.client.asBinder() != client.asBinder()) {
   1133                     try {
   1134                         // We need to check if this is the current client with
   1135                         // focus in the window manager, to allow this call to
   1136                         // be made before input is started in it.
   1137                         if (!mIWindowManager.inputMethodClientHasFocus(client)) {
   1138                             Slog.w(TAG, "Client not active, ignoring focus gain of: " + client);
   1139                             return;
   1140                         }
   1141                     } catch (RemoteException e) {
   1142                     }
   1143                 }
   1144 
   1145                 if (mCurFocusedWindow == windowToken) {
   1146                     Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client);
   1147                     return;
   1148                 }
   1149                 mCurFocusedWindow = windowToken;
   1150 
   1151                 switch (softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) {
   1152                     case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED:
   1153                         if (!isTextEditor || (softInputMode &
   1154                                 WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
   1155                                 != WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) {
   1156                             if (WindowManager.LayoutParams.mayUseInputMethod(windowFlags)) {
   1157                                 // There is no focus view, and this window will
   1158                                 // be behind any soft input window, so hide the
   1159                                 // soft input window if it is shown.
   1160                                 if (DEBUG) Slog.v(TAG, "Unspecified window will hide input");
   1161                                 hideCurrentInputLocked(InputMethodManager.HIDE_NOT_ALWAYS, null);
   1162                             }
   1163                         } else if (isTextEditor && (softInputMode &
   1164                                 WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
   1165                                 == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
   1166                                 && (softInputMode &
   1167                                         WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
   1168                             // There is a focus view, and we are navigating forward
   1169                             // into the window, so show the input window for the user.
   1170                             if (DEBUG) Slog.v(TAG, "Unspecified window will show input");
   1171                             showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
   1172                         }
   1173                         break;
   1174                     case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED:
   1175                         // Do nothing.
   1176                         break;
   1177                     case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN:
   1178                         if ((softInputMode &
   1179                                 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
   1180                             if (DEBUG) Slog.v(TAG, "Window asks to hide input going forward");
   1181                             hideCurrentInputLocked(0, null);
   1182                         }
   1183                         break;
   1184                     case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN:
   1185                         if (DEBUG) Slog.v(TAG, "Window asks to hide input");
   1186                         hideCurrentInputLocked(0, null);
   1187                         break;
   1188                     case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE:
   1189                         if ((softInputMode &
   1190                                 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
   1191                             if (DEBUG) Slog.v(TAG, "Window asks to show input going forward");
   1192                             showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
   1193                         }
   1194                         break;
   1195                     case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE:
   1196                         if (DEBUG) Slog.v(TAG, "Window asks to always show input");
   1197                         showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
   1198                         break;
   1199                 }
   1200             }
   1201         } finally {
   1202             Binder.restoreCallingIdentity(ident);
   1203         }
   1204     }
   1205 
   1206     public void showInputMethodPickerFromClient(IInputMethodClient client) {
   1207         synchronized (mMethodMap) {
   1208             if (mCurClient == null || client == null
   1209                     || mCurClient.client.asBinder() != client.asBinder()) {
   1210                 Slog.w(TAG, "Ignoring showInputMethodDialogFromClient of: " + client);
   1211             }
   1212 
   1213             mHandler.sendEmptyMessage(MSG_SHOW_IM_PICKER);
   1214         }
   1215     }
   1216 
   1217     public void setInputMethod(IBinder token, String id) {
   1218         synchronized (mMethodMap) {
   1219             if (token == null) {
   1220                 if (mContext.checkCallingOrSelfPermission(
   1221                         android.Manifest.permission.WRITE_SECURE_SETTINGS)
   1222                         != PackageManager.PERMISSION_GRANTED) {
   1223                     throw new SecurityException(
   1224                             "Using null token requires permission "
   1225                             + android.Manifest.permission.WRITE_SECURE_SETTINGS);
   1226                 }
   1227             } else if (mCurToken != token) {
   1228                 Slog.w(TAG, "Ignoring setInputMethod of token: " + token);
   1229                 return;
   1230             }
   1231 
   1232             long ident = Binder.clearCallingIdentity();
   1233             try {
   1234                 setInputMethodLocked(id);
   1235             } finally {
   1236                 Binder.restoreCallingIdentity(ident);
   1237             }
   1238         }
   1239     }
   1240 
   1241     public void hideMySoftInput(IBinder token, int flags) {
   1242         synchronized (mMethodMap) {
   1243             if (token == null || mCurToken != token) {
   1244                 Slog.w(TAG, "Ignoring hideInputMethod of token: " + token);
   1245                 return;
   1246             }
   1247             long ident = Binder.clearCallingIdentity();
   1248             try {
   1249                 hideCurrentInputLocked(flags, null);
   1250             } finally {
   1251                 Binder.restoreCallingIdentity(ident);
   1252             }
   1253         }
   1254     }
   1255 
   1256     public void showMySoftInput(IBinder token, int flags) {
   1257         synchronized (mMethodMap) {
   1258             if (token == null || mCurToken != token) {
   1259                 Slog.w(TAG, "Ignoring hideInputMethod of token: " + token);
   1260                 return;
   1261             }
   1262             long ident = Binder.clearCallingIdentity();
   1263             try {
   1264                 showCurrentInputLocked(flags, null);
   1265             } finally {
   1266                 Binder.restoreCallingIdentity(ident);
   1267             }
   1268         }
   1269     }
   1270 
   1271     void setEnabledSessionInMainThread(SessionState session) {
   1272         if (mEnabledSession != session) {
   1273             if (mEnabledSession != null) {
   1274                 try {
   1275                     if (DEBUG) Slog.v(TAG, "Disabling: " + mEnabledSession);
   1276                     mEnabledSession.method.setSessionEnabled(
   1277                             mEnabledSession.session, false);
   1278                 } catch (RemoteException e) {
   1279                 }
   1280             }
   1281             mEnabledSession = session;
   1282             try {
   1283                 if (DEBUG) Slog.v(TAG, "Enabling: " + mEnabledSession);
   1284                 session.method.setSessionEnabled(
   1285                         session.session, true);
   1286             } catch (RemoteException e) {
   1287             }
   1288         }
   1289     }
   1290 
   1291     public boolean handleMessage(Message msg) {
   1292         HandlerCaller.SomeArgs args;
   1293         switch (msg.what) {
   1294             case MSG_SHOW_IM_PICKER:
   1295                 showInputMethodMenu();
   1296                 return true;
   1297 
   1298             // ---------------------------------------------------------
   1299 
   1300             case MSG_UNBIND_INPUT:
   1301                 try {
   1302                     ((IInputMethod)msg.obj).unbindInput();
   1303                 } catch (RemoteException e) {
   1304                     // There is nothing interesting about the method dying.
   1305                 }
   1306                 return true;
   1307             case MSG_BIND_INPUT:
   1308                 args = (HandlerCaller.SomeArgs)msg.obj;
   1309                 try {
   1310                     ((IInputMethod)args.arg1).bindInput((InputBinding)args.arg2);
   1311                 } catch (RemoteException e) {
   1312                 }
   1313                 return true;
   1314             case MSG_SHOW_SOFT_INPUT:
   1315                 args = (HandlerCaller.SomeArgs)msg.obj;
   1316                 try {
   1317                     ((IInputMethod)args.arg1).showSoftInput(msg.arg1,
   1318                             (ResultReceiver)args.arg2);
   1319                 } catch (RemoteException e) {
   1320                 }
   1321                 return true;
   1322             case MSG_HIDE_SOFT_INPUT:
   1323                 args = (HandlerCaller.SomeArgs)msg.obj;
   1324                 try {
   1325                     ((IInputMethod)args.arg1).hideSoftInput(0,
   1326                             (ResultReceiver)args.arg2);
   1327                 } catch (RemoteException e) {
   1328                 }
   1329                 return true;
   1330             case MSG_ATTACH_TOKEN:
   1331                 args = (HandlerCaller.SomeArgs)msg.obj;
   1332                 try {
   1333                     if (DEBUG) Slog.v(TAG, "Sending attach of token: " + args.arg2);
   1334                     ((IInputMethod)args.arg1).attachToken((IBinder)args.arg2);
   1335                 } catch (RemoteException e) {
   1336                 }
   1337                 return true;
   1338             case MSG_CREATE_SESSION:
   1339                 args = (HandlerCaller.SomeArgs)msg.obj;
   1340                 try {
   1341                     ((IInputMethod)args.arg1).createSession(
   1342                             (IInputMethodCallback)args.arg2);
   1343                 } catch (RemoteException e) {
   1344                 }
   1345                 return true;
   1346             // ---------------------------------------------------------
   1347 
   1348             case MSG_START_INPUT:
   1349                 args = (HandlerCaller.SomeArgs)msg.obj;
   1350                 try {
   1351                     SessionState session = (SessionState)args.arg1;
   1352                     setEnabledSessionInMainThread(session);
   1353                     session.method.startInput((IInputContext)args.arg2,
   1354                             (EditorInfo)args.arg3);
   1355                 } catch (RemoteException e) {
   1356                 }
   1357                 return true;
   1358             case MSG_RESTART_INPUT:
   1359                 args = (HandlerCaller.SomeArgs)msg.obj;
   1360                 try {
   1361                     SessionState session = (SessionState)args.arg1;
   1362                     setEnabledSessionInMainThread(session);
   1363                     session.method.restartInput((IInputContext)args.arg2,
   1364                             (EditorInfo)args.arg3);
   1365                 } catch (RemoteException e) {
   1366                 }
   1367                 return true;
   1368 
   1369             // ---------------------------------------------------------
   1370 
   1371             case MSG_UNBIND_METHOD:
   1372                 try {
   1373                     ((IInputMethodClient)msg.obj).onUnbindMethod(msg.arg1);
   1374                 } catch (RemoteException e) {
   1375                     // There is nothing interesting about the last client dying.
   1376                 }
   1377                 return true;
   1378             case MSG_BIND_METHOD:
   1379                 args = (HandlerCaller.SomeArgs)msg.obj;
   1380                 try {
   1381                     ((IInputMethodClient)args.arg1).onBindMethod(
   1382                             (InputBindResult)args.arg2);
   1383                 } catch (RemoteException e) {
   1384                     Slog.w(TAG, "Client died receiving input method " + args.arg2);
   1385                 }
   1386                 return true;
   1387         }
   1388         return false;
   1389     }
   1390 
   1391     private boolean isSystemIme(InputMethodInfo inputMethod) {
   1392         return (inputMethod.getServiceInfo().applicationInfo.flags
   1393                 & ApplicationInfo.FLAG_SYSTEM) != 0;
   1394     }
   1395 
   1396     private boolean chooseNewDefaultIMELocked() {
   1397         List<InputMethodInfo> enabled = getEnabledInputMethodListLocked();
   1398         if (enabled != null && enabled.size() > 0) {
   1399             // We'd prefer to fall back on a system IME, since that is safer.
   1400             int i=enabled.size();
   1401             while (i > 0) {
   1402                 i--;
   1403                 if ((enabled.get(i).getServiceInfo().applicationInfo.flags
   1404                         & ApplicationInfo.FLAG_SYSTEM) != 0) {
   1405                     break;
   1406                 }
   1407             }
   1408             Settings.Secure.putString(mContext.getContentResolver(),
   1409                     Settings.Secure.DEFAULT_INPUT_METHOD,
   1410                     enabled.get(i).getId());
   1411             return true;
   1412         }
   1413 
   1414         return false;
   1415     }
   1416 
   1417     void buildInputMethodListLocked(ArrayList<InputMethodInfo> list,
   1418             HashMap<String, InputMethodInfo> map) {
   1419         list.clear();
   1420         map.clear();
   1421 
   1422         PackageManager pm = mContext.getPackageManager();
   1423         final Configuration config = mContext.getResources().getConfiguration();
   1424         final boolean haveHardKeyboard = config.keyboard == Configuration.KEYBOARD_QWERTY;
   1425         String disabledSysImes = Settings.Secure.getString(mContext.getContentResolver(),
   1426                 Secure.DISABLED_SYSTEM_INPUT_METHODS);
   1427         if (disabledSysImes == null) disabledSysImes = "";
   1428 
   1429         List<ResolveInfo> services = pm.queryIntentServices(
   1430                 new Intent(InputMethod.SERVICE_INTERFACE),
   1431                 PackageManager.GET_META_DATA);
   1432 
   1433         for (int i = 0; i < services.size(); ++i) {
   1434             ResolveInfo ri = services.get(i);
   1435             ServiceInfo si = ri.serviceInfo;
   1436             ComponentName compName = new ComponentName(si.packageName, si.name);
   1437             if (!android.Manifest.permission.BIND_INPUT_METHOD.equals(
   1438                     si.permission)) {
   1439                 Slog.w(TAG, "Skipping input method " + compName
   1440                         + ": it does not require the permission "
   1441                         + android.Manifest.permission.BIND_INPUT_METHOD);
   1442                 continue;
   1443             }
   1444 
   1445             if (DEBUG) Slog.d(TAG, "Checking " + compName);
   1446 
   1447             try {
   1448                 InputMethodInfo p = new InputMethodInfo(mContext, ri);
   1449                 list.add(p);
   1450                 final String id = p.getId();
   1451                 map.put(id, p);
   1452 
   1453                 // System IMEs are enabled by default, unless there's a hard keyboard
   1454                 // and the system IME was explicitly disabled
   1455                 if (isSystemIme(p) && (!haveHardKeyboard || disabledSysImes.indexOf(id) < 0)) {
   1456                     setInputMethodEnabledLocked(id, true);
   1457                 }
   1458 
   1459                 if (DEBUG) {
   1460                     Slog.d(TAG, "Found a third-party input method " + p);
   1461                 }
   1462 
   1463             } catch (XmlPullParserException e) {
   1464                 Slog.w(TAG, "Unable to load input method " + compName, e);
   1465             } catch (IOException e) {
   1466                 Slog.w(TAG, "Unable to load input method " + compName, e);
   1467             }
   1468         }
   1469 
   1470         String defaultIme = Settings.Secure.getString(mContext
   1471                 .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
   1472         if (!map.containsKey(defaultIme)) {
   1473             if (chooseNewDefaultIMELocked()) {
   1474                 updateFromSettingsLocked();
   1475             }
   1476         }
   1477     }
   1478 
   1479     // ----------------------------------------------------------------------
   1480 
   1481     void showInputMethodMenu() {
   1482         if (DEBUG) Slog.v(TAG, "Show switching menu");
   1483 
   1484         final Context context = mContext;
   1485 
   1486         final PackageManager pm = context.getPackageManager();
   1487 
   1488         String lastInputMethodId = Settings.Secure.getString(context
   1489                 .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
   1490         if (DEBUG) Slog.v(TAG, "Current IME: " + lastInputMethodId);
   1491 
   1492         final List<InputMethodInfo> immis = getEnabledInputMethodList();
   1493 
   1494         if (immis == null) {
   1495             return;
   1496         }
   1497 
   1498         synchronized (mMethodMap) {
   1499             hideInputMethodMenuLocked();
   1500 
   1501             int N = immis.size();
   1502 
   1503             mItems = new CharSequence[N];
   1504             mIms = new InputMethodInfo[N];
   1505 
   1506             int j = 0;
   1507             for (int i = 0; i < N; ++i) {
   1508                 InputMethodInfo property = immis.get(i);
   1509                 if (property == null) {
   1510                     continue;
   1511                 }
   1512                 mItems[j] = property.loadLabel(pm);
   1513                 mIms[j] = property;
   1514                 j++;
   1515             }
   1516 
   1517             int checkedItem = 0;
   1518             for (int i = 0; i < N; ++i) {
   1519                 if (mIms[i].getId().equals(lastInputMethodId)) {
   1520                     checkedItem = i;
   1521                     break;
   1522                 }
   1523             }
   1524 
   1525             AlertDialog.OnClickListener adocl = new AlertDialog.OnClickListener() {
   1526                 public void onClick(DialogInterface dialog, int which) {
   1527                     hideInputMethodMenu();
   1528                 }
   1529             };
   1530 
   1531             TypedArray a = context.obtainStyledAttributes(null,
   1532                     com.android.internal.R.styleable.DialogPreference,
   1533                     com.android.internal.R.attr.alertDialogStyle, 0);
   1534             mDialogBuilder = new AlertDialog.Builder(context)
   1535                     .setTitle(com.android.internal.R.string.select_input_method)
   1536                     .setOnCancelListener(new OnCancelListener() {
   1537                         public void onCancel(DialogInterface dialog) {
   1538                             hideInputMethodMenu();
   1539                         }
   1540                     })
   1541                     .setIcon(a.getDrawable(
   1542                             com.android.internal.R.styleable.DialogPreference_dialogTitle));
   1543             a.recycle();
   1544 
   1545             mDialogBuilder.setSingleChoiceItems(mItems, checkedItem,
   1546                     new AlertDialog.OnClickListener() {
   1547                         public void onClick(DialogInterface dialog, int which) {
   1548                             synchronized (mMethodMap) {
   1549                                 if (mIms == null || mIms.length <= which) {
   1550                                     return;
   1551                                 }
   1552                                 InputMethodInfo im = mIms[which];
   1553                                 hideInputMethodMenu();
   1554                                 if (im != null) {
   1555                                     setInputMethodLocked(im.getId());
   1556                                 }
   1557                             }
   1558                         }
   1559                     });
   1560 
   1561             mSwitchingDialog = mDialogBuilder.create();
   1562             mSwitchingDialog.getWindow().setType(
   1563                     WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG);
   1564             mSwitchingDialog.show();
   1565         }
   1566     }
   1567 
   1568     void hideInputMethodMenu() {
   1569         synchronized (mMethodMap) {
   1570             hideInputMethodMenuLocked();
   1571         }
   1572     }
   1573 
   1574     void hideInputMethodMenuLocked() {
   1575         if (DEBUG) Slog.v(TAG, "Hide switching menu");
   1576 
   1577         if (mSwitchingDialog != null) {
   1578             mSwitchingDialog.dismiss();
   1579             mSwitchingDialog = null;
   1580         }
   1581 
   1582         mDialogBuilder = null;
   1583         mItems = null;
   1584         mIms = null;
   1585     }
   1586 
   1587     // ----------------------------------------------------------------------
   1588 
   1589     public boolean setInputMethodEnabled(String id, boolean enabled) {
   1590         synchronized (mMethodMap) {
   1591             if (mContext.checkCallingOrSelfPermission(
   1592                     android.Manifest.permission.WRITE_SECURE_SETTINGS)
   1593                     != PackageManager.PERMISSION_GRANTED) {
   1594                 throw new SecurityException(
   1595                         "Requires permission "
   1596                         + android.Manifest.permission.WRITE_SECURE_SETTINGS);
   1597             }
   1598 
   1599             long ident = Binder.clearCallingIdentity();
   1600             try {
   1601                 return setInputMethodEnabledLocked(id, enabled);
   1602             } finally {
   1603                 Binder.restoreCallingIdentity(ident);
   1604             }
   1605         }
   1606     }
   1607 
   1608     boolean setInputMethodEnabledLocked(String id, boolean enabled) {
   1609         // Make sure this is a valid input method.
   1610         InputMethodInfo imm = mMethodMap.get(id);
   1611         if (imm == null) {
   1612             if (imm == null) {
   1613                 throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
   1614             }
   1615         }
   1616 
   1617         StringBuilder builder = new StringBuilder(256);
   1618 
   1619         boolean removed = false;
   1620         String firstId = null;
   1621 
   1622         // Look through the currently enabled input methods.
   1623         String enabledStr = Settings.Secure.getString(mContext.getContentResolver(),
   1624                 Settings.Secure.ENABLED_INPUT_METHODS);
   1625         if (enabledStr != null) {
   1626             final TextUtils.SimpleStringSplitter splitter = mStringColonSplitter;
   1627             splitter.setString(enabledStr);
   1628             while (splitter.hasNext()) {
   1629                 String curId = splitter.next();
   1630                 if (curId.equals(id)) {
   1631                     if (enabled) {
   1632                         // We are enabling this input method, but it is
   1633                         // already enabled.  Nothing to do.  The previous
   1634                         // state was enabled.
   1635                         return true;
   1636                     }
   1637                     // We are disabling this input method, and it is
   1638                     // currently enabled.  Skip it to remove from the
   1639                     // new list.
   1640                     removed = true;
   1641                 } else if (!enabled) {
   1642                     // We are building a new list of input methods that
   1643                     // doesn't contain the given one.
   1644                     if (firstId == null) firstId = curId;
   1645                     if (builder.length() > 0) builder.append(':');
   1646                     builder.append(curId);
   1647                 }
   1648             }
   1649         }
   1650 
   1651         if (!enabled) {
   1652             if (!removed) {
   1653                 // We are disabling the input method but it is already
   1654                 // disabled.  Nothing to do.  The previous state was
   1655                 // disabled.
   1656                 return false;
   1657             }
   1658             // Update the setting with the new list of input methods.
   1659             Settings.Secure.putString(mContext.getContentResolver(),
   1660                     Settings.Secure.ENABLED_INPUT_METHODS, builder.toString());
   1661             // We the disabled input method is currently selected, switch
   1662             // to another one.
   1663             String selId = Settings.Secure.getString(mContext.getContentResolver(),
   1664                     Settings.Secure.DEFAULT_INPUT_METHOD);
   1665             if (id.equals(selId)) {
   1666                 Settings.Secure.putString(mContext.getContentResolver(),
   1667                         Settings.Secure.DEFAULT_INPUT_METHOD,
   1668                         firstId != null ? firstId : "");
   1669             }
   1670             // Previous state was enabled.
   1671             return true;
   1672         }
   1673 
   1674         // Add in the newly enabled input method.
   1675         if (enabledStr == null || enabledStr.length() == 0) {
   1676             enabledStr = id;
   1677         } else {
   1678             enabledStr = enabledStr + ':' + id;
   1679         }
   1680 
   1681         Settings.Secure.putString(mContext.getContentResolver(),
   1682                 Settings.Secure.ENABLED_INPUT_METHODS, enabledStr);
   1683 
   1684         // Previous state was disabled.
   1685         return false;
   1686     }
   1687 
   1688     // ----------------------------------------------------------------------
   1689 
   1690     @Override
   1691     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
   1692         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
   1693                 != PackageManager.PERMISSION_GRANTED) {
   1694 
   1695             pw.println("Permission Denial: can't dump InputMethodManager from from pid="
   1696                     + Binder.getCallingPid()
   1697                     + ", uid=" + Binder.getCallingUid());
   1698             return;
   1699         }
   1700 
   1701         IInputMethod method;
   1702         ClientState client;
   1703 
   1704         final Printer p = new PrintWriterPrinter(pw);
   1705 
   1706         synchronized (mMethodMap) {
   1707             p.println("Current Input Method Manager state:");
   1708             int N = mMethodList.size();
   1709             p.println("  Input Methods:");
   1710             for (int i=0; i<N; i++) {
   1711                 InputMethodInfo info = mMethodList.get(i);
   1712                 p.println("  InputMethod #" + i + ":");
   1713                 info.dump(p, "    ");
   1714             }
   1715             p.println("  Clients:");
   1716             for (ClientState ci : mClients.values()) {
   1717                 p.println("  Client " + ci + ":");
   1718                 p.println("    client=" + ci.client);
   1719                 p.println("    inputContext=" + ci.inputContext);
   1720                 p.println("    sessionRequested=" + ci.sessionRequested);
   1721                 p.println("    curSession=" + ci.curSession);
   1722             }
   1723             p.println("  mInputMethodIcon=" + mInputMethodIcon);
   1724             p.println("  mInputMethodData=" + mInputMethodData);
   1725             p.println("  mCurMethodId=" + mCurMethodId);
   1726             client = mCurClient;
   1727             p.println("  mCurClient=" + client + " mCurSeq=" + mCurSeq);
   1728             p.println("  mCurFocusedWindow=" + mCurFocusedWindow);
   1729             p.println("  mCurId=" + mCurId + " mHaveConnect=" + mHaveConnection
   1730                     + " mBoundToMethod=" + mBoundToMethod);
   1731             p.println("  mCurToken=" + mCurToken);
   1732             p.println("  mCurIntent=" + mCurIntent);
   1733             method = mCurMethod;
   1734             p.println("  mCurMethod=" + mCurMethod);
   1735             p.println("  mEnabledSession=" + mEnabledSession);
   1736             p.println("  mShowRequested=" + mShowRequested
   1737                     + " mShowExplicitlyRequested=" + mShowExplicitlyRequested
   1738                     + " mShowForced=" + mShowForced
   1739                     + " mInputShown=" + mInputShown);
   1740             p.println("  mSystemReady=" + mSystemReady + " mScreenOn=" + mScreenOn);
   1741         }
   1742 
   1743         if (client != null) {
   1744             p.println(" ");
   1745             pw.flush();
   1746             try {
   1747                 client.client.asBinder().dump(fd, args);
   1748             } catch (RemoteException e) {
   1749                 p.println("Input method client dead: " + e);
   1750             }
   1751         }
   1752 
   1753         if (method != null) {
   1754             p.println(" ");
   1755             pw.flush();
   1756             try {
   1757                 method.asBinder().dump(fd, args);
   1758             } catch (RemoteException e) {
   1759                 p.println("Input method service dead: " + e);
   1760             }
   1761         }
   1762     }
   1763 }
   1764