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