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