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