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