blob: b4b541dc5cd074c2a54c522ad086cab67f71ea42 [file] [log] [blame]
Yohei Yukawabae5bea2018-11-12 15:08:30 -08001/*
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
17package android.inputmethodservice;
18
19import android.annotation.Nullable;
20import android.annotation.WorkerThread;
21import android.graphics.Rect;
22import android.os.Bundle;
23import android.os.Debug;
24import android.os.Handler;
25import android.os.Looper;
26import android.os.ResultReceiver;
27import android.util.Log;
28import android.view.InputChannel;
29import android.view.InputDevice;
30import android.view.InputEvent;
31import android.view.InputEventReceiver;
32import android.view.KeyEvent;
33import android.view.MotionEvent;
34import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
35import android.view.inputmethod.CompletionInfo;
36import android.view.inputmethod.CursorAnchorInfo;
37import android.view.inputmethod.EditorInfo;
38import android.view.inputmethod.ExtractedText;
39
40import com.android.internal.annotations.GuardedBy;
41import com.android.internal.inputmethod.IMultiClientInputMethodSession;
42import com.android.internal.os.SomeArgs;
43import com.android.internal.util.function.pooled.PooledLambda;
44import com.android.internal.view.IInputContext;
45import com.android.internal.view.IInputMethodSession;
46import com.android.internal.view.InputConnectionWrapper;
47
48import java.lang.ref.WeakReference;
49import 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 */
66final 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
85 IInputMethodSession.Stub createIInputMethodSession() {
86 synchronized (mSessionLock) {
87 return new InputMethodSessionImpl(
88 mSessionLock, mCallbackImpl, mHandler, mFinished);
89 }
90 }
91
92 IMultiClientInputMethodSession.Stub createIMultiClientInputMethodSession() {
93 synchronized (mSessionLock) {
94 return new MultiClientInputMethodSessionImpl(
95 mSessionLock, mCallbackImpl, mHandler, mFinished);
96 }
97 }
98
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
115 KeyEventCallbackAdaptor(
116 MultiClientInputMethodServiceDelegate.ClientCallback callback) {
117 mLocalCallback = callback;
118 }
119
120 @Override
121 public boolean onKeyDown(int keyCode, KeyEvent event) {
122 return mLocalCallback.onKeyDown(keyCode, event);
123 }
124
125 @Override
126 public boolean onKeyLongPress(int keyCode, KeyEvent event) {
127 return mLocalCallback.onKeyLongPress(keyCode, event);
128 }
129
130 @Override
131 public boolean onKeyUp(int keyCode, KeyEvent event) {
132 return mLocalCallback.onKeyUp(keyCode, event);
133 }
134
135 @Override
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
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
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
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
201 public void updateExtractedText(int token, ExtractedText text) {
202 reportNotSupported();
203 }
204
205 @Override
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
226 public void viewClicked(boolean focusChanged) {
227 reportNotSupported();
228 }
229
230 @Override
231 public void updateCursor(Rect newCursor) {
232 reportNotSupported();
233 }
234
235 @Override
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
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
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
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
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
295 private static final class MultiClientInputMethodSessionImpl
296 extends IMultiClientInputMethodSession.Stub {
297 private final Object mSessionLock;
298 @GuardedBy("mSessionLock")
299 private CallbackImpl mCallbackImpl;
300 @GuardedBy("mSessionLock")
301 private Handler mHandler;
302 private final AtomicBoolean mSessionFinished;
303
304 MultiClientInputMethodSessionImpl(Object lock, CallbackImpl callback,
305 Handler handler, AtomicBoolean sessionFinished) {
306 mSessionLock = lock;
307 mCallbackImpl = callback;
308 mHandler = handler;
309 mSessionFinished = sessionFinished;
310 }
311
312 @Override
313 public void startInputOrWindowGainedFocus(@Nullable IInputContext inputContext,
314 int missingMethods, @Nullable EditorInfo editorInfo, int controlFlags,
315 @SoftInputModeFlags int softInputMode, int windowHandle) {
316 synchronized (mSessionLock) {
317 if (mCallbackImpl == null || mHandler == null) {
318 return;
319 }
320 final SomeArgs args = SomeArgs.obtain();
321 // TODO(Bug 119211536): Remove dependency on AbstractInputMethodService from ICW
322 final WeakReference<AbstractInputMethodService> fakeIMS =
323 new WeakReference<>(null);
324 args.arg1 = (inputContext == null) ? null
325 : new InputConnectionWrapper(fakeIMS, inputContext, missingMethods,
326 mSessionFinished);
327 args.arg2 = editorInfo;
328 args.argi1 = controlFlags;
329 args.argi2 = softInputMode;
330 args.argi3 = windowHandle;
331 mHandler.sendMessage(PooledLambda.obtainMessage(
332 CallbackImpl::startInputOrWindowGainedFocus, mCallbackImpl, args));
333 }
334 }
335
336 @Override
337 public void showSoftInput(int flags, ResultReceiver resultReceiver) {
338 synchronized (mSessionLock) {
339 if (mCallbackImpl == null || mHandler == null) {
340 return;
341 }
342 mHandler.sendMessage(PooledLambda.obtainMessage(
343 CallbackImpl::showSoftInput, mCallbackImpl, flags,
344 resultReceiver));
345 }
346 }
347
348 @Override
349 public void hideSoftInput(int flags, ResultReceiver resultReceiver) {
350 synchronized (mSessionLock) {
351 if (mCallbackImpl == null || mHandler == null) {
352 return;
353 }
354 mHandler.sendMessage(PooledLambda.obtainMessage(
355 CallbackImpl::hideSoftInput, mCallbackImpl, flags,
356 resultReceiver));
357 }
358 }
359 }
360
361 /**
362 * The maim part of adaptor to {@link MultiClientInputMethodServiceDelegate.ClientCallback}.
363 */
364 @WorkerThread
365 private static final class CallbackImpl {
366 private final MultiClientInputMethodClientCallbackAdaptor mCallbackAdaptor;
367 private final MultiClientInputMethodServiceDelegate.ClientCallback mOriginalCallback;
368 private boolean mFinished = false;
369
370 CallbackImpl(MultiClientInputMethodClientCallbackAdaptor callbackAdaptor,
371 MultiClientInputMethodServiceDelegate.ClientCallback callback) {
372 mCallbackAdaptor = callbackAdaptor;
373 mOriginalCallback = callback;
374 }
375
376 void updateSelection(SomeArgs args) {
377 try {
378 if (mFinished) {
379 return;
380 }
381 mOriginalCallback.onUpdateSelection(args.argi1, args.argi2, args.argi3,
382 args.argi4, args.argi5, args.argi6);
383 } finally {
384 args.recycle();
385 }
386 }
387
388 void displayCompletions(CompletionInfo[] completions) {
389 if (mFinished) {
390 return;
391 }
392 mOriginalCallback.onDisplayCompletions(completions);
393 }
394
395 void appPrivateCommand(String action, Bundle data) {
396 if (mFinished) {
397 return;
398 }
399 mOriginalCallback.onAppPrivateCommand(action, data);
400 }
401
402 void toggleSoftInput(int showFlags, int hideFlags) {
403 if (mFinished) {
404 return;
405 }
406 mOriginalCallback.onToggleSoftInput(showFlags, hideFlags);
407 }
408
409 void finishSession() {
410 if (mFinished) {
411 return;
412 }
413 mFinished = true;
414 mOriginalCallback.onFinishSession();
415 synchronized (mCallbackAdaptor.mSessionLock) {
416 mCallbackAdaptor.mDispatcherState = null;
417 if (mCallbackAdaptor.mReadChannel != null) {
418 mCallbackAdaptor.mReadChannel.dispose();
419 mCallbackAdaptor.mReadChannel = null;
420 }
421 mCallbackAdaptor.mInputEventReceiver = null;
422 }
423 }
424
425 void updateCursorAnchorInfo(CursorAnchorInfo info) {
426 if (mFinished) {
427 return;
428 }
429 mOriginalCallback.onUpdateCursorAnchorInfo(info);
430 }
431
432 void startInputOrWindowGainedFocus(SomeArgs args) {
433 try {
434 if (mFinished) {
435 return;
436 }
437 final InputConnectionWrapper inputConnection = (InputConnectionWrapper) args.arg1;
438 final EditorInfo editorInfo = (EditorInfo) args.arg2;
439 final int startInputFlags = args.argi1;
440 final int softInputMode = args.argi2;
441 final int windowHandle = args.argi3;
442 mOriginalCallback.onStartInputOrWindowGainedFocus(inputConnection, editorInfo,
443 startInputFlags, softInputMode, windowHandle);
444 } finally {
445 args.recycle();
446 }
447 }
448
449 void showSoftInput(int flags, ResultReceiver resultReceiver) {
450 if (mFinished) {
451 return;
452 }
453 mOriginalCallback.onShowSoftInput(flags, resultReceiver);
454 }
455
456 void hideSoftInput(int flags, ResultReceiver resultReceiver) {
457 if (mFinished) {
458 return;
459 }
460 mOriginalCallback.onHideSoftInput(flags, resultReceiver);
461 }
462 }
463
464 private static void reportNotSupported() {
465 if (DEBUG) {
466 Log.d(TAG, Debug.getCaller() + " is not supported");
467 }
468 }
469}