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