1 /*
2  * Copyright (C) 2018 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.Nullable;
20 import android.annotation.WorkerThread;
21 import android.graphics.Rect;
22 import android.os.Bundle;
23 import android.os.Debug;
24 import android.os.Handler;
25 import android.os.Looper;
26 import android.os.RemoteException;
27 import android.os.ResultReceiver;
28 import android.util.Log;
29 import android.view.InputChannel;
30 import android.view.InputDevice;
31 import android.view.InputEvent;
32 import android.view.InputEventReceiver;
33 import android.view.KeyEvent;
34 import android.view.MotionEvent;
35 import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
36 import android.view.inputmethod.CompletionInfo;
37 import android.view.inputmethod.CursorAnchorInfo;
38 import android.view.inputmethod.EditorInfo;
39 import android.view.inputmethod.ExtractedText;
40 
41 import com.android.internal.annotations.GuardedBy;
42 import com.android.internal.inputmethod.CancellationGroup;
43 import com.android.internal.inputmethod.IMultiClientInputMethodSession;
44 import com.android.internal.os.SomeArgs;
45 import com.android.internal.util.function.pooled.PooledLambda;
46 import com.android.internal.view.IInputContext;
47 import com.android.internal.view.IInputMethodSession;
48 import com.android.internal.view.InputConnectionWrapper;
49 
50 import java.lang.ref.WeakReference;
51 
52 /**
53  * Re-dispatches all the incoming per-client events to the specified {@link Looper} thread.
54  *
55  * <p>There are three types of per-client callbacks.</p>
56  *
57  * <ul>
58  *     <li>{@link IInputMethodSession} - from the IME client</li>
59  *     <li>{@link IMultiClientInputMethodSession} - from MultiClientInputMethodManagerService</li>
60  *     <li>{@link InputChannel} - from the IME client</li>
61  * </ul>
62  *
63  * <p>This class serializes all the incoming events among those channels onto
64  * {@link MultiClientInputMethodServiceDelegate.ClientCallback} on the specified {@link Looper}
65  * thread.</p>
66  */
67 final class MultiClientInputMethodClientCallbackAdaptor {
68     static final boolean DEBUG = false;
69     static final String TAG = MultiClientInputMethodClientCallbackAdaptor.class.getSimpleName();
70 
71     private final Object mSessionLock = new Object();
72     @GuardedBy("mSessionLock")
73     CallbackImpl mCallbackImpl;
74     @GuardedBy("mSessionLock")
75     InputChannel mReadChannel;
76     @GuardedBy("mSessionLock")
77     KeyEvent.DispatcherState mDispatcherState;
78     @GuardedBy("mSessionLock")
79     Handler mHandler;
80     @GuardedBy("mSessionLock")
81     @Nullable
82     InputEventReceiver mInputEventReceiver;
83 
84     private final CancellationGroup mCancellationGroup = new CancellationGroup();
85 
createIInputMethodSession()86     IInputMethodSession.Stub createIInputMethodSession() {
87         synchronized (mSessionLock) {
88             return new InputMethodSessionImpl(
89                     mSessionLock, mCallbackImpl, mHandler, mCancellationGroup);
90         }
91     }
92 
createIMultiClientInputMethodSession()93     IMultiClientInputMethodSession.Stub createIMultiClientInputMethodSession() {
94         synchronized (mSessionLock) {
95             return new MultiClientInputMethodSessionImpl(
96                     mSessionLock, mCallbackImpl, mHandler, mCancellationGroup);
97         }
98     }
99 
MultiClientInputMethodClientCallbackAdaptor( MultiClientInputMethodServiceDelegate.ClientCallback clientCallback, Looper looper, KeyEvent.DispatcherState dispatcherState, InputChannel readChannel)100     MultiClientInputMethodClientCallbackAdaptor(
101             MultiClientInputMethodServiceDelegate.ClientCallback clientCallback, Looper looper,
102             KeyEvent.DispatcherState dispatcherState, InputChannel readChannel) {
103         synchronized (mSessionLock) {
104             mCallbackImpl = new CallbackImpl(this, clientCallback);
105             mDispatcherState = dispatcherState;
106             mHandler = new Handler(looper, null, true);
107             mReadChannel = readChannel;
108             mInputEventReceiver = new ImeInputEventReceiver(mReadChannel, mHandler.getLooper(),
109                     mCancellationGroup, mDispatcherState, mCallbackImpl.mOriginalCallback);
110         }
111     }
112 
113     private static final class KeyEventCallbackAdaptor implements KeyEvent.Callback {
114         private final MultiClientInputMethodServiceDelegate.ClientCallback mLocalCallback;
115 
KeyEventCallbackAdaptor( MultiClientInputMethodServiceDelegate.ClientCallback callback)116         KeyEventCallbackAdaptor(
117                 MultiClientInputMethodServiceDelegate.ClientCallback callback) {
118             mLocalCallback = callback;
119         }
120 
121         @Override
onKeyDown(int keyCode, KeyEvent event)122         public boolean onKeyDown(int keyCode, KeyEvent event) {
123             return mLocalCallback.onKeyDown(keyCode, event);
124         }
125 
126         @Override
onKeyLongPress(int keyCode, KeyEvent event)127         public boolean onKeyLongPress(int keyCode, KeyEvent event) {
128             return mLocalCallback.onKeyLongPress(keyCode, event);
129         }
130 
131         @Override
onKeyUp(int keyCode, KeyEvent event)132         public boolean onKeyUp(int keyCode, KeyEvent event) {
133             return mLocalCallback.onKeyUp(keyCode, event);
134         }
135 
136         @Override
onKeyMultiple(int keyCode, int count, KeyEvent event)137         public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) {
138             return mLocalCallback.onKeyMultiple(keyCode, event);
139         }
140     }
141 
142     private static final class ImeInputEventReceiver extends InputEventReceiver {
143         private final CancellationGroup mCancellationGroupOnFinishSession;
144         private final KeyEvent.DispatcherState mDispatcherState;
145         private final MultiClientInputMethodServiceDelegate.ClientCallback mClientCallback;
146         private final KeyEventCallbackAdaptor mKeyEventCallbackAdaptor;
147 
ImeInputEventReceiver(InputChannel readChannel, Looper looper, CancellationGroup cancellationGroupOnFinishSession, KeyEvent.DispatcherState dispatcherState, MultiClientInputMethodServiceDelegate.ClientCallback callback)148         ImeInputEventReceiver(InputChannel readChannel, Looper looper,
149                 CancellationGroup cancellationGroupOnFinishSession,
150                 KeyEvent.DispatcherState dispatcherState,
151                 MultiClientInputMethodServiceDelegate.ClientCallback callback) {
152             super(readChannel, looper);
153             mCancellationGroupOnFinishSession = cancellationGroupOnFinishSession;
154             mDispatcherState = dispatcherState;
155             mClientCallback = callback;
156             mKeyEventCallbackAdaptor = new KeyEventCallbackAdaptor(callback);
157         }
158 
159         @Override
onInputEvent(InputEvent event)160         public void onInputEvent(InputEvent event) {
161             if (mCancellationGroupOnFinishSession.isCanceled()) {
162                 // The session has been finished.
163                 finishInputEvent(event, false);
164                 return;
165             }
166             boolean handled = false;
167             try {
168                 if (event instanceof KeyEvent) {
169                     final KeyEvent keyEvent = (KeyEvent) event;
170                     handled = keyEvent.dispatch(mKeyEventCallbackAdaptor, mDispatcherState,
171                             mKeyEventCallbackAdaptor);
172                 } else {
173                     final MotionEvent motionEvent = (MotionEvent) event;
174                     if (motionEvent.isFromSource(InputDevice.SOURCE_CLASS_TRACKBALL)) {
175                         handled = mClientCallback.onTrackballEvent(motionEvent);
176                     } else {
177                         handled = mClientCallback.onGenericMotionEvent(motionEvent);
178                     }
179                 }
180             } finally {
181                 finishInputEvent(event, handled);
182             }
183         }
184     }
185 
186     private static final class InputMethodSessionImpl extends IInputMethodSession.Stub {
187         private final Object mSessionLock;
188         @GuardedBy("mSessionLock")
189         private CallbackImpl mCallbackImpl;
190         @GuardedBy("mSessionLock")
191         private Handler mHandler;
192         private final CancellationGroup mCancellationGroupOnFinishSession;
193 
InputMethodSessionImpl(Object lock, CallbackImpl callback, Handler handler, CancellationGroup cancellationGroupOnFinishSession)194         InputMethodSessionImpl(Object lock, CallbackImpl callback, Handler handler,
195                 CancellationGroup cancellationGroupOnFinishSession) {
196             mSessionLock = lock;
197             mCallbackImpl = callback;
198             mHandler = handler;
199             mCancellationGroupOnFinishSession = cancellationGroupOnFinishSession;
200         }
201 
202         @Override
updateExtractedText(int token, ExtractedText text)203         public void updateExtractedText(int token, ExtractedText text) {
204             reportNotSupported();
205         }
206 
207         @Override
updateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd)208         public void updateSelection(int oldSelStart, int oldSelEnd,
209                 int newSelStart, int newSelEnd,
210                 int candidatesStart, int candidatesEnd) {
211             synchronized (mSessionLock) {
212                 if (mCallbackImpl == null || mHandler == null) {
213                     return;
214                 }
215                 final SomeArgs args = SomeArgs.obtain();
216                 args.argi1 = oldSelStart;
217                 args.argi2 = oldSelEnd;
218                 args.argi3 = newSelStart;
219                 args.argi4 = newSelEnd;
220                 args.argi5 = candidatesStart;
221                 args.argi6 = candidatesEnd;
222                 mHandler.sendMessage(PooledLambda.obtainMessage(
223                         CallbackImpl::updateSelection, mCallbackImpl, args));
224             }
225         }
226 
227         @Override
viewClicked(boolean focusChanged)228         public void viewClicked(boolean focusChanged) {
229             reportNotSupported();
230         }
231 
232         @Override
updateCursor(Rect newCursor)233         public void updateCursor(Rect newCursor) {
234             reportNotSupported();
235         }
236 
237         @Override
displayCompletions(CompletionInfo[] completions)238         public void displayCompletions(CompletionInfo[] completions) {
239             synchronized (mSessionLock) {
240                 if (mCallbackImpl == null || mHandler == null) {
241                     return;
242                 }
243                 mHandler.sendMessage(PooledLambda.obtainMessage(
244                         CallbackImpl::displayCompletions, mCallbackImpl, completions));
245             }
246         }
247 
248         @Override
appPrivateCommand(String action, Bundle data)249         public void appPrivateCommand(String action, Bundle data) {
250             synchronized (mSessionLock) {
251                 if (mCallbackImpl == null || mHandler == null) {
252                     return;
253                 }
254                 mHandler.sendMessage(PooledLambda.obtainMessage(
255                         CallbackImpl::appPrivateCommand, mCallbackImpl, action, data));
256             }
257         }
258 
259         @Override
finishSession()260         public void finishSession() {
261             synchronized (mSessionLock) {
262                 if (mCallbackImpl == null || mHandler == null) {
263                     return;
264                 }
265                 mCancellationGroupOnFinishSession.cancelAll();
266                 mHandler.sendMessage(PooledLambda.obtainMessage(
267                         CallbackImpl::finishSession, mCallbackImpl));
268                 mCallbackImpl = null;
269                 mHandler = null;
270             }
271         }
272 
273         @Override
updateCursorAnchorInfo(CursorAnchorInfo info)274         public void updateCursorAnchorInfo(CursorAnchorInfo info) {
275             synchronized (mSessionLock) {
276                 if (mCallbackImpl == null || mHandler == null) {
277                     return;
278                 }
279                 mHandler.sendMessage(PooledLambda.obtainMessage(
280                         CallbackImpl::updateCursorAnchorInfo, mCallbackImpl, info));
281             }
282         }
283 
284         @Override
notifyImeHidden()285         public final void notifyImeHidden() {
286             // no-op for multi-session since IME is responsible controlling navigation bar buttons.
287             reportNotSupported();
288         }
289 
290         @Override
removeImeSurface()291         public void removeImeSurface() {
292             // no-op for multi-session
293             reportNotSupported();
294         }
295 
296         @Override
finishInput()297         public void finishInput() throws RemoteException {
298             // no-op for multi-session
299             reportNotSupported();
300         }
301     }
302 
303     private static final class MultiClientInputMethodSessionImpl
304             extends IMultiClientInputMethodSession.Stub {
305         private final Object mSessionLock;
306         @GuardedBy("mSessionLock")
307         private CallbackImpl mCallbackImpl;
308         @GuardedBy("mSessionLock")
309         private Handler mHandler;
310         private final CancellationGroup mCancellationGroupOnFinishSession;
311 
MultiClientInputMethodSessionImpl(Object lock, CallbackImpl callback, Handler handler, CancellationGroup cancellationGroupOnFinishSession)312         MultiClientInputMethodSessionImpl(Object lock, CallbackImpl callback,
313                 Handler handler, CancellationGroup cancellationGroupOnFinishSession) {
314             mSessionLock = lock;
315             mCallbackImpl = callback;
316             mHandler = handler;
317             mCancellationGroupOnFinishSession = cancellationGroupOnFinishSession;
318         }
319 
320         @Override
startInputOrWindowGainedFocus(@ullable IInputContext inputContext, int missingMethods, @Nullable EditorInfo editorInfo, int controlFlags, @SoftInputModeFlags int softInputMode, int windowHandle)321         public void startInputOrWindowGainedFocus(@Nullable IInputContext inputContext,
322                 int missingMethods, @Nullable EditorInfo editorInfo, int controlFlags,
323                 @SoftInputModeFlags int softInputMode, int windowHandle) {
324             synchronized (mSessionLock) {
325                 if (mCallbackImpl == null || mHandler == null) {
326                     return;
327                 }
328                 final SomeArgs args = SomeArgs.obtain();
329                 // TODO(Bug 119211536): Remove dependency on AbstractInputMethodService from ICW
330                 final WeakReference<AbstractInputMethodService> fakeIMS =
331                         new WeakReference<>(null);
332                 args.arg1 = (inputContext == null) ? null
333                         : new InputConnectionWrapper(fakeIMS, inputContext, missingMethods,
334                                 mCancellationGroupOnFinishSession);
335                 args.arg2 = editorInfo;
336                 args.argi1 = controlFlags;
337                 args.argi2 = softInputMode;
338                 args.argi3 = windowHandle;
339                 mHandler.sendMessage(PooledLambda.obtainMessage(
340                         CallbackImpl::startInputOrWindowGainedFocus, mCallbackImpl, args));
341             }
342         }
343 
344         @Override
showSoftInput(int flags, ResultReceiver resultReceiver)345         public void showSoftInput(int flags, ResultReceiver resultReceiver) {
346             synchronized (mSessionLock) {
347                 if (mCallbackImpl == null || mHandler == null) {
348                     return;
349                 }
350                 mHandler.sendMessage(PooledLambda.obtainMessage(
351                         CallbackImpl::showSoftInput, mCallbackImpl, flags,
352                         resultReceiver));
353             }
354         }
355 
356         @Override
hideSoftInput(int flags, ResultReceiver resultReceiver)357         public void hideSoftInput(int flags, ResultReceiver resultReceiver) {
358             synchronized (mSessionLock) {
359                 if (mCallbackImpl == null || mHandler == null) {
360                     return;
361                 }
362                 mHandler.sendMessage(PooledLambda.obtainMessage(
363                         CallbackImpl::hideSoftInput, mCallbackImpl, flags,
364                         resultReceiver));
365             }
366         }
367     }
368 
369     /**
370      * The maim part of adaptor to {@link MultiClientInputMethodServiceDelegate.ClientCallback}.
371      */
372     @WorkerThread
373     private static final class CallbackImpl {
374         private final MultiClientInputMethodClientCallbackAdaptor mCallbackAdaptor;
375         private final MultiClientInputMethodServiceDelegate.ClientCallback mOriginalCallback;
376         private boolean mFinished = false;
377 
CallbackImpl(MultiClientInputMethodClientCallbackAdaptor callbackAdaptor, MultiClientInputMethodServiceDelegate.ClientCallback callback)378         CallbackImpl(MultiClientInputMethodClientCallbackAdaptor callbackAdaptor,
379                 MultiClientInputMethodServiceDelegate.ClientCallback callback) {
380             mCallbackAdaptor = callbackAdaptor;
381             mOriginalCallback = callback;
382         }
383 
updateSelection(SomeArgs args)384         void updateSelection(SomeArgs args) {
385             try {
386                 if (mFinished) {
387                     return;
388                 }
389                 mOriginalCallback.onUpdateSelection(args.argi1, args.argi2, args.argi3,
390                         args.argi4, args.argi5, args.argi6);
391             } finally {
392                 args.recycle();
393             }
394         }
395 
displayCompletions(CompletionInfo[] completions)396         void displayCompletions(CompletionInfo[] completions) {
397             if (mFinished) {
398                 return;
399             }
400             mOriginalCallback.onDisplayCompletions(completions);
401         }
402 
appPrivateCommand(String action, Bundle data)403         void appPrivateCommand(String action, Bundle data) {
404             if (mFinished) {
405                 return;
406             }
407             mOriginalCallback.onAppPrivateCommand(action, data);
408         }
409 
finishSession()410         void finishSession() {
411             if (mFinished) {
412                 return;
413             }
414             mFinished = true;
415             mOriginalCallback.onFinishSession();
416             synchronized (mCallbackAdaptor.mSessionLock) {
417                 mCallbackAdaptor.mDispatcherState = null;
418                 if (mCallbackAdaptor.mReadChannel != null) {
419                     mCallbackAdaptor.mReadChannel.dispose();
420                     mCallbackAdaptor.mReadChannel = null;
421                 }
422                 mCallbackAdaptor.mInputEventReceiver = null;
423             }
424         }
425 
updateCursorAnchorInfo(CursorAnchorInfo info)426         void updateCursorAnchorInfo(CursorAnchorInfo info) {
427             if (mFinished) {
428                 return;
429             }
430             mOriginalCallback.onUpdateCursorAnchorInfo(info);
431         }
432 
startInputOrWindowGainedFocus(SomeArgs args)433         void startInputOrWindowGainedFocus(SomeArgs args) {
434             try {
435                 if (mFinished) {
436                     return;
437                 }
438                 final InputConnectionWrapper inputConnection = (InputConnectionWrapper) args.arg1;
439                 final EditorInfo editorInfo = (EditorInfo) args.arg2;
440                 final int startInputFlags = args.argi1;
441                 final int softInputMode = args.argi2;
442                 final int windowHandle = args.argi3;
443                 mOriginalCallback.onStartInputOrWindowGainedFocus(inputConnection, editorInfo,
444                         startInputFlags, softInputMode, windowHandle);
445             } finally {
446                 args.recycle();
447             }
448         }
449 
showSoftInput(int flags, ResultReceiver resultReceiver)450         void showSoftInput(int flags, ResultReceiver resultReceiver) {
451             if (mFinished) {
452                 return;
453             }
454             mOriginalCallback.onShowSoftInput(flags, resultReceiver);
455         }
456 
hideSoftInput(int flags, ResultReceiver resultReceiver)457         void hideSoftInput(int flags, ResultReceiver resultReceiver) {
458             if (mFinished) {
459                 return;
460             }
461             mOriginalCallback.onHideSoftInput(flags, resultReceiver);
462         }
463     }
464 
reportNotSupported()465     private static void reportNotSupported() {
466         if (DEBUG) {
467             Log.d(TAG, Debug.getCaller() + " is not supported");
468         }
469     }
470 }
471