IInputMethodWrapper.java revision dea3ef7967228f0ddcc03f2455a4f1254758e584
1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of 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,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.inputmethodservice;
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.IInputMethodSession;
24import com.android.internal.view.InputConnectionWrapper;
25
26import android.content.Context;
27import android.content.pm.PackageManager;
28import android.os.Binder;
29import android.os.IBinder;
30import android.os.Message;
31import android.os.RemoteException;
32import android.os.ResultReceiver;
33import android.util.Log;
34import android.view.inputmethod.EditorInfo;
35import android.view.inputmethod.InputBinding;
36import android.view.inputmethod.InputConnection;
37import android.view.inputmethod.InputMethod;
38import android.view.inputmethod.InputMethodSession;
39import android.view.inputmethod.InputMethodSubtype;
40
41import java.io.FileDescriptor;
42import java.io.PrintWriter;
43import java.lang.ref.WeakReference;
44import java.util.concurrent.CountDownLatch;
45import java.util.concurrent.TimeUnit;
46
47/**
48 * Implements the internal IInputMethod interface to convert incoming calls
49 * on to it back to calls on the public InputMethod interface, scheduling
50 * them on the main thread of the process.
51 */
52class IInputMethodWrapper extends IInputMethod.Stub
53        implements HandlerCaller.Callback {
54    private static final String TAG = "InputMethodWrapper";
55    private static final boolean DEBUG = false;
56
57    private static final int DO_DUMP = 1;
58    private static final int DO_ATTACH_TOKEN = 10;
59    private static final int DO_SET_INPUT_CONTEXT = 20;
60    private static final int DO_UNSET_INPUT_CONTEXT = 30;
61    private static final int DO_START_INPUT = 32;
62    private static final int DO_RESTART_INPUT = 34;
63    private static final int DO_CREATE_SESSION = 40;
64    private static final int DO_SET_SESSION_ENABLED = 45;
65    private static final int DO_REVOKE_SESSION = 50;
66    private static final int DO_SHOW_SOFT_INPUT = 60;
67    private static final int DO_HIDE_SOFT_INPUT = 70;
68    private static final int DO_CHANGE_INPUTMETHOD_SUBTYPE = 80;
69
70    final WeakReference<AbstractInputMethodService> mTarget;
71    final HandlerCaller mCaller;
72    final WeakReference<InputMethod> mInputMethod;
73    final int mTargetSdkVersion;
74
75    static class Notifier {
76        boolean notified;
77    }
78
79    // NOTE: we should have a cache of these.
80    static class InputMethodSessionCallbackWrapper implements InputMethod.SessionCallback {
81        final Context mContext;
82        final IInputMethodCallback mCb;
83        InputMethodSessionCallbackWrapper(Context context, IInputMethodCallback cb) {
84            mContext = context;
85            mCb = cb;
86        }
87        public void sessionCreated(InputMethodSession session) {
88            try {
89                if (session != null) {
90                    IInputMethodSessionWrapper wrap =
91                            new IInputMethodSessionWrapper(mContext, session);
92                    mCb.sessionCreated(wrap);
93                } else {
94                    mCb.sessionCreated(null);
95                }
96            } catch (RemoteException e) {
97            }
98        }
99    }
100
101    public IInputMethodWrapper(AbstractInputMethodService context,
102            InputMethod inputMethod) {
103        mTarget = new WeakReference<AbstractInputMethodService>(context);
104        mCaller = new HandlerCaller(context.getApplicationContext(), this);
105        mInputMethod = new WeakReference<InputMethod>(inputMethod);
106        mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
107    }
108
109    public InputMethod getInternalInputMethod() {
110        return mInputMethod.get();
111    }
112
113    public void executeMessage(Message msg) {
114        InputMethod inputMethod = mInputMethod.get();
115        // Need a valid reference to the inputMethod for everything except a dump.
116        if (inputMethod == null && msg.what != DO_DUMP) {
117            Log.w(TAG, "Input method reference was null, ignoring message: " + msg.what);
118            return;
119        }
120
121        switch (msg.what) {
122            case DO_DUMP: {
123                AbstractInputMethodService target = mTarget.get();
124                if (target == null) {
125                    return;
126                }
127                HandlerCaller.SomeArgs args = (HandlerCaller.SomeArgs)msg.obj;
128                try {
129                    target.dump((FileDescriptor)args.arg1,
130                            (PrintWriter)args.arg2, (String[])args.arg3);
131                } catch (RuntimeException e) {
132                    ((PrintWriter)args.arg2).println("Exception: " + e);
133                }
134                synchronized (args.arg4) {
135                    ((CountDownLatch)args.arg4).countDown();
136                }
137                return;
138            }
139
140            case DO_ATTACH_TOKEN: {
141                inputMethod.attachToken((IBinder)msg.obj);
142                return;
143            }
144            case DO_SET_INPUT_CONTEXT: {
145                inputMethod.bindInput((InputBinding)msg.obj);
146                return;
147            }
148            case DO_UNSET_INPUT_CONTEXT:
149                inputMethod.unbindInput();
150                return;
151            case DO_START_INPUT: {
152                HandlerCaller.SomeArgs args = (HandlerCaller.SomeArgs)msg.obj;
153                IInputContext inputContext = (IInputContext)args.arg1;
154                InputConnection ic = inputContext != null
155                        ? new InputConnectionWrapper(inputContext) : null;
156                EditorInfo info = (EditorInfo)args.arg2;
157                info.makeCompatible(mTargetSdkVersion);
158                inputMethod.startInput(ic, info);
159                return;
160            }
161            case DO_RESTART_INPUT: {
162                HandlerCaller.SomeArgs args = (HandlerCaller.SomeArgs)msg.obj;
163                IInputContext inputContext = (IInputContext)args.arg1;
164                InputConnection ic = inputContext != null
165                        ? new InputConnectionWrapper(inputContext) : null;
166                EditorInfo info = (EditorInfo)args.arg2;
167                info.makeCompatible(mTargetSdkVersion);
168                inputMethod.restartInput(ic, info);
169                return;
170            }
171            case DO_CREATE_SESSION: {
172                inputMethod.createSession(new InputMethodSessionCallbackWrapper(
173                        mCaller.mContext, (IInputMethodCallback)msg.obj));
174                return;
175            }
176            case DO_SET_SESSION_ENABLED:
177                inputMethod.setSessionEnabled((InputMethodSession)msg.obj,
178                        msg.arg1 != 0);
179                return;
180            case DO_REVOKE_SESSION:
181                inputMethod.revokeSession((InputMethodSession)msg.obj);
182                return;
183            case DO_SHOW_SOFT_INPUT:
184                inputMethod.showSoftInput(msg.arg1, (ResultReceiver)msg.obj);
185                return;
186            case DO_HIDE_SOFT_INPUT:
187                inputMethod.hideSoftInput(msg.arg1, (ResultReceiver)msg.obj);
188                return;
189            case DO_CHANGE_INPUTMETHOD_SUBTYPE:
190                inputMethod.changeInputMethodSubtype((InputMethodSubtype)msg.obj);
191                return;
192        }
193        Log.w(TAG, "Unhandled message code: " + msg.what);
194    }
195
196    @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
197        AbstractInputMethodService target = mTarget.get();
198        if (target == null) {
199            return;
200        }
201        if (target.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
202                != PackageManager.PERMISSION_GRANTED) {
203
204            fout.println("Permission Denial: can't dump InputMethodManager from from pid="
205                    + Binder.getCallingPid()
206                    + ", uid=" + Binder.getCallingUid());
207            return;
208        }
209
210        CountDownLatch latch = new CountDownLatch(1);
211        mCaller.executeOrSendMessage(mCaller.obtainMessageOOOO(DO_DUMP,
212                fd, fout, args, latch));
213        try {
214            if (!latch.await(5, TimeUnit.SECONDS)) {
215                fout.println("Timeout waiting for dump");
216            }
217        } catch (InterruptedException e) {
218            fout.println("Interrupted waiting for dump");
219        }
220    }
221
222    public void attachToken(IBinder token) {
223        mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_ATTACH_TOKEN, token));
224    }
225
226    public void bindInput(InputBinding binding) {
227        InputConnection ic = new InputConnectionWrapper(
228                IInputContext.Stub.asInterface(binding.getConnectionToken()));
229        InputBinding nu = new InputBinding(ic, binding);
230        mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_INPUT_CONTEXT, nu));
231    }
232
233    public void unbindInput() {
234        mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_UNSET_INPUT_CONTEXT));
235    }
236
237    public void startInput(IInputContext inputContext, EditorInfo attribute) {
238        mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_START_INPUT,
239                inputContext, attribute));
240    }
241
242    public void restartInput(IInputContext inputContext, EditorInfo attribute) {
243        mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_RESTART_INPUT,
244                inputContext, attribute));
245    }
246
247    public void createSession(IInputMethodCallback callback) {
248        mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_CREATE_SESSION, callback));
249    }
250
251    public void setSessionEnabled(IInputMethodSession session, boolean enabled) {
252        try {
253            InputMethodSession ls = ((IInputMethodSessionWrapper)
254                    session).getInternalInputMethodSession();
255            mCaller.executeOrSendMessage(mCaller.obtainMessageIO(
256                    DO_SET_SESSION_ENABLED, enabled ? 1 : 0, ls));
257        } catch (ClassCastException e) {
258            Log.w(TAG, "Incoming session not of correct type: " + session, e);
259        }
260    }
261
262    public void revokeSession(IInputMethodSession session) {
263        try {
264            InputMethodSession ls = ((IInputMethodSessionWrapper)
265                    session).getInternalInputMethodSession();
266            mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_REVOKE_SESSION, ls));
267        } catch (ClassCastException e) {
268            Log.w(TAG, "Incoming session not of correct type: " + session, e);
269        }
270    }
271
272    public void showSoftInput(int flags, ResultReceiver resultReceiver) {
273        mCaller.executeOrSendMessage(mCaller.obtainMessageIO(DO_SHOW_SOFT_INPUT,
274                flags, resultReceiver));
275    }
276
277    public void hideSoftInput(int flags, ResultReceiver resultReceiver) {
278        mCaller.executeOrSendMessage(mCaller.obtainMessageIO(DO_HIDE_SOFT_INPUT,
279                flags, resultReceiver));
280    }
281
282    public void changeInputMethodSubtype(InputMethodSubtype subtype) {
283        mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_CHANGE_INPUTMETHOD_SUBTYPE,
284                subtype));
285    }
286}
287