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