All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.android.inputmethodservice.MultiClientInputMethodClientCallbackAdaptor Maven / Gradle / Ivy

/*
 * 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.

* *
    *
  • {@link IInputMethodSession} - from the IME client
  • *
  • {@link IMultiClientInputMethodSession} - from MultiClientInputMethodManagerService
  • *
  • {@link InputChannel} - from the IME client
  • *
* *

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 fakeIMS = new WeakReference<>(null); args.arg1 = (inputContext == null) ? null : new InputConnectionWrapper(fakeIMS, inputContext, missingMethods, mCancellationGroupOnFinishSession); args.arg2 = editorInfo; args.argi1 = controlFlags; args.argi2 = softInputMode; args.argi3 = windowHandle; mHandler.sendMessage(PooledLambda.obtainMessage( CallbackImpl::startInputOrWindowGainedFocus, mCallbackImpl, args)); } } @Override public void showSoftInput(int flags, ResultReceiver resultReceiver) { synchronized (mSessionLock) { if (mCallbackImpl == null || mHandler == null) { return; } mHandler.sendMessage(PooledLambda.obtainMessage( CallbackImpl::showSoftInput, mCallbackImpl, flags, resultReceiver)); } } @Override public void hideSoftInput(int flags, ResultReceiver resultReceiver) { synchronized (mSessionLock) { if (mCallbackImpl == null || mHandler == null) { return; } mHandler.sendMessage(PooledLambda.obtainMessage( CallbackImpl::hideSoftInput, mCallbackImpl, flags, resultReceiver)); } } } /** * The maim part of adaptor to {@link MultiClientInputMethodServiceDelegate.ClientCallback}. */ @WorkerThread private static final class CallbackImpl { private final MultiClientInputMethodClientCallbackAdaptor mCallbackAdaptor; private final MultiClientInputMethodServiceDelegate.ClientCallback mOriginalCallback; private boolean mFinished = false; CallbackImpl(MultiClientInputMethodClientCallbackAdaptor callbackAdaptor, MultiClientInputMethodServiceDelegate.ClientCallback callback) { mCallbackAdaptor = callbackAdaptor; mOriginalCallback = callback; } void updateSelection(SomeArgs args) { try { if (mFinished) { return; } mOriginalCallback.onUpdateSelection(args.argi1, args.argi2, args.argi3, args.argi4, args.argi5, args.argi6); } finally { args.recycle(); } } void displayCompletions(CompletionInfo[] completions) { if (mFinished) { return; } mOriginalCallback.onDisplayCompletions(completions); } void appPrivateCommand(String action, Bundle data) { if (mFinished) { return; } mOriginalCallback.onAppPrivateCommand(action, data); } void finishSession() { if (mFinished) { return; } mFinished = true; mOriginalCallback.onFinishSession(); synchronized (mCallbackAdaptor.mSessionLock) { mCallbackAdaptor.mDispatcherState = null; if (mCallbackAdaptor.mReadChannel != null) { mCallbackAdaptor.mReadChannel.dispose(); mCallbackAdaptor.mReadChannel = null; } mCallbackAdaptor.mInputEventReceiver = null; } } void updateCursorAnchorInfo(CursorAnchorInfo info) { if (mFinished) { return; } mOriginalCallback.onUpdateCursorAnchorInfo(info); } void startInputOrWindowGainedFocus(SomeArgs args) { try { if (mFinished) { return; } final InputConnectionWrapper inputConnection = (InputConnectionWrapper) args.arg1; final EditorInfo editorInfo = (EditorInfo) args.arg2; final int startInputFlags = args.argi1; final int softInputMode = args.argi2; final int windowHandle = args.argi3; mOriginalCallback.onStartInputOrWindowGainedFocus(inputConnection, editorInfo, startInputFlags, softInputMode, windowHandle); } finally { args.recycle(); } } void showSoftInput(int flags, ResultReceiver resultReceiver) { if (mFinished) { return; } mOriginalCallback.onShowSoftInput(flags, resultReceiver); } void hideSoftInput(int flags, ResultReceiver resultReceiver) { if (mFinished) { return; } mOriginalCallback.onHideSoftInput(flags, resultReceiver); } } private static void reportNotSupported() { if (DEBUG) { Log.d(TAG, Debug.getCaller() + " is not supported"); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy