/* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.inputmethodservice; import android.annotation.Nullable; import android.annotation.WorkerThread; import android.graphics.Rect; import android.os.Bundle; import android.os.Debug; import android.os.Handler; import android.os.Looper; import android.os.RemoteException; import android.os.ResultReceiver; import android.util.Log; import android.view.InputChannel; import android.view.InputDevice; import android.view.InputEvent; import android.view.InputEventReceiver; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.WindowManager.LayoutParams.SoftInputModeFlags; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.CursorAnchorInfo; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.ExtractedText; import com.android.internal.annotations.GuardedBy; import com.android.internal.inputmethod.CancellationGroup; import com.android.internal.inputmethod.IMultiClientInputMethodSession; import com.android.internal.os.SomeArgs; import com.android.internal.util.function.pooled.PooledLambda; import com.android.internal.view.IInputContext; import com.android.internal.view.IInputMethodSession; import com.android.internal.view.InputConnectionWrapper; import java.lang.ref.WeakReference; /** * Re-dispatches all the incoming per-client events to the specified {@link Looper} thread. * *
There are three types of per-client callbacks.
* *This class serializes all the incoming events among those channels onto * {@link MultiClientInputMethodServiceDelegate.ClientCallback} on the specified {@link Looper} * thread.
*/ final class MultiClientInputMethodClientCallbackAdaptor { static final boolean DEBUG = false; static final String TAG = MultiClientInputMethodClientCallbackAdaptor.class.getSimpleName(); private final Object mSessionLock = new Object(); @GuardedBy("mSessionLock") CallbackImpl mCallbackImpl; @GuardedBy("mSessionLock") InputChannel mReadChannel; @GuardedBy("mSessionLock") KeyEvent.DispatcherState mDispatcherState; @GuardedBy("mSessionLock") Handler mHandler; @GuardedBy("mSessionLock") @Nullable InputEventReceiver mInputEventReceiver; private final CancellationGroup mCancellationGroup = new CancellationGroup(); IInputMethodSession.Stub createIInputMethodSession() { synchronized (mSessionLock) { return new InputMethodSessionImpl( mSessionLock, mCallbackImpl, mHandler, mCancellationGroup); } } IMultiClientInputMethodSession.Stub createIMultiClientInputMethodSession() { synchronized (mSessionLock) { return new MultiClientInputMethodSessionImpl( mSessionLock, mCallbackImpl, mHandler, mCancellationGroup); } } MultiClientInputMethodClientCallbackAdaptor( MultiClientInputMethodServiceDelegate.ClientCallback clientCallback, Looper looper, KeyEvent.DispatcherState dispatcherState, InputChannel readChannel) { synchronized (mSessionLock) { mCallbackImpl = new CallbackImpl(this, clientCallback); mDispatcherState = dispatcherState; mHandler = new Handler(looper, null, true); mReadChannel = readChannel; mInputEventReceiver = new ImeInputEventReceiver(mReadChannel, mHandler.getLooper(), mCancellationGroup, mDispatcherState, mCallbackImpl.mOriginalCallback); } } private static final class KeyEventCallbackAdaptor implements KeyEvent.Callback { private final MultiClientInputMethodServiceDelegate.ClientCallback mLocalCallback; KeyEventCallbackAdaptor( MultiClientInputMethodServiceDelegate.ClientCallback callback) { mLocalCallback = callback; } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { return mLocalCallback.onKeyDown(keyCode, event); } @Override public boolean onKeyLongPress(int keyCode, KeyEvent event) { return mLocalCallback.onKeyLongPress(keyCode, event); } @Override public boolean onKeyUp(int keyCode, KeyEvent event) { return mLocalCallback.onKeyUp(keyCode, event); } @Override public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) { return mLocalCallback.onKeyMultiple(keyCode, event); } } private static final class ImeInputEventReceiver extends InputEventReceiver { private final CancellationGroup mCancellationGroupOnFinishSession; private final KeyEvent.DispatcherState mDispatcherState; private final MultiClientInputMethodServiceDelegate.ClientCallback mClientCallback; private final KeyEventCallbackAdaptor mKeyEventCallbackAdaptor; ImeInputEventReceiver(InputChannel readChannel, Looper looper, CancellationGroup cancellationGroupOnFinishSession, KeyEvent.DispatcherState dispatcherState, MultiClientInputMethodServiceDelegate.ClientCallback callback) { super(readChannel, looper); mCancellationGroupOnFinishSession = cancellationGroupOnFinishSession; mDispatcherState = dispatcherState; mClientCallback = callback; mKeyEventCallbackAdaptor = new KeyEventCallbackAdaptor(callback); } @Override public void onInputEvent(InputEvent event) { if (mCancellationGroupOnFinishSession.isCanceled()) { // The session has been finished. finishInputEvent(event, false); return; } boolean handled = false; try { if (event instanceof KeyEvent) { final KeyEvent keyEvent = (KeyEvent) event; handled = keyEvent.dispatch(mKeyEventCallbackAdaptor, mDispatcherState, mKeyEventCallbackAdaptor); } else { final MotionEvent motionEvent = (MotionEvent) event; if (motionEvent.isFromSource(InputDevice.SOURCE_CLASS_TRACKBALL)) { handled = mClientCallback.onTrackballEvent(motionEvent); } else { handled = mClientCallback.onGenericMotionEvent(motionEvent); } } } finally { finishInputEvent(event, handled); } } } private static final class InputMethodSessionImpl extends IInputMethodSession.Stub { private final Object mSessionLock; @GuardedBy("mSessionLock") private CallbackImpl mCallbackImpl; @GuardedBy("mSessionLock") private Handler mHandler; private final CancellationGroup mCancellationGroupOnFinishSession; InputMethodSessionImpl(Object lock, CallbackImpl callback, Handler handler, CancellationGroup cancellationGroupOnFinishSession) { mSessionLock = lock; mCallbackImpl = callback; mHandler = handler; mCancellationGroupOnFinishSession = cancellationGroupOnFinishSession; } @Override public void updateExtractedText(int token, ExtractedText text) { reportNotSupported(); } @Override public void updateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd) { synchronized (mSessionLock) { if (mCallbackImpl == null || mHandler == null) { return; } final SomeArgs args = SomeArgs.obtain(); args.argi1 = oldSelStart; args.argi2 = oldSelEnd; args.argi3 = newSelStart; args.argi4 = newSelEnd; args.argi5 = candidatesStart; args.argi6 = candidatesEnd; mHandler.sendMessage(PooledLambda.obtainMessage( CallbackImpl::updateSelection, mCallbackImpl, args)); } } @Override public void viewClicked(boolean focusChanged) { reportNotSupported(); } @Override public void updateCursor(Rect newCursor) { reportNotSupported(); } @Override public void displayCompletions(CompletionInfo[] completions) { synchronized (mSessionLock) { if (mCallbackImpl == null || mHandler == null) { return; } mHandler.sendMessage(PooledLambda.obtainMessage( CallbackImpl::displayCompletions, mCallbackImpl, completions)); } } @Override public void appPrivateCommand(String action, Bundle data) { synchronized (mSessionLock) { if (mCallbackImpl == null || mHandler == null) { return; } mHandler.sendMessage(PooledLambda.obtainMessage( CallbackImpl::appPrivateCommand, mCallbackImpl, action, data)); } } @Override public void finishSession() { synchronized (mSessionLock) { if (mCallbackImpl == null || mHandler == null) { return; } mCancellationGroupOnFinishSession.cancelAll(); mHandler.sendMessage(PooledLambda.obtainMessage( CallbackImpl::finishSession, mCallbackImpl)); mCallbackImpl = null; mHandler = null; } } @Override public void updateCursorAnchorInfo(CursorAnchorInfo info) { synchronized (mSessionLock) { if (mCallbackImpl == null || mHandler == null) { return; } mHandler.sendMessage(PooledLambda.obtainMessage( CallbackImpl::updateCursorAnchorInfo, mCallbackImpl, info)); } } @Override public final void notifyImeHidden() { // no-op for multi-session since IME is responsible controlling navigation bar buttons. reportNotSupported(); } @Override public void removeImeSurface() { // no-op for multi-session reportNotSupported(); } @Override public void finishInput() throws RemoteException { // no-op for multi-session reportNotSupported(); } } private static final class MultiClientInputMethodSessionImpl extends IMultiClientInputMethodSession.Stub { private final Object mSessionLock; @GuardedBy("mSessionLock") private CallbackImpl mCallbackImpl; @GuardedBy("mSessionLock") private Handler mHandler; private final CancellationGroup mCancellationGroupOnFinishSession; MultiClientInputMethodSessionImpl(Object lock, CallbackImpl callback, Handler handler, CancellationGroup cancellationGroupOnFinishSession) { mSessionLock = lock; mCallbackImpl = callback; mHandler = handler; mCancellationGroupOnFinishSession = cancellationGroupOnFinishSession; } @Override public void startInputOrWindowGainedFocus(@Nullable IInputContext inputContext, int missingMethods, @Nullable EditorInfo editorInfo, int controlFlags, @SoftInputModeFlags int softInputMode, int windowHandle) { synchronized (mSessionLock) { if (mCallbackImpl == null || mHandler == null) { return; } final SomeArgs args = SomeArgs.obtain(); // TODO(Bug 119211536): Remove dependency on AbstractInputMethodService from ICW final WeakReference