• 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.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.compat.annotation.UnsupportedAppUsage;
24 import android.content.Context;
25 import android.content.pm.PackageManager;
26 import android.os.Binder;
27 import android.os.IBinder;
28 import android.os.Message;
29 import android.os.RemoteException;
30 import android.os.ResultReceiver;
31 import android.util.Log;
32 import android.view.InputChannel;
33 import android.view.MotionEvent;
34 import android.view.inputmethod.EditorInfo;
35 import android.view.inputmethod.InputBinding;
36 import android.view.inputmethod.InputConnection;
37 import android.view.inputmethod.InputMethod;
38 import android.view.inputmethod.InputMethodSession;
39 import android.view.inputmethod.InputMethodSubtype;
40 import android.window.ImeOnBackInvokedDispatcher;
41 
42 import com.android.internal.inputmethod.CancellationGroup;
43 import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
44 import com.android.internal.inputmethod.InputMethodNavButtonFlags;
45 import com.android.internal.os.HandlerCaller;
46 import com.android.internal.os.SomeArgs;
47 import com.android.internal.view.IInlineSuggestionsRequestCallback;
48 import com.android.internal.view.IInputContext;
49 import com.android.internal.view.IInputMethod;
50 import com.android.internal.view.IInputMethodSession;
51 import com.android.internal.view.IInputSessionCallback;
52 import com.android.internal.view.InlineSuggestionsRequestInfo;
53 
54 import java.io.FileDescriptor;
55 import java.io.PrintWriter;
56 import java.lang.ref.WeakReference;
57 import java.util.List;
58 import java.util.concurrent.CountDownLatch;
59 import java.util.concurrent.TimeUnit;
60 
61 /**
62  * Implements the internal IInputMethod interface to convert incoming calls
63  * on to it back to calls on the public InputMethod interface, scheduling
64  * them on the main thread of the process.
65  */
66 class IInputMethodWrapper extends IInputMethod.Stub
67         implements HandlerCaller.Callback {
68     private static final String TAG = "InputMethodWrapper";
69 
70     private static final int DO_DUMP = 1;
71     private static final int DO_INITIALIZE_INTERNAL = 10;
72     private static final int DO_SET_INPUT_CONTEXT = 20;
73     private static final int DO_UNSET_INPUT_CONTEXT = 30;
74     private static final int DO_START_INPUT = 32;
75     private static final int DO_ON_NAV_BUTTON_FLAGS_CHANGED = 35;
76     private static final int DO_CREATE_SESSION = 40;
77     private static final int DO_SET_SESSION_ENABLED = 45;
78     private static final int DO_SHOW_SOFT_INPUT = 60;
79     private static final int DO_HIDE_SOFT_INPUT = 70;
80     private static final int DO_CHANGE_INPUTMETHOD_SUBTYPE = 80;
81     private static final int DO_CREATE_INLINE_SUGGESTIONS_REQUEST = 90;
82     private static final int DO_CAN_START_STYLUS_HANDWRITING = 100;
83     private static final int DO_START_STYLUS_HANDWRITING = 110;
84     private static final int DO_INIT_INK_WINDOW = 120;
85     private static final int DO_FINISH_STYLUS_HANDWRITING = 130;
86 
87     final WeakReference<InputMethodServiceInternal> mTarget;
88     final Context mContext;
89     @UnsupportedAppUsage
90     final HandlerCaller mCaller;
91     final WeakReference<InputMethod> mInputMethod;
92     final int mTargetSdkVersion;
93 
94     /**
95      * This is not {@null} only between {@link #bindInput(InputBinding)} and {@link #unbindInput()}
96      * so that {@link RemoteInputConnection} can query if {@link #unbindInput()} has already been
97      * called or not, mainly to avoid unnecessary blocking operations.
98      *
99      * <p>This field must be set and cleared only from the binder thread(s), where the system
100      * guarantees that {@link #bindInput(InputBinding)},
101      * {@link #startInput(IBinder, IInputContext, int, EditorInfo, boolean, boolean)}, and
102      * {@link #unbindInput()} are called with the same order as the original calls
103      * in {@link com.android.server.inputmethod.InputMethodManagerService}.
104      * See {@link IBinder#FLAG_ONEWAY} for detailed semantics.</p>
105      */
106     @Nullable
107     CancellationGroup mCancellationGroup = null;
108 
109     // NOTE: we should have a cache of these.
110     static final class InputMethodSessionCallbackWrapper implements InputMethod.SessionCallback {
111         final Context mContext;
112         final InputChannel mChannel;
113         final IInputSessionCallback mCb;
114 
InputMethodSessionCallbackWrapper(Context context, InputChannel channel, IInputSessionCallback cb)115         InputMethodSessionCallbackWrapper(Context context, InputChannel channel,
116                 IInputSessionCallback cb) {
117             mContext = context;
118             mChannel = channel;
119             mCb = cb;
120         }
121 
122         @Override
sessionCreated(InputMethodSession session)123         public void sessionCreated(InputMethodSession session) {
124             try {
125                 if (session != null) {
126                     IInputMethodSessionWrapper wrap =
127                             new IInputMethodSessionWrapper(mContext, session, mChannel);
128                     mCb.sessionCreated(wrap);
129                 } else {
130                     if (mChannel != null) {
131                         mChannel.dispose();
132                     }
133                     mCb.sessionCreated(null);
134                 }
135             } catch (RemoteException e) {
136             }
137         }
138     }
139 
IInputMethodWrapper(InputMethodServiceInternal imsInternal, InputMethod inputMethod)140     IInputMethodWrapper(InputMethodServiceInternal imsInternal, InputMethod inputMethod) {
141         mTarget = new WeakReference<>(imsInternal);
142         mContext = imsInternal.getContext().getApplicationContext();
143         mCaller = new HandlerCaller(mContext, null, this, true /*asyncHandler*/);
144         mInputMethod = new WeakReference<>(inputMethod);
145         mTargetSdkVersion = imsInternal.getContext().getApplicationInfo().targetSdkVersion;
146     }
147 
148     @MainThread
149     @Override
executeMessage(Message msg)150     public void executeMessage(Message msg) {
151         InputMethod inputMethod = mInputMethod.get();
152         // Need a valid reference to the inputMethod for everything except a dump.
153         if (inputMethod == null && msg.what != DO_DUMP) {
154             Log.w(TAG, "Input method reference was null, ignoring message: " + msg.what);
155             return;
156         }
157 
158         switch (msg.what) {
159             case DO_DUMP: {
160                 InputMethodServiceInternal target = mTarget.get();
161                 if (target == null) {
162                     return;
163                 }
164                 SomeArgs args = (SomeArgs)msg.obj;
165                 try {
166                     target.dump((FileDescriptor) args.arg1,
167                             (PrintWriter) args.arg2, (String[]) args.arg3);
168                 } catch (RuntimeException e) {
169                     ((PrintWriter)args.arg2).println("Exception: " + e);
170                 }
171                 synchronized (args.arg4) {
172                     ((CountDownLatch)args.arg4).countDown();
173                 }
174                 args.recycle();
175                 return;
176             }
177             case DO_INITIALIZE_INTERNAL: {
178                 SomeArgs args = (SomeArgs) msg.obj;
179                 try {
180                     inputMethod.initializeInternal((IBinder) args.arg1,
181                             (IInputMethodPrivilegedOperations) args.arg2, msg.arg1,
182                             (boolean) args.arg3, msg.arg2);
183                 } finally {
184                     args.recycle();
185                 }
186                 return;
187             }
188             case DO_SET_INPUT_CONTEXT: {
189                 inputMethod.bindInput((InputBinding)msg.obj);
190                 return;
191             }
192             case DO_UNSET_INPUT_CONTEXT:
193                 inputMethod.unbindInput();
194                 return;
195             case DO_START_INPUT: {
196                 final SomeArgs args = (SomeArgs) msg.obj;
197                 final IBinder startInputToken = (IBinder) args.arg1;
198                 final IInputContext inputContext = (IInputContext) ((SomeArgs) args.arg2).arg1;
199                 final ImeOnBackInvokedDispatcher imeDispatcher =
200                         (ImeOnBackInvokedDispatcher) ((SomeArgs) args.arg2).arg2;
201                 final EditorInfo info = (EditorInfo) args.arg3;
202                 final CancellationGroup cancellationGroup = (CancellationGroup) args.arg4;
203                 final boolean restarting = args.argi5 == 1;
204                 @InputMethodNavButtonFlags
205                 final int navButtonFlags = args.argi6;
206                 final InputConnection ic = inputContext != null
207                         ? new RemoteInputConnection(mTarget, inputContext, cancellationGroup)
208                         : null;
209                 info.makeCompatible(mTargetSdkVersion);
210                 inputMethod.dispatchStartInputWithToken(ic, info, restarting, startInputToken,
211                         navButtonFlags, imeDispatcher);
212                 args.recycle();
213                 return;
214             }
215             case DO_ON_NAV_BUTTON_FLAGS_CHANGED:
216                 inputMethod.onNavButtonFlagsChanged(msg.arg1);
217                 return;
218             case DO_CREATE_SESSION: {
219                 SomeArgs args = (SomeArgs)msg.obj;
220                 inputMethod.createSession(new InputMethodSessionCallbackWrapper(
221                         mContext, (InputChannel)args.arg1,
222                         (IInputSessionCallback)args.arg2));
223                 args.recycle();
224                 return;
225             }
226             case DO_SET_SESSION_ENABLED:
227                 inputMethod.setSessionEnabled((InputMethodSession)msg.obj,
228                         msg.arg1 != 0);
229                 return;
230             case DO_SHOW_SOFT_INPUT: {
231                 final SomeArgs args = (SomeArgs)msg.obj;
232                 inputMethod.showSoftInputWithToken(
233                         msg.arg1, (ResultReceiver) args.arg2, (IBinder) args.arg1);
234                 args.recycle();
235                 return;
236             }
237             case DO_HIDE_SOFT_INPUT: {
238                 final SomeArgs args = (SomeArgs) msg.obj;
239                 inputMethod.hideSoftInputWithToken(msg.arg1, (ResultReceiver) args.arg2,
240                         (IBinder) args.arg1);
241                 args.recycle();
242                 return;
243             }
244             case DO_CHANGE_INPUTMETHOD_SUBTYPE:
245                 inputMethod.changeInputMethodSubtype((InputMethodSubtype)msg.obj);
246                 return;
247             case DO_CREATE_INLINE_SUGGESTIONS_REQUEST: {
248                 final SomeArgs args = (SomeArgs) msg.obj;
249                 inputMethod.onCreateInlineSuggestionsRequest(
250                         (InlineSuggestionsRequestInfo) args.arg1,
251                         (IInlineSuggestionsRequestCallback) args.arg2);
252                 args.recycle();
253                 return;
254             }
255             case DO_CAN_START_STYLUS_HANDWRITING: {
256                 inputMethod.canStartStylusHandwriting(msg.arg1);
257                 return;
258             }
259             case DO_START_STYLUS_HANDWRITING: {
260                 final SomeArgs args = (SomeArgs) msg.obj;
261                 inputMethod.startStylusHandwriting(msg.arg1, (InputChannel) args.arg1,
262                         (List<MotionEvent>) args.arg2);
263                 args.recycle();
264                 return;
265             }
266             case DO_INIT_INK_WINDOW: {
267                 inputMethod.initInkWindow();
268                 return;
269             }
270             case DO_FINISH_STYLUS_HANDWRITING: {
271                 inputMethod.finishStylusHandwriting();
272                 return;
273             }
274 
275         }
276         Log.w(TAG, "Unhandled message code: " + msg.what);
277     }
278 
279     @BinderThread
280     @Override
dump(FileDescriptor fd, PrintWriter fout, String[] args)281     protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
282         InputMethodServiceInternal target = mTarget.get();
283         if (target == null) {
284             return;
285         }
286         if (target.getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
287                 != PackageManager.PERMISSION_GRANTED) {
288 
289             fout.println("Permission Denial: can't dump InputMethodManager from from pid="
290                     + Binder.getCallingPid()
291                     + ", uid=" + Binder.getCallingUid());
292             return;
293         }
294 
295         CountDownLatch latch = new CountDownLatch(1);
296         mCaller.getHandler().sendMessageAtFrontOfQueue(mCaller.obtainMessageOOOO(DO_DUMP,
297                 fd, fout, args, latch));
298         try {
299             if (!latch.await(5, TimeUnit.SECONDS)) {
300                 fout.println("Timeout waiting for dump");
301             }
302         } catch (InterruptedException e) {
303             fout.println("Interrupted waiting for dump");
304         }
305     }
306 
307     @BinderThread
308     @Override
initializeInternal(IBinder token, IInputMethodPrivilegedOperations privOps, int configChanges, boolean stylusHwSupported, @InputMethodNavButtonFlags int navButtonFlags)309     public void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privOps,
310             int configChanges, boolean stylusHwSupported,
311             @InputMethodNavButtonFlags int navButtonFlags) {
312         mCaller.executeOrSendMessage(mCaller.obtainMessageIIOOO(DO_INITIALIZE_INTERNAL,
313                 configChanges, navButtonFlags, token, privOps, stylusHwSupported));
314     }
315 
316     @BinderThread
317     @Override
onCreateInlineSuggestionsRequest(InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback cb)318     public void onCreateInlineSuggestionsRequest(InlineSuggestionsRequestInfo requestInfo,
319             IInlineSuggestionsRequestCallback cb) {
320         mCaller.executeOrSendMessage(
321                 mCaller.obtainMessageOO(DO_CREATE_INLINE_SUGGESTIONS_REQUEST, requestInfo, cb));
322     }
323 
324     @BinderThread
325     @Override
bindInput(InputBinding binding)326     public void bindInput(InputBinding binding) {
327         if (mCancellationGroup != null) {
328             Log.e(TAG, "bindInput must be paired with unbindInput.");
329         }
330         mCancellationGroup = new CancellationGroup();
331         InputConnection ic = new RemoteInputConnection(mTarget,
332                 IInputContext.Stub.asInterface(binding.getConnectionToken()), mCancellationGroup);
333         InputBinding nu = new InputBinding(ic, binding);
334         mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_INPUT_CONTEXT, nu));
335     }
336 
337     @BinderThread
338     @Override
unbindInput()339     public void unbindInput() {
340         if (mCancellationGroup != null) {
341             // Signal the flag then forget it.
342             mCancellationGroup.cancelAll();
343             mCancellationGroup = null;
344         } else {
345             Log.e(TAG, "unbindInput must be paired with bindInput.");
346         }
347         mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_UNSET_INPUT_CONTEXT));
348     }
349 
350     @BinderThread
351     @Override
startInput(IBinder startInputToken, IInputContext inputContext, EditorInfo attribute, boolean restarting, @InputMethodNavButtonFlags int navButtonFlags, @NonNull ImeOnBackInvokedDispatcher imeDispatcher)352     public void startInput(IBinder startInputToken, IInputContext inputContext,
353             EditorInfo attribute, boolean restarting,
354             @InputMethodNavButtonFlags int navButtonFlags,
355             @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
356         if (mCancellationGroup == null) {
357             Log.e(TAG, "startInput must be called after bindInput.");
358             mCancellationGroup = new CancellationGroup();
359         }
360         SomeArgs args = SomeArgs.obtain();
361         args.arg1 = inputContext;
362         args.arg2 = imeDispatcher;
363         mCaller.executeOrSendMessage(mCaller.obtainMessageOOOOII(DO_START_INPUT, startInputToken,
364                 args, attribute, mCancellationGroup, restarting ? 1 : 0, navButtonFlags));
365     }
366 
367     @BinderThread
368     @Override
onNavButtonFlagsChanged(@nputMethodNavButtonFlags int navButtonFlags)369     public void onNavButtonFlagsChanged(@InputMethodNavButtonFlags int navButtonFlags) {
370         mCaller.executeOrSendMessage(
371                 mCaller.obtainMessageI(DO_ON_NAV_BUTTON_FLAGS_CHANGED, navButtonFlags));
372     }
373 
374     @BinderThread
375     @Override
createSession(InputChannel channel, IInputSessionCallback callback)376     public void createSession(InputChannel channel, IInputSessionCallback callback) {
377         mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_CREATE_SESSION,
378                 channel, callback));
379     }
380 
381     @BinderThread
382     @Override
setSessionEnabled(IInputMethodSession session, boolean enabled)383     public void setSessionEnabled(IInputMethodSession session, boolean enabled) {
384         try {
385             InputMethodSession ls = ((IInputMethodSessionWrapper)
386                     session).getInternalInputMethodSession();
387             if (ls == null) {
388                 Log.w(TAG, "Session is already finished: " + session);
389                 return;
390             }
391             mCaller.executeOrSendMessage(mCaller.obtainMessageIO(
392                     DO_SET_SESSION_ENABLED, enabled ? 1 : 0, ls));
393         } catch (ClassCastException e) {
394             Log.w(TAG, "Incoming session not of correct type: " + session, e);
395         }
396     }
397 
398     @BinderThread
399     @Override
showSoftInput(IBinder showInputToken, int flags, ResultReceiver resultReceiver)400     public void showSoftInput(IBinder showInputToken, int flags, ResultReceiver resultReceiver) {
401         mCaller.executeOrSendMessage(mCaller.obtainMessageIOO(DO_SHOW_SOFT_INPUT,
402                 flags, showInputToken, resultReceiver));
403     }
404 
405     @BinderThread
406     @Override
hideSoftInput(IBinder hideInputToken, int flags, ResultReceiver resultReceiver)407     public void hideSoftInput(IBinder hideInputToken, int flags, ResultReceiver resultReceiver) {
408         mCaller.executeOrSendMessage(mCaller.obtainMessageIOO(DO_HIDE_SOFT_INPUT,
409                 flags, hideInputToken, resultReceiver));
410     }
411 
412     @BinderThread
413     @Override
changeInputMethodSubtype(InputMethodSubtype subtype)414     public void changeInputMethodSubtype(InputMethodSubtype subtype) {
415         mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_CHANGE_INPUTMETHOD_SUBTYPE,
416                 subtype));
417     }
418 
419     @BinderThread
420     @Override
canStartStylusHandwriting(int requestId)421     public void canStartStylusHandwriting(int requestId)
422             throws RemoteException {
423         mCaller.executeOrSendMessage(
424                 mCaller.obtainMessageI(DO_CAN_START_STYLUS_HANDWRITING, requestId));
425     }
426 
427     @BinderThread
428     @Override
startStylusHandwriting(int requestId, @NonNull InputChannel channel, @Nullable List<MotionEvent> stylusEvents)429     public void startStylusHandwriting(int requestId, @NonNull InputChannel channel,
430             @Nullable List<MotionEvent> stylusEvents)
431             throws RemoteException {
432         mCaller.executeOrSendMessage(
433                 mCaller.obtainMessageIOO(DO_START_STYLUS_HANDWRITING, requestId, channel,
434                         stylusEvents));
435     }
436 
437     @BinderThread
438     @Override
initInkWindow()439     public void initInkWindow() {
440         mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_INIT_INK_WINDOW));
441     }
442 
443     @BinderThread
444     @Override
finishStylusHandwriting()445     public void finishStylusHandwriting() {
446         mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_FINISH_STYLUS_HANDWRITING));
447     }
448 }
449