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