Loading core/java/android/inputmethodservice/IInputMethodWrapper.java +17 −15 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package android.inputmethodservice; import android.annotation.BinderThread; import android.annotation.MainThread; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.pm.PackageManager; Loading @@ -37,6 +38,7 @@ import android.view.inputmethod.InputMethodSession; import android.view.inputmethod.InputMethodSubtype; import com.android.internal.inputmethod.IInputMethodPrivilegedOperations; import com.android.internal.inputmethod.CancellationGroup; import com.android.internal.os.HandlerCaller; import com.android.internal.os.SomeArgs; import com.android.internal.view.IInlineSuggestionsRequestCallback; Loading @@ -52,7 +54,6 @@ import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; /** * Implements the internal IInputMethod interface to convert incoming calls Loading Loading @@ -90,12 +91,13 @@ class IInputMethodWrapper extends IInputMethod.Stub * * <p>This field must be set and cleared only from the binder thread(s), where the system * guarantees that {@link #bindInput(InputBinding)}, * {@link #startInput(IBinder, IInputContext, int, EditorInfo, boolean)}, and * {@link #startInput(IBinder, IInputContext, int, EditorInfo, boolean, boolean)}, and * {@link #unbindInput()} are called with the same order as the original calls * in {@link com.android.server.inputmethod.InputMethodManagerService}. * See {@link IBinder#FLAG_ONEWAY} for detailed semantics.</p> */ AtomicBoolean mIsUnbindIssued = null; @Nullable CancellationGroup mCancellationGroup = null; // NOTE: we should have a cache of these. static final class InputMethodSessionCallbackWrapper implements InputMethod.SessionCallback { Loading Loading @@ -187,11 +189,11 @@ class IInputMethodWrapper extends IInputMethod.Stub final IBinder startInputToken = (IBinder) args.arg1; final IInputContext inputContext = (IInputContext) args.arg2; final EditorInfo info = (EditorInfo) args.arg3; final AtomicBoolean isUnbindIssued = (AtomicBoolean) args.arg4; final CancellationGroup cancellationGroup = (CancellationGroup) args.arg4; SomeArgs moreArgs = (SomeArgs) args.arg5; final InputConnection ic = inputContext != null ? new InputConnectionWrapper( mTarget, inputContext, moreArgs.argi3, isUnbindIssued) mTarget, inputContext, moreArgs.argi3, cancellationGroup) : null; info.makeCompatible(mTargetSdkVersion); inputMethod.dispatchStartInputWithToken( Loading Loading @@ -295,15 +297,15 @@ class IInputMethodWrapper extends IInputMethod.Stub @BinderThread @Override public void bindInput(InputBinding binding) { if (mIsUnbindIssued != null) { if (mCancellationGroup != null) { Log.e(TAG, "bindInput must be paired with unbindInput."); } mIsUnbindIssued = new AtomicBoolean(); mCancellationGroup = new CancellationGroup(); // This IInputContext is guaranteed to implement all the methods. final int missingMethodFlags = 0; InputConnection ic = new InputConnectionWrapper(mTarget, IInputContext.Stub.asInterface(binding.getConnectionToken()), missingMethodFlags, mIsUnbindIssued); mCancellationGroup); InputBinding nu = new InputBinding(ic, binding); mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_INPUT_CONTEXT, nu)); } Loading @@ -311,10 +313,10 @@ class IInputMethodWrapper extends IInputMethod.Stub @BinderThread @Override public void unbindInput() { if (mIsUnbindIssued != null) { if (mCancellationGroup != null) { // Signal the flag then forget it. mIsUnbindIssued.set(true); mIsUnbindIssued = null; mCancellationGroup.cancelAll(); mCancellationGroup = null; } else { Log.e(TAG, "unbindInput must be paired with bindInput."); } Loading @@ -326,16 +328,16 @@ class IInputMethodWrapper extends IInputMethod.Stub public void startInput(IBinder startInputToken, IInputContext inputContext, @InputConnectionInspector.MissingMethodFlags final int missingMethods, EditorInfo attribute, boolean restarting, boolean shouldPreRenderIme) { if (mIsUnbindIssued == null) { if (mCancellationGroup == null) { Log.e(TAG, "startInput must be called after bindInput."); mIsUnbindIssued = new AtomicBoolean(); mCancellationGroup = new CancellationGroup(); } SomeArgs args = SomeArgs.obtain(); args.argi1 = restarting ? 1 : 0; args.argi2 = shouldPreRenderIme ? 1 : 0; args.argi3 = missingMethods; mCaller.executeOrSendMessage(mCaller.obtainMessageOOOOO( DO_START_INPUT, startInputToken, inputContext, attribute, mIsUnbindIssued, args)); mCaller.executeOrSendMessage(mCaller.obtainMessageOOOOO(DO_START_INPUT, startInputToken, inputContext, attribute, mCancellationGroup, args)); } @BinderThread Loading core/java/android/inputmethodservice/MultiClientInputMethodClientCallbackAdaptor.java +18 −17 Original line number Diff line number Diff line Loading @@ -39,6 +39,7 @@ import android.view.inputmethod.ExtractedText; import com.android.internal.annotations.GuardedBy; import com.android.internal.inputmethod.IMultiClientInputMethodSession; import com.android.internal.inputmethod.CancellationGroup; import com.android.internal.os.SomeArgs; import com.android.internal.util.function.pooled.PooledLambda; import com.android.internal.view.IInputContext; Loading @@ -46,7 +47,6 @@ import com.android.internal.view.IInputMethodSession; import com.android.internal.view.InputConnectionWrapper; import java.lang.ref.WeakReference; import java.util.concurrent.atomic.AtomicBoolean; /** * Re-dispatches all the incoming per-client events to the specified {@link Looper} thread. Loading Loading @@ -80,19 +80,19 @@ final class MultiClientInputMethodClientCallbackAdaptor { @Nullable InputEventReceiver mInputEventReceiver; private final AtomicBoolean mFinished = new AtomicBoolean(false); private final CancellationGroup mCancellationGroup = new CancellationGroup(); IInputMethodSession.Stub createIInputMethodSession() { synchronized (mSessionLock) { return new InputMethodSessionImpl( mSessionLock, mCallbackImpl, mHandler, mFinished); mSessionLock, mCallbackImpl, mHandler, mCancellationGroup); } } IMultiClientInputMethodSession.Stub createIMultiClientInputMethodSession() { synchronized (mSessionLock) { return new MultiClientInputMethodSessionImpl( mSessionLock, mCallbackImpl, mHandler, mFinished); mSessionLock, mCallbackImpl, mHandler, mCancellationGroup); } } Loading @@ -105,7 +105,7 @@ final class MultiClientInputMethodClientCallbackAdaptor { mHandler = new Handler(looper, null, true); mReadChannel = readChannel; mInputEventReceiver = new ImeInputEventReceiver(mReadChannel, mHandler.getLooper(), mFinished, mDispatcherState, mCallbackImpl.mOriginalCallback); mCancellationGroup, mDispatcherState, mCallbackImpl.mOriginalCallback); } } Loading Loading @@ -139,16 +139,17 @@ final class MultiClientInputMethodClientCallbackAdaptor { } private static final class ImeInputEventReceiver extends InputEventReceiver { private final AtomicBoolean mFinished; private final CancellationGroup mCancellationGroupOnFinishSession; private final KeyEvent.DispatcherState mDispatcherState; private final MultiClientInputMethodServiceDelegate.ClientCallback mClientCallback; private final KeyEventCallbackAdaptor mKeyEventCallbackAdaptor; ImeInputEventReceiver(InputChannel readChannel, Looper looper, AtomicBoolean finished, ImeInputEventReceiver(InputChannel readChannel, Looper looper, CancellationGroup cancellationGroupOnFinishSession, KeyEvent.DispatcherState dispatcherState, MultiClientInputMethodServiceDelegate.ClientCallback callback) { super(readChannel, looper); mFinished = finished; mCancellationGroupOnFinishSession = cancellationGroupOnFinishSession; mDispatcherState = dispatcherState; mClientCallback = callback; mKeyEventCallbackAdaptor = new KeyEventCallbackAdaptor(callback); Loading @@ -156,7 +157,7 @@ final class MultiClientInputMethodClientCallbackAdaptor { @Override public void onInputEvent(InputEvent event) { if (mFinished.get()) { if (mCancellationGroupOnFinishSession.isCanceled()) { // The session has been finished. finishInputEvent(event, false); return; Loading Loading @@ -187,14 +188,14 @@ final class MultiClientInputMethodClientCallbackAdaptor { private CallbackImpl mCallbackImpl; @GuardedBy("mSessionLock") private Handler mHandler; private final AtomicBoolean mSessionFinished; private final CancellationGroup mCancellationGroupOnFinishSession; InputMethodSessionImpl(Object lock, CallbackImpl callback, Handler handler, AtomicBoolean sessionFinished) { CancellationGroup cancellationGroupOnFinishSession) { mSessionLock = lock; mCallbackImpl = callback; mHandler = handler; mSessionFinished = sessionFinished; mCancellationGroupOnFinishSession = cancellationGroupOnFinishSession; } @Override Loading Loading @@ -272,7 +273,7 @@ final class MultiClientInputMethodClientCallbackAdaptor { if (mCallbackImpl == null || mHandler == null) { return; } mSessionFinished.set(true); mCancellationGroupOnFinishSession.cancelAll(); mHandler.sendMessage(PooledLambda.obtainMessage( CallbackImpl::finishSession, mCallbackImpl)); mCallbackImpl = null; Loading Loading @@ -311,14 +312,14 @@ final class MultiClientInputMethodClientCallbackAdaptor { private CallbackImpl mCallbackImpl; @GuardedBy("mSessionLock") private Handler mHandler; private final AtomicBoolean mSessionFinished; private final CancellationGroup mCancellationGroupOnFinishSession; MultiClientInputMethodSessionImpl(Object lock, CallbackImpl callback, Handler handler, AtomicBoolean sessionFinished) { Handler handler, CancellationGroup cancellationGroupOnFinishSession) { mSessionLock = lock; mCallbackImpl = callback; mHandler = handler; mSessionFinished = sessionFinished; mCancellationGroupOnFinishSession = cancellationGroupOnFinishSession; } @Override Loading @@ -335,7 +336,7 @@ final class MultiClientInputMethodClientCallbackAdaptor { new WeakReference<>(null); args.arg1 = (inputContext == null) ? null : new InputConnectionWrapper(fakeIMS, inputContext, missingMethods, mSessionFinished); mCancellationGroupOnFinishSession); args.arg2 = editorInfo; args.argi1 = controlFlags; args.argi2 = softInputMode; Loading core/java/com/android/internal/inputmethod/CancellationGroup.java 0 → 100644 +348 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 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 com.android.internal.inputmethod; import android.annotation.AnyThread; import android.annotation.NonNull; import android.annotation.Nullable; import com.android.internal.annotations.GuardedBy; import java.util.ArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; /** * A utility class, which works as both a factory class of completable objects and a cancellation * signal to cancel all the completable objects created by this object. */ public final class CancellationGroup { private final Object mLock = new Object(); /** * List of {@link CountDownLatch}, which can be used to propagate {@link #cancelAll()} to * completable objects. * * <p>This will be lazily instantiated to avoid unnecessary object allocations.</p> */ @Nullable @GuardedBy("mLock") private ArrayList<CountDownLatch> mLatchList = null; @GuardedBy("mLock") private boolean mCanceled = false; /** * An inner class to consolidate completable object types supported by * {@link CancellationGroup}. */ public static final class Completable { /** * Not intended to be instantiated. */ private Completable() { } /** * Base class of all the completable types supported by {@link CancellationGroup}. */ protected static class ValueBase { /** * {@link CountDownLatch} to be signaled to unblock {@link #await(int, TimeUnit)}. */ private final CountDownLatch mLatch = new CountDownLatch(1); /** * {@link CancellationGroup} to which this completable object belongs. */ @NonNull private final CancellationGroup mParentGroup; /** * Lock {@link Object} to guard complete operations within this class. */ protected final Object mValueLock = new Object(); /** * {@code true} after {@link #onComplete()} gets called. */ @GuardedBy("mValueLock") protected boolean mHasValue = false; /** * Base constructor. * * @param parentGroup {@link CancellationGroup} to which this completable object * belongs. */ protected ValueBase(@NonNull CancellationGroup parentGroup) { mParentGroup = parentGroup; } /** * @return {@link true} if {@link #onComplete()} gets called already. */ @AnyThread public boolean hasValue() { synchronized (mValueLock) { return mHasValue; } } /** * Called by subclasses to signale {@link #mLatch}. */ @AnyThread protected void onComplete() { mLatch.countDown(); } /** * Blocks the calling thread until at least one of the following conditions is met. * * <p> * <ol> * <li>This object becomes ready to return the value.</li> * <li>{@link CancellationGroup#cancelAll()} gets called.</li> * <li>The given timeout period has passed.</li> * </ol> * </p> * * <p>The caller can distinguish the case 1 and case 2 by calling {@link #hasValue()}. * Note that the return value of {@link #hasValue()} can change from {@code false} to * {@code true} at any time, even after this methods finishes with returning * {@code true}.</p> * * @param timeout length of the timeout. * @param timeUnit unit of {@code timeout}. * @return {@code false} if and only if the given timeout period has passed. Otherwise * {@code true}. */ @AnyThread public boolean await(int timeout, @NonNull TimeUnit timeUnit) { if (!mParentGroup.registerLatch(mLatch)) { // Already canceled when this method gets called. return false; } try { return mLatch.await(timeout, timeUnit); } catch (InterruptedException e) { return true; } finally { mParentGroup.unregisterLatch(mLatch); } } } /** * Completable object of integer primitive. */ public static final class Int extends ValueBase { @GuardedBy("mValueLock") private int mValue = 0; private Int(@NonNull CancellationGroup factory) { super(factory); } /** * Notify when a value is set to this completable object. * * @param value value to be set. */ @AnyThread void onComplete(int value) { synchronized (mValueLock) { if (mHasValue) { throw new UnsupportedOperationException( "onComplete() cannot be called multiple times"); } mValue = value; mHasValue = true; } onComplete(); } /** * @return value associated with this object. * @throws UnsupportedOperationException when called while {@link #hasValue()} returns * {@code false}. */ @AnyThread public int getValue() { synchronized (mValueLock) { if (!mHasValue) { throw new UnsupportedOperationException( "getValue() is allowed only if hasValue() returns true"); } return mValue; } } } /** * Base class of completable object types. * * @param <T> type associated with this completable object. */ public static class Values<T> extends ValueBase { @GuardedBy("mValueLock") @Nullable private T mValue = null; protected Values(@NonNull CancellationGroup factory) { super(factory); } /** * Notify when a value is set to this completable value object. * * @param value value to be set. */ @AnyThread void onComplete(@Nullable T value) { synchronized (mValueLock) { if (mHasValue) { throw new UnsupportedOperationException( "onComplete() cannot be called multiple times"); } mValue = value; mHasValue = true; } onComplete(); } /** * @return value associated with this object. * @throws UnsupportedOperationException when called while {@link #hasValue()} returns * {@code false}. */ @AnyThread @Nullable public T getValue() { synchronized (mValueLock) { if (!mHasValue) { throw new UnsupportedOperationException( "getValue() is allowed only if hasValue() returns true"); } return mValue; } } } /** * Completable object of {@link java.lang.CharSequence}. */ public static final class CharSequence extends Values<java.lang.CharSequence> { private CharSequence(@NonNull CancellationGroup factory) { super(factory); } } /** * Completable object of {@link android.view.inputmethod.ExtractedText}. */ public static final class ExtractedText extends Values<android.view.inputmethod.ExtractedText> { private ExtractedText(@NonNull CancellationGroup factory) { super(factory); } } } /** * @return an instance of {@link Completable.Int} that is associated with this * {@link CancellationGroup}. */ @AnyThread public Completable.Int createCompletableInt() { return new Completable.Int(this); } /** * @return an instance of {@link Completable.CharSequence} that is associated with this * {@link CancellationGroup}. */ @AnyThread public Completable.CharSequence createCompletableCharSequence() { return new Completable.CharSequence(this); } /** * @return an instance of {@link Completable.ExtractedText} that is associated with this * {@link CancellationGroup}. */ @AnyThread public Completable.ExtractedText createCompletableExtractedText() { return new Completable.ExtractedText(this); } @AnyThread private boolean registerLatch(@NonNull CountDownLatch latch) { synchronized (mLock) { if (mCanceled) { return false; } if (mLatchList == null) { // Set the initial capacity to 1 with an assumption that usually there is up to 1 // on-going operation. mLatchList = new ArrayList<>(1); } mLatchList.add(latch); return true; } } @AnyThread private void unregisterLatch(@NonNull CountDownLatch latch) { synchronized (mLock) { if (mLatchList != null) { mLatchList.remove(latch); } } } /** * Cancel all the completable objects created from this {@link CancellationGroup}. * * <p>Secondary calls will be silently ignored.</p> */ @AnyThread public void cancelAll() { synchronized (mLock) { if (!mCanceled) { mCanceled = true; if (mLatchList != null) { mLatchList.forEach(CountDownLatch::countDown); mLatchList.clear(); mLatchList = null; } } } } /** * @return {@code true} if {@link #cancelAll()} is already called. {@code false} otherwise. */ @AnyThread public boolean isCanceled() { synchronized (mLock) { return mCanceled; } } } core/java/com/android/internal/inputmethod/ICharSequenceResultCallback.aidl 0 → 100644 +21 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 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 com.android.internal.inputmethod; oneway interface ICharSequenceResultCallback { void onResult(in CharSequence result); } core/java/com/android/internal/view/IInputContextCallback.aidl→core/java/com/android/internal/inputmethod/IExtractedTextResultCallback.aidl +4 −13 Original line number Diff line number Diff line /* * Copyright (C) 2008 The Android Open Source Project * Copyright (C) 2020 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. Loading @@ -14,19 +14,10 @@ * limitations under the License. */ package com.android.internal.view; package com.android.internal.inputmethod; import android.view.inputmethod.ExtractedText; /** * {@hide} */ oneway interface IInputContextCallback { void setTextBeforeCursor(CharSequence textBeforeCursor, int seq); void setTextAfterCursor(CharSequence textAfterCursor, int seq); void setCursorCapsMode(int capsMode, int seq); void setExtractedText(in ExtractedText extractedText, int seq); void setSelectedText(CharSequence selectedText, int seq); void setRequestUpdateCursorAnchorInfoResult(boolean result, int seq); void setCommitContentResult(boolean result, int seq); oneway interface IExtractedTextResultCallback { void onResult(in ExtractedText result); } Loading
core/java/android/inputmethodservice/IInputMethodWrapper.java +17 −15 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package android.inputmethodservice; import android.annotation.BinderThread; import android.annotation.MainThread; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.pm.PackageManager; Loading @@ -37,6 +38,7 @@ import android.view.inputmethod.InputMethodSession; import android.view.inputmethod.InputMethodSubtype; import com.android.internal.inputmethod.IInputMethodPrivilegedOperations; import com.android.internal.inputmethod.CancellationGroup; import com.android.internal.os.HandlerCaller; import com.android.internal.os.SomeArgs; import com.android.internal.view.IInlineSuggestionsRequestCallback; Loading @@ -52,7 +54,6 @@ import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; /** * Implements the internal IInputMethod interface to convert incoming calls Loading Loading @@ -90,12 +91,13 @@ class IInputMethodWrapper extends IInputMethod.Stub * * <p>This field must be set and cleared only from the binder thread(s), where the system * guarantees that {@link #bindInput(InputBinding)}, * {@link #startInput(IBinder, IInputContext, int, EditorInfo, boolean)}, and * {@link #startInput(IBinder, IInputContext, int, EditorInfo, boolean, boolean)}, and * {@link #unbindInput()} are called with the same order as the original calls * in {@link com.android.server.inputmethod.InputMethodManagerService}. * See {@link IBinder#FLAG_ONEWAY} for detailed semantics.</p> */ AtomicBoolean mIsUnbindIssued = null; @Nullable CancellationGroup mCancellationGroup = null; // NOTE: we should have a cache of these. static final class InputMethodSessionCallbackWrapper implements InputMethod.SessionCallback { Loading Loading @@ -187,11 +189,11 @@ class IInputMethodWrapper extends IInputMethod.Stub final IBinder startInputToken = (IBinder) args.arg1; final IInputContext inputContext = (IInputContext) args.arg2; final EditorInfo info = (EditorInfo) args.arg3; final AtomicBoolean isUnbindIssued = (AtomicBoolean) args.arg4; final CancellationGroup cancellationGroup = (CancellationGroup) args.arg4; SomeArgs moreArgs = (SomeArgs) args.arg5; final InputConnection ic = inputContext != null ? new InputConnectionWrapper( mTarget, inputContext, moreArgs.argi3, isUnbindIssued) mTarget, inputContext, moreArgs.argi3, cancellationGroup) : null; info.makeCompatible(mTargetSdkVersion); inputMethod.dispatchStartInputWithToken( Loading Loading @@ -295,15 +297,15 @@ class IInputMethodWrapper extends IInputMethod.Stub @BinderThread @Override public void bindInput(InputBinding binding) { if (mIsUnbindIssued != null) { if (mCancellationGroup != null) { Log.e(TAG, "bindInput must be paired with unbindInput."); } mIsUnbindIssued = new AtomicBoolean(); mCancellationGroup = new CancellationGroup(); // This IInputContext is guaranteed to implement all the methods. final int missingMethodFlags = 0; InputConnection ic = new InputConnectionWrapper(mTarget, IInputContext.Stub.asInterface(binding.getConnectionToken()), missingMethodFlags, mIsUnbindIssued); mCancellationGroup); InputBinding nu = new InputBinding(ic, binding); mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_INPUT_CONTEXT, nu)); } Loading @@ -311,10 +313,10 @@ class IInputMethodWrapper extends IInputMethod.Stub @BinderThread @Override public void unbindInput() { if (mIsUnbindIssued != null) { if (mCancellationGroup != null) { // Signal the flag then forget it. mIsUnbindIssued.set(true); mIsUnbindIssued = null; mCancellationGroup.cancelAll(); mCancellationGroup = null; } else { Log.e(TAG, "unbindInput must be paired with bindInput."); } Loading @@ -326,16 +328,16 @@ class IInputMethodWrapper extends IInputMethod.Stub public void startInput(IBinder startInputToken, IInputContext inputContext, @InputConnectionInspector.MissingMethodFlags final int missingMethods, EditorInfo attribute, boolean restarting, boolean shouldPreRenderIme) { if (mIsUnbindIssued == null) { if (mCancellationGroup == null) { Log.e(TAG, "startInput must be called after bindInput."); mIsUnbindIssued = new AtomicBoolean(); mCancellationGroup = new CancellationGroup(); } SomeArgs args = SomeArgs.obtain(); args.argi1 = restarting ? 1 : 0; args.argi2 = shouldPreRenderIme ? 1 : 0; args.argi3 = missingMethods; mCaller.executeOrSendMessage(mCaller.obtainMessageOOOOO( DO_START_INPUT, startInputToken, inputContext, attribute, mIsUnbindIssued, args)); mCaller.executeOrSendMessage(mCaller.obtainMessageOOOOO(DO_START_INPUT, startInputToken, inputContext, attribute, mCancellationGroup, args)); } @BinderThread Loading
core/java/android/inputmethodservice/MultiClientInputMethodClientCallbackAdaptor.java +18 −17 Original line number Diff line number Diff line Loading @@ -39,6 +39,7 @@ import android.view.inputmethod.ExtractedText; import com.android.internal.annotations.GuardedBy; import com.android.internal.inputmethod.IMultiClientInputMethodSession; import com.android.internal.inputmethod.CancellationGroup; import com.android.internal.os.SomeArgs; import com.android.internal.util.function.pooled.PooledLambda; import com.android.internal.view.IInputContext; Loading @@ -46,7 +47,6 @@ import com.android.internal.view.IInputMethodSession; import com.android.internal.view.InputConnectionWrapper; import java.lang.ref.WeakReference; import java.util.concurrent.atomic.AtomicBoolean; /** * Re-dispatches all the incoming per-client events to the specified {@link Looper} thread. Loading Loading @@ -80,19 +80,19 @@ final class MultiClientInputMethodClientCallbackAdaptor { @Nullable InputEventReceiver mInputEventReceiver; private final AtomicBoolean mFinished = new AtomicBoolean(false); private final CancellationGroup mCancellationGroup = new CancellationGroup(); IInputMethodSession.Stub createIInputMethodSession() { synchronized (mSessionLock) { return new InputMethodSessionImpl( mSessionLock, mCallbackImpl, mHandler, mFinished); mSessionLock, mCallbackImpl, mHandler, mCancellationGroup); } } IMultiClientInputMethodSession.Stub createIMultiClientInputMethodSession() { synchronized (mSessionLock) { return new MultiClientInputMethodSessionImpl( mSessionLock, mCallbackImpl, mHandler, mFinished); mSessionLock, mCallbackImpl, mHandler, mCancellationGroup); } } Loading @@ -105,7 +105,7 @@ final class MultiClientInputMethodClientCallbackAdaptor { mHandler = new Handler(looper, null, true); mReadChannel = readChannel; mInputEventReceiver = new ImeInputEventReceiver(mReadChannel, mHandler.getLooper(), mFinished, mDispatcherState, mCallbackImpl.mOriginalCallback); mCancellationGroup, mDispatcherState, mCallbackImpl.mOriginalCallback); } } Loading Loading @@ -139,16 +139,17 @@ final class MultiClientInputMethodClientCallbackAdaptor { } private static final class ImeInputEventReceiver extends InputEventReceiver { private final AtomicBoolean mFinished; private final CancellationGroup mCancellationGroupOnFinishSession; private final KeyEvent.DispatcherState mDispatcherState; private final MultiClientInputMethodServiceDelegate.ClientCallback mClientCallback; private final KeyEventCallbackAdaptor mKeyEventCallbackAdaptor; ImeInputEventReceiver(InputChannel readChannel, Looper looper, AtomicBoolean finished, ImeInputEventReceiver(InputChannel readChannel, Looper looper, CancellationGroup cancellationGroupOnFinishSession, KeyEvent.DispatcherState dispatcherState, MultiClientInputMethodServiceDelegate.ClientCallback callback) { super(readChannel, looper); mFinished = finished; mCancellationGroupOnFinishSession = cancellationGroupOnFinishSession; mDispatcherState = dispatcherState; mClientCallback = callback; mKeyEventCallbackAdaptor = new KeyEventCallbackAdaptor(callback); Loading @@ -156,7 +157,7 @@ final class MultiClientInputMethodClientCallbackAdaptor { @Override public void onInputEvent(InputEvent event) { if (mFinished.get()) { if (mCancellationGroupOnFinishSession.isCanceled()) { // The session has been finished. finishInputEvent(event, false); return; Loading Loading @@ -187,14 +188,14 @@ final class MultiClientInputMethodClientCallbackAdaptor { private CallbackImpl mCallbackImpl; @GuardedBy("mSessionLock") private Handler mHandler; private final AtomicBoolean mSessionFinished; private final CancellationGroup mCancellationGroupOnFinishSession; InputMethodSessionImpl(Object lock, CallbackImpl callback, Handler handler, AtomicBoolean sessionFinished) { CancellationGroup cancellationGroupOnFinishSession) { mSessionLock = lock; mCallbackImpl = callback; mHandler = handler; mSessionFinished = sessionFinished; mCancellationGroupOnFinishSession = cancellationGroupOnFinishSession; } @Override Loading Loading @@ -272,7 +273,7 @@ final class MultiClientInputMethodClientCallbackAdaptor { if (mCallbackImpl == null || mHandler == null) { return; } mSessionFinished.set(true); mCancellationGroupOnFinishSession.cancelAll(); mHandler.sendMessage(PooledLambda.obtainMessage( CallbackImpl::finishSession, mCallbackImpl)); mCallbackImpl = null; Loading Loading @@ -311,14 +312,14 @@ final class MultiClientInputMethodClientCallbackAdaptor { private CallbackImpl mCallbackImpl; @GuardedBy("mSessionLock") private Handler mHandler; private final AtomicBoolean mSessionFinished; private final CancellationGroup mCancellationGroupOnFinishSession; MultiClientInputMethodSessionImpl(Object lock, CallbackImpl callback, Handler handler, AtomicBoolean sessionFinished) { Handler handler, CancellationGroup cancellationGroupOnFinishSession) { mSessionLock = lock; mCallbackImpl = callback; mHandler = handler; mSessionFinished = sessionFinished; mCancellationGroupOnFinishSession = cancellationGroupOnFinishSession; } @Override Loading @@ -335,7 +336,7 @@ final class MultiClientInputMethodClientCallbackAdaptor { new WeakReference<>(null); args.arg1 = (inputContext == null) ? null : new InputConnectionWrapper(fakeIMS, inputContext, missingMethods, mSessionFinished); mCancellationGroupOnFinishSession); args.arg2 = editorInfo; args.argi1 = controlFlags; args.argi2 = softInputMode; Loading
core/java/com/android/internal/inputmethod/CancellationGroup.java 0 → 100644 +348 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 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 com.android.internal.inputmethod; import android.annotation.AnyThread; import android.annotation.NonNull; import android.annotation.Nullable; import com.android.internal.annotations.GuardedBy; import java.util.ArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; /** * A utility class, which works as both a factory class of completable objects and a cancellation * signal to cancel all the completable objects created by this object. */ public final class CancellationGroup { private final Object mLock = new Object(); /** * List of {@link CountDownLatch}, which can be used to propagate {@link #cancelAll()} to * completable objects. * * <p>This will be lazily instantiated to avoid unnecessary object allocations.</p> */ @Nullable @GuardedBy("mLock") private ArrayList<CountDownLatch> mLatchList = null; @GuardedBy("mLock") private boolean mCanceled = false; /** * An inner class to consolidate completable object types supported by * {@link CancellationGroup}. */ public static final class Completable { /** * Not intended to be instantiated. */ private Completable() { } /** * Base class of all the completable types supported by {@link CancellationGroup}. */ protected static class ValueBase { /** * {@link CountDownLatch} to be signaled to unblock {@link #await(int, TimeUnit)}. */ private final CountDownLatch mLatch = new CountDownLatch(1); /** * {@link CancellationGroup} to which this completable object belongs. */ @NonNull private final CancellationGroup mParentGroup; /** * Lock {@link Object} to guard complete operations within this class. */ protected final Object mValueLock = new Object(); /** * {@code true} after {@link #onComplete()} gets called. */ @GuardedBy("mValueLock") protected boolean mHasValue = false; /** * Base constructor. * * @param parentGroup {@link CancellationGroup} to which this completable object * belongs. */ protected ValueBase(@NonNull CancellationGroup parentGroup) { mParentGroup = parentGroup; } /** * @return {@link true} if {@link #onComplete()} gets called already. */ @AnyThread public boolean hasValue() { synchronized (mValueLock) { return mHasValue; } } /** * Called by subclasses to signale {@link #mLatch}. */ @AnyThread protected void onComplete() { mLatch.countDown(); } /** * Blocks the calling thread until at least one of the following conditions is met. * * <p> * <ol> * <li>This object becomes ready to return the value.</li> * <li>{@link CancellationGroup#cancelAll()} gets called.</li> * <li>The given timeout period has passed.</li> * </ol> * </p> * * <p>The caller can distinguish the case 1 and case 2 by calling {@link #hasValue()}. * Note that the return value of {@link #hasValue()} can change from {@code false} to * {@code true} at any time, even after this methods finishes with returning * {@code true}.</p> * * @param timeout length of the timeout. * @param timeUnit unit of {@code timeout}. * @return {@code false} if and only if the given timeout period has passed. Otherwise * {@code true}. */ @AnyThread public boolean await(int timeout, @NonNull TimeUnit timeUnit) { if (!mParentGroup.registerLatch(mLatch)) { // Already canceled when this method gets called. return false; } try { return mLatch.await(timeout, timeUnit); } catch (InterruptedException e) { return true; } finally { mParentGroup.unregisterLatch(mLatch); } } } /** * Completable object of integer primitive. */ public static final class Int extends ValueBase { @GuardedBy("mValueLock") private int mValue = 0; private Int(@NonNull CancellationGroup factory) { super(factory); } /** * Notify when a value is set to this completable object. * * @param value value to be set. */ @AnyThread void onComplete(int value) { synchronized (mValueLock) { if (mHasValue) { throw new UnsupportedOperationException( "onComplete() cannot be called multiple times"); } mValue = value; mHasValue = true; } onComplete(); } /** * @return value associated with this object. * @throws UnsupportedOperationException when called while {@link #hasValue()} returns * {@code false}. */ @AnyThread public int getValue() { synchronized (mValueLock) { if (!mHasValue) { throw new UnsupportedOperationException( "getValue() is allowed only if hasValue() returns true"); } return mValue; } } } /** * Base class of completable object types. * * @param <T> type associated with this completable object. */ public static class Values<T> extends ValueBase { @GuardedBy("mValueLock") @Nullable private T mValue = null; protected Values(@NonNull CancellationGroup factory) { super(factory); } /** * Notify when a value is set to this completable value object. * * @param value value to be set. */ @AnyThread void onComplete(@Nullable T value) { synchronized (mValueLock) { if (mHasValue) { throw new UnsupportedOperationException( "onComplete() cannot be called multiple times"); } mValue = value; mHasValue = true; } onComplete(); } /** * @return value associated with this object. * @throws UnsupportedOperationException when called while {@link #hasValue()} returns * {@code false}. */ @AnyThread @Nullable public T getValue() { synchronized (mValueLock) { if (!mHasValue) { throw new UnsupportedOperationException( "getValue() is allowed only if hasValue() returns true"); } return mValue; } } } /** * Completable object of {@link java.lang.CharSequence}. */ public static final class CharSequence extends Values<java.lang.CharSequence> { private CharSequence(@NonNull CancellationGroup factory) { super(factory); } } /** * Completable object of {@link android.view.inputmethod.ExtractedText}. */ public static final class ExtractedText extends Values<android.view.inputmethod.ExtractedText> { private ExtractedText(@NonNull CancellationGroup factory) { super(factory); } } } /** * @return an instance of {@link Completable.Int} that is associated with this * {@link CancellationGroup}. */ @AnyThread public Completable.Int createCompletableInt() { return new Completable.Int(this); } /** * @return an instance of {@link Completable.CharSequence} that is associated with this * {@link CancellationGroup}. */ @AnyThread public Completable.CharSequence createCompletableCharSequence() { return new Completable.CharSequence(this); } /** * @return an instance of {@link Completable.ExtractedText} that is associated with this * {@link CancellationGroup}. */ @AnyThread public Completable.ExtractedText createCompletableExtractedText() { return new Completable.ExtractedText(this); } @AnyThread private boolean registerLatch(@NonNull CountDownLatch latch) { synchronized (mLock) { if (mCanceled) { return false; } if (mLatchList == null) { // Set the initial capacity to 1 with an assumption that usually there is up to 1 // on-going operation. mLatchList = new ArrayList<>(1); } mLatchList.add(latch); return true; } } @AnyThread private void unregisterLatch(@NonNull CountDownLatch latch) { synchronized (mLock) { if (mLatchList != null) { mLatchList.remove(latch); } } } /** * Cancel all the completable objects created from this {@link CancellationGroup}. * * <p>Secondary calls will be silently ignored.</p> */ @AnyThread public void cancelAll() { synchronized (mLock) { if (!mCanceled) { mCanceled = true; if (mLatchList != null) { mLatchList.forEach(CountDownLatch::countDown); mLatchList.clear(); mLatchList = null; } } } } /** * @return {@code true} if {@link #cancelAll()} is already called. {@code false} otherwise. */ @AnyThread public boolean isCanceled() { synchronized (mLock) { return mCanceled; } } }
core/java/com/android/internal/inputmethod/ICharSequenceResultCallback.aidl 0 → 100644 +21 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 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 com.android.internal.inputmethod; oneway interface ICharSequenceResultCallback { void onResult(in CharSequence result); }
core/java/com/android/internal/view/IInputContextCallback.aidl→core/java/com/android/internal/inputmethod/IExtractedTextResultCallback.aidl +4 −13 Original line number Diff line number Diff line /* * Copyright (C) 2008 The Android Open Source Project * Copyright (C) 2020 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. Loading @@ -14,19 +14,10 @@ * limitations under the License. */ package com.android.internal.view; package com.android.internal.inputmethod; import android.view.inputmethod.ExtractedText; /** * {@hide} */ oneway interface IInputContextCallback { void setTextBeforeCursor(CharSequence textBeforeCursor, int seq); void setTextAfterCursor(CharSequence textAfterCursor, int seq); void setCursorCapsMode(int capsMode, int seq); void setExtractedText(in ExtractedText extractedText, int seq); void setSelectedText(CharSequence selectedText, int seq); void setRequestUpdateCursorAnchorInfoResult(boolean result, int seq); void setCommitContentResult(boolean result, int seq); oneway interface IExtractedTextResultCallback { void onResult(in ExtractedText result); }