• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package android.inputmethodservice;
18 
19 import android.annotation.BinderThread;
20 import android.annotation.MainThread;
21 import android.content.Context;
22 import android.content.pm.PackageManager;
23 import android.os.Binder;
24 import android.os.IBinder;
25 import android.os.Message;
26 import android.os.RemoteException;
27 import android.os.ResultReceiver;
28 import android.util.Log;
29 import android.view.InputChannel;
30 import android.view.inputmethod.EditorInfo;
31 import android.view.inputmethod.InputBinding;
32 import android.view.inputmethod.InputConnection;
33 import android.view.inputmethod.InputConnectionInspector;
34 import android.view.inputmethod.InputMethod;
35 import android.view.inputmethod.InputMethodSession;
36 import android.view.inputmethod.InputMethodSubtype;
37 
38 import com.android.internal.os.HandlerCaller;
39 import com.android.internal.os.SomeArgs;
40 import com.android.internal.view.IInputContext;
41 import com.android.internal.view.IInputMethod;
42 import com.android.internal.view.IInputMethodSession;
43 import com.android.internal.view.IInputSessionCallback;
44 import com.android.internal.view.InputConnectionWrapper;
45 
46 import java.io.FileDescriptor;
47 import java.io.PrintWriter;
48 import java.lang.ref.WeakReference;
49 import java.util.concurrent.CountDownLatch;
50 import java.util.concurrent.TimeUnit;
51 import java.util.concurrent.atomic.AtomicBoolean;
52 
53 /**
54  * Implements the internal IInputMethod interface to convert incoming calls
55  * on to it back to calls on the public InputMethod interface, scheduling
56  * them on the main thread of the process.
57  */
58 class IInputMethodWrapper extends IInputMethod.Stub
59         implements HandlerCaller.Callback {
60     private static final String TAG = "InputMethodWrapper";
61 
62     private static final int DO_DUMP = 1;
63     private static final int DO_ATTACH_TOKEN = 10;
64     private static final int DO_SET_INPUT_CONTEXT = 20;
65     private static final int DO_UNSET_INPUT_CONTEXT = 30;
66     private static final int DO_START_INPUT = 32;
67     private static final int DO_CREATE_SESSION = 40;
68     private static final int DO_SET_SESSION_ENABLED = 45;
69     private static final int DO_REVOKE_SESSION = 50;
70     private static final int DO_SHOW_SOFT_INPUT = 60;
71     private static final int DO_HIDE_SOFT_INPUT = 70;
72     private static final int DO_CHANGE_INPUTMETHOD_SUBTYPE = 80;
73 
74     final WeakReference<AbstractInputMethodService> mTarget;
75     final Context mContext;
76     final HandlerCaller mCaller;
77     final WeakReference<InputMethod> mInputMethod;
78     final int mTargetSdkVersion;
79 
80     /**
81      * This is not {@null} only between {@link #bindInput(InputBinding)} and {@link #unbindInput()}
82      * so that {@link InputConnectionWrapper} can query if {@link #unbindInput()} has already been
83      * called or not, mainly to avoid unnecessary blocking operations.
84      *
85      * <p>This field must be set and cleared only from the binder thread(s), where the system
86      * guarantees that {@link #bindInput(InputBinding)},
87      * {@link #startInput(IBinder, IInputContext, int, EditorInfo, boolean)}, and
88      * {@link #unbindInput()} are called with the same order as the original calls
89      * in {@link com.android.server.InputMethodManagerService}.  See {@link IBinder#FLAG_ONEWAY}
90      * for detailed semantics.</p>
91      */
92     AtomicBoolean mIsUnbindIssued = null;
93 
94     // NOTE: we should have a cache of these.
95     static final class InputMethodSessionCallbackWrapper implements InputMethod.SessionCallback {
96         final Context mContext;
97         final InputChannel mChannel;
98         final IInputSessionCallback mCb;
99 
InputMethodSessionCallbackWrapper(Context context, InputChannel channel, IInputSessionCallback cb)100         InputMethodSessionCallbackWrapper(Context context, InputChannel channel,
101                 IInputSessionCallback cb) {
102             mContext = context;
103             mChannel = channel;
104             mCb = cb;
105         }
106 
107         @Override
sessionCreated(InputMethodSession session)108         public void sessionCreated(InputMethodSession session) {
109             try {
110                 if (session != null) {
111                     IInputMethodSessionWrapper wrap =
112                             new IInputMethodSessionWrapper(mContext, session, mChannel);
113                     mCb.sessionCreated(wrap);
114                 } else {
115                     if (mChannel != null) {
116                         mChannel.dispose();
117                     }
118                     mCb.sessionCreated(null);
119                 }
120             } catch (RemoteException e) {
121             }
122         }
123     }
124 
IInputMethodWrapper(AbstractInputMethodService context, InputMethod inputMethod)125     public IInputMethodWrapper(AbstractInputMethodService context, InputMethod inputMethod) {
126         mTarget = new WeakReference<>(context);
127         mContext = context.getApplicationContext();
128         mCaller = new HandlerCaller(mContext, null, this, true /*asyncHandler*/);
129         mInputMethod = new WeakReference<>(inputMethod);
130         mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
131     }
132 
133     @MainThread
134     @Override
executeMessage(Message msg)135     public void executeMessage(Message msg) {
136         InputMethod inputMethod = mInputMethod.get();
137         // Need a valid reference to the inputMethod for everything except a dump.
138         if (inputMethod == null && msg.what != DO_DUMP) {
139             Log.w(TAG, "Input method reference was null, ignoring message: " + msg.what);
140             return;
141         }
142 
143         switch (msg.what) {
144             case DO_DUMP: {
145                 AbstractInputMethodService target = mTarget.get();
146                 if (target == null) {
147                     return;
148                 }
149                 SomeArgs args = (SomeArgs)msg.obj;
150                 try {
151                     target.dump((FileDescriptor)args.arg1,
152                             (PrintWriter)args.arg2, (String[])args.arg3);
153                 } catch (RuntimeException e) {
154                     ((PrintWriter)args.arg2).println("Exception: " + e);
155                 }
156                 synchronized (args.arg4) {
157                     ((CountDownLatch)args.arg4).countDown();
158                 }
159                 args.recycle();
160                 return;
161             }
162 
163             case DO_ATTACH_TOKEN: {
164                 inputMethod.attachToken((IBinder)msg.obj);
165                 return;
166             }
167             case DO_SET_INPUT_CONTEXT: {
168                 inputMethod.bindInput((InputBinding)msg.obj);
169                 return;
170             }
171             case DO_UNSET_INPUT_CONTEXT:
172                 inputMethod.unbindInput();
173                 return;
174             case DO_START_INPUT: {
175                 final SomeArgs args = (SomeArgs) msg.obj;
176                 final int missingMethods = msg.arg1;
177                 final boolean restarting = msg.arg2 != 0;
178                 final IBinder startInputToken = (IBinder) args.arg1;
179                 final IInputContext inputContext = (IInputContext) args.arg2;
180                 final EditorInfo info = (EditorInfo) args.arg3;
181                 final AtomicBoolean isUnbindIssued = (AtomicBoolean) args.arg4;
182                 final InputConnection ic = inputContext != null
183                         ? new InputConnectionWrapper(
184                                 mTarget, inputContext, missingMethods, isUnbindIssued) : null;
185                 info.makeCompatible(mTargetSdkVersion);
186                 inputMethod.dispatchStartInputWithToken(ic, info, restarting /* restarting */,
187                         startInputToken);
188                 args.recycle();
189                 return;
190             }
191             case DO_CREATE_SESSION: {
192                 SomeArgs args = (SomeArgs)msg.obj;
193                 inputMethod.createSession(new InputMethodSessionCallbackWrapper(
194                         mContext, (InputChannel)args.arg1,
195                         (IInputSessionCallback)args.arg2));
196                 args.recycle();
197                 return;
198             }
199             case DO_SET_SESSION_ENABLED:
200                 inputMethod.setSessionEnabled((InputMethodSession)msg.obj,
201                         msg.arg1 != 0);
202                 return;
203             case DO_REVOKE_SESSION:
204                 inputMethod.revokeSession((InputMethodSession)msg.obj);
205                 return;
206             case DO_SHOW_SOFT_INPUT:
207                 inputMethod.showSoftInput(msg.arg1, (ResultReceiver)msg.obj);
208                 return;
209             case DO_HIDE_SOFT_INPUT:
210                 inputMethod.hideSoftInput(msg.arg1, (ResultReceiver)msg.obj);
211                 return;
212             case DO_CHANGE_INPUTMETHOD_SUBTYPE:
213                 inputMethod.changeInputMethodSubtype((InputMethodSubtype)msg.obj);
214                 return;
215         }
216         Log.w(TAG, "Unhandled message code: " + msg.what);
217     }
218 
219     @BinderThread
220     @Override
dump(FileDescriptor fd, PrintWriter fout, String[] args)221     protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
222         AbstractInputMethodService target = mTarget.get();
223         if (target == null) {
224             return;
225         }
226         if (target.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
227                 != PackageManager.PERMISSION_GRANTED) {
228 
229             fout.println("Permission Denial: can't dump InputMethodManager from from pid="
230                     + Binder.getCallingPid()
231                     + ", uid=" + Binder.getCallingUid());
232             return;
233         }
234 
235         CountDownLatch latch = new CountDownLatch(1);
236         mCaller.executeOrSendMessage(mCaller.obtainMessageOOOO(DO_DUMP,
237                 fd, fout, args, latch));
238         try {
239             if (!latch.await(5, TimeUnit.SECONDS)) {
240                 fout.println("Timeout waiting for dump");
241             }
242         } catch (InterruptedException e) {
243             fout.println("Interrupted waiting for dump");
244         }
245     }
246 
247     @BinderThread
248     @Override
attachToken(IBinder token)249     public void attachToken(IBinder token) {
250         mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_ATTACH_TOKEN, token));
251     }
252 
253     @BinderThread
254     @Override
bindInput(InputBinding binding)255     public void bindInput(InputBinding binding) {
256         if (mIsUnbindIssued != null) {
257             Log.e(TAG, "bindInput must be paired with unbindInput.");
258         }
259         mIsUnbindIssued = new AtomicBoolean();
260         // This IInputContext is guaranteed to implement all the methods.
261         final int missingMethodFlags = 0;
262         InputConnection ic = new InputConnectionWrapper(mTarget,
263                 IInputContext.Stub.asInterface(binding.getConnectionToken()), missingMethodFlags,
264                 mIsUnbindIssued);
265         InputBinding nu = new InputBinding(ic, binding);
266         mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_INPUT_CONTEXT, nu));
267     }
268 
269     @BinderThread
270     @Override
unbindInput()271     public void unbindInput() {
272         if (mIsUnbindIssued != null) {
273             // Signal the flag then forget it.
274             mIsUnbindIssued.set(true);
275             mIsUnbindIssued = null;
276         } else {
277             Log.e(TAG, "unbindInput must be paired with bindInput.");
278         }
279         mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_UNSET_INPUT_CONTEXT));
280     }
281 
282     @BinderThread
283     @Override
startInput(IBinder startInputToken, IInputContext inputContext, @InputConnectionInspector.MissingMethodFlags final int missingMethods, EditorInfo attribute, boolean restarting)284     public void startInput(IBinder startInputToken, IInputContext inputContext,
285             @InputConnectionInspector.MissingMethodFlags final int missingMethods,
286             EditorInfo attribute, boolean restarting) {
287         if (mIsUnbindIssued == null) {
288             Log.e(TAG, "startInput must be called after bindInput.");
289             mIsUnbindIssued = new AtomicBoolean();
290         }
291         mCaller.executeOrSendMessage(mCaller.obtainMessageIIOOOO(DO_START_INPUT,
292                 missingMethods, restarting ? 1 : 0, startInputToken, inputContext, attribute,
293                 mIsUnbindIssued));
294     }
295 
296     @BinderThread
297     @Override
createSession(InputChannel channel, IInputSessionCallback callback)298     public void createSession(InputChannel channel, IInputSessionCallback callback) {
299         mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_CREATE_SESSION,
300                 channel, callback));
301     }
302 
303     @BinderThread
304     @Override
setSessionEnabled(IInputMethodSession session, boolean enabled)305     public void setSessionEnabled(IInputMethodSession session, boolean enabled) {
306         try {
307             InputMethodSession ls = ((IInputMethodSessionWrapper)
308                     session).getInternalInputMethodSession();
309             if (ls == null) {
310                 Log.w(TAG, "Session is already finished: " + session);
311                 return;
312             }
313             mCaller.executeOrSendMessage(mCaller.obtainMessageIO(
314                     DO_SET_SESSION_ENABLED, enabled ? 1 : 0, ls));
315         } catch (ClassCastException e) {
316             Log.w(TAG, "Incoming session not of correct type: " + session, e);
317         }
318     }
319 
320     @BinderThread
321     @Override
revokeSession(IInputMethodSession session)322     public void revokeSession(IInputMethodSession session) {
323         try {
324             InputMethodSession ls = ((IInputMethodSessionWrapper)
325                     session).getInternalInputMethodSession();
326             if (ls == null) {
327                 Log.w(TAG, "Session is already finished: " + session);
328                 return;
329             }
330             mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_REVOKE_SESSION, ls));
331         } catch (ClassCastException e) {
332             Log.w(TAG, "Incoming session not of correct type: " + session, e);
333         }
334     }
335 
336     @BinderThread
337     @Override
showSoftInput(int flags, ResultReceiver resultReceiver)338     public void showSoftInput(int flags, ResultReceiver resultReceiver) {
339         mCaller.executeOrSendMessage(mCaller.obtainMessageIO(DO_SHOW_SOFT_INPUT,
340                 flags, resultReceiver));
341     }
342 
343     @BinderThread
344     @Override
hideSoftInput(int flags, ResultReceiver resultReceiver)345     public void hideSoftInput(int flags, ResultReceiver resultReceiver) {
346         mCaller.executeOrSendMessage(mCaller.obtainMessageIO(DO_HIDE_SOFT_INPUT,
347                 flags, resultReceiver));
348     }
349 
350     @BinderThread
351     @Override
changeInputMethodSubtype(InputMethodSubtype subtype)352     public void changeInputMethodSubtype(InputMethodSubtype subtype) {
353         mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_CHANGE_INPUTMETHOD_SUBTYPE,
354                 subtype));
355     }
356 }
357