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