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