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