Loading core/api/test-current.txt +1 −0 Original line number Diff line number Diff line Loading @@ -800,6 +800,7 @@ package android.content.pm { method public boolean hasRequestForegroundServiceExemption(); method public boolean isPrivilegedApp(); method public boolean isSystemApp(); method public void setEnableOnBackInvokedCallback(boolean); field public static final int PRIVATE_FLAG_PRIVILEGED = 8; // 0x8 field public int privateFlags; } Loading core/java/android/content/pm/ApplicationInfo.java +17 −0 Original line number Diff line number Diff line Loading @@ -2752,4 +2752,21 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { mKnownActivityEmbeddingCerts.add(knownCert.toUpperCase(Locale.US)); } } /** * Sets whether the application will use the {@link android.window.OnBackInvokedCallback} * navigation system instead of the {@link android.view.KeyEvent#KEYCODE_BACK} and related * callbacks. Intended to be used from tests only. * * @see #isOnBackInvokedCallbackEnabled() * @hide */ @TestApi public void setEnableOnBackInvokedCallback(boolean isEnable) { if (isEnable) { privateFlagsExt |= PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK; } else { privateFlagsExt &= ~PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK; } } } core/java/android/inputmethodservice/IInputMethodWrapper.java +11 −4 Original line number Diff line number Diff line Loading @@ -37,6 +37,7 @@ import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethod; import android.view.inputmethod.InputMethodSession; import android.view.inputmethod.InputMethodSubtype; import android.window.ImeOnBackInvokedDispatcher; import com.android.internal.inputmethod.CancellationGroup; import com.android.internal.inputmethod.IInputMethodPrivilegedOperations; Loading Loading @@ -194,7 +195,9 @@ class IInputMethodWrapper extends IInputMethod.Stub case DO_START_INPUT: { final SomeArgs args = (SomeArgs) msg.obj; final IBinder startInputToken = (IBinder) args.arg1; final IInputContext inputContext = (IInputContext) args.arg2; final IInputContext inputContext = (IInputContext) ((SomeArgs) args.arg2).arg1; final ImeOnBackInvokedDispatcher imeDispatcher = (ImeOnBackInvokedDispatcher) ((SomeArgs) args.arg2).arg2; final EditorInfo info = (EditorInfo) args.arg3; final CancellationGroup cancellationGroup = (CancellationGroup) args.arg4; final boolean restarting = args.argi5 == 1; Loading @@ -205,7 +208,7 @@ class IInputMethodWrapper extends IInputMethod.Stub : null; info.makeCompatible(mTargetSdkVersion); inputMethod.dispatchStartInputWithToken(ic, info, restarting, startInputToken, navButtonFlags); navButtonFlags, imeDispatcher); args.recycle(); return; } Loading Loading @@ -348,13 +351,17 @@ class IInputMethodWrapper extends IInputMethod.Stub @Override public void startInput(IBinder startInputToken, IInputContext inputContext, EditorInfo attribute, boolean restarting, @InputMethodNavButtonFlags int navButtonFlags) { @InputMethodNavButtonFlags int navButtonFlags, @NonNull ImeOnBackInvokedDispatcher imeDispatcher) { if (mCancellationGroup == null) { Log.e(TAG, "startInput must be called after bindInput."); mCancellationGroup = new CancellationGroup(); } SomeArgs args = SomeArgs.obtain(); args.arg1 = inputContext; args.arg2 = imeDispatcher; mCaller.executeOrSendMessage(mCaller.obtainMessageOOOOII(DO_START_INPUT, startInputToken, inputContext, attribute, mCancellationGroup, restarting ? 1 : 0, navButtonFlags)); args, attribute, mCancellationGroup, restarting ? 1 : 0, navButtonFlags)); } @BinderThread Loading core/java/android/inputmethodservice/InputMethodService.java +69 −1 Original line number Diff line number Diff line Loading @@ -101,6 +101,7 @@ import android.view.BatchedInputEventReceiver.SimpleBatchedInputEventReceiver; import android.view.Choreographer; import android.view.Gravity; import android.view.InputChannel; import android.view.InputDevice; import android.view.InputEventReceiver; import android.view.KeyCharacterMap; import android.view.KeyEvent; Loading Loading @@ -134,6 +135,9 @@ import android.widget.FrameLayout; import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.TextView; import android.window.ImeOnBackInvokedDispatcher; import android.window.OnBackInvokedCallback; import android.window.OnBackInvokedDispatcher; import android.window.WindowMetricsHelper; import com.android.internal.annotations.GuardedBy; Loading Loading @@ -344,6 +348,9 @@ public class InputMethodService extends AbstractInputMethodService { * A circular buffer of size MAX_EVENTS_BUFFER in case IME is taking too long to add ink view. **/ private RingBuffer<MotionEvent> mPendingEvents; private ImeOnBackInvokedDispatcher mImeDispatcher; private Boolean mBackCallbackRegistered = false; private final OnBackInvokedCallback mCompatBackCallback = this::compatHandleBack; /** * Returns whether {@link InputMethodService} is responsible for rendering the back button and Loading Loading @@ -797,7 +804,13 @@ public class InputMethodService extends AbstractInputMethodService { @Override public final void dispatchStartInputWithToken(@Nullable InputConnection inputConnection, @NonNull EditorInfo editorInfo, boolean restarting, @NonNull IBinder startInputToken, @InputMethodNavButtonFlags int navButtonFlags) { @NonNull IBinder startInputToken, @InputMethodNavButtonFlags int navButtonFlags, @NonNull ImeOnBackInvokedDispatcher imeDispatcher) { mImeDispatcher = imeDispatcher; if (mWindow != null) { mWindow.getOnBackInvokedDispatcher().setImeOnBackInvokedDispatcher( imeDispatcher); } mPrivOps.reportStartInputAsync(startInputToken); mNavigationBarController.onNavButtonFlagsChanged(navButtonFlags); if (restarting) { Loading Loading @@ -1496,6 +1509,10 @@ public class InputMethodService extends AbstractInputMethodService { Context.LAYOUT_INFLATER_SERVICE); Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.initSoftInputWindow"); mWindow = new SoftInputWindow(this, mTheme, mDispatcherState); if (mImeDispatcher != null) { mWindow.getOnBackInvokedDispatcher() .setImeOnBackInvokedDispatcher(mImeDispatcher); } mNavigationBarController.onSoftInputWindowCreated(mWindow); { final Window window = mWindow.getWindow(); Loading Loading @@ -1608,6 +1625,8 @@ public class InputMethodService extends AbstractInputMethodService { // when IME developers are doing something unsupported. InputMethodPrivilegedOperationsRegistry.remove(mToken); } unregisterCompatOnBackInvokedCallback(); mImeDispatcher = null; } /** Loading Loading @@ -2568,9 +2587,47 @@ public class InputMethodService extends AbstractInputMethodService { cancelImeSurfaceRemoval(); mInShowWindow = false; Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); registerCompatOnBackInvokedCallback(); } /** * Registers an {@link OnBackInvokedCallback} to handle back invocation when ahead-of-time * back dispatching is enabled. We keep the {@link KeyEvent#KEYCODE_BACK} based legacy code * around to handle back on older devices. */ private void registerCompatOnBackInvokedCallback() { if (mBackCallbackRegistered) { return; } if (mWindow != null) { mWindow.getOnBackInvokedDispatcher().registerOnBackInvokedCallback( OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCompatBackCallback); mBackCallbackRegistered = true; } } private void unregisterCompatOnBackInvokedCallback() { if (!mBackCallbackRegistered) { return; } if (mWindow != null) { mWindow.getOnBackInvokedDispatcher() .unregisterOnBackInvokedCallback(mCompatBackCallback); mBackCallbackRegistered = false; } } private KeyEvent createBackKeyEvent(int action, boolean isTracking) { final long when = SystemClock.uptimeMillis(); return new KeyEvent(when, when, action, KeyEvent.KEYCODE_BACK, 0 /* repeat */, 0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */, KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY | (isTracking ? KeyEvent.FLAG_TRACKING : 0), InputDevice.SOURCE_KEYBOARD); } private boolean prepareWindow(boolean showInput) { boolean doShowInput = false; mDecorViewVisible = true; Loading Loading @@ -2658,6 +2715,7 @@ public class InputMethodService extends AbstractInputMethodService { } mLastWasInFullscreenMode = mIsFullscreen; updateFullscreenMode(); unregisterCompatOnBackInvokedCallback(); } /** Loading Loading @@ -3797,4 +3855,14 @@ public class InputMethodService extends AbstractInputMethodService { proto.end(token); } }; private void compatHandleBack() { final KeyEvent downEvent = createBackKeyEvent( KeyEvent.ACTION_DOWN, false /* isTracking */); onKeyDown(KeyEvent.KEYCODE_BACK, downEvent); final boolean hasStartedTracking = (downEvent.getFlags() & KeyEvent.FLAG_START_TRACKING) != 0; final KeyEvent upEvent = createBackKeyEvent(KeyEvent.ACTION_UP, hasStartedTracking); onKeyUp(KeyEvent.KEYCODE_BACK, upEvent); } } core/java/android/view/ViewRootImpl.java +22 −18 Original line number Diff line number Diff line Loading @@ -6095,6 +6095,28 @@ public final class ViewRootImpl implements ViewParent, @Override protected int onProcess(QueuedInputEvent q) { if (q.mEvent instanceof KeyEvent) { final KeyEvent event = (KeyEvent) q.mEvent; // If the new back dispatch is enabled, intercept KEYCODE_BACK before it reaches the // view tree or IME, and invoke the appropriate {@link OnBackInvokedCallback}. if (isBack(event) && mContext != null && WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(mContext)) { OnBackInvokedCallback topCallback = getOnBackInvokedDispatcher().getTopCallback(); if (event.getAction() == KeyEvent.ACTION_UP) { if (topCallback != null) { topCallback.onBackInvoked(); return FINISH_HANDLED; } } else { // Drop other actions such as {@link KeyEvent.ACTION_DOWN}. return FINISH_NOT_HANDLED; } } } if (mInputQueue != null && q.mEvent instanceof KeyEvent) { mInputQueue.sendInputEvent(q.mEvent, q, true, this); return DEFER; Loading Loading @@ -6446,24 +6468,6 @@ public final class ViewRootImpl implements ViewParent, return FINISH_HANDLED; } // If the new back dispatch is enabled, intercept KEYCODE_BACK before it reaches the // view tree and invoke the appropriate {@link OnBackInvokedCallback}. if (isBack(event) && mContext != null && WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(mContext)) { OnBackInvokedCallback topCallback = getOnBackInvokedDispatcher().getTopCallback(); if (event.getAction() == KeyEvent.ACTION_UP) { if (topCallback != null) { topCallback.onBackInvoked(); return FINISH_HANDLED; } } else { // Drop other actions such as {@link KeyEvent.ACTION_DOWN}. return FINISH_NOT_HANDLED; } } // Deliver the key to the view hierarchy. if (mView.dispatchKeyEvent(event)) { return FINISH_HANDLED; Loading Loading
core/api/test-current.txt +1 −0 Original line number Diff line number Diff line Loading @@ -800,6 +800,7 @@ package android.content.pm { method public boolean hasRequestForegroundServiceExemption(); method public boolean isPrivilegedApp(); method public boolean isSystemApp(); method public void setEnableOnBackInvokedCallback(boolean); field public static final int PRIVATE_FLAG_PRIVILEGED = 8; // 0x8 field public int privateFlags; } Loading
core/java/android/content/pm/ApplicationInfo.java +17 −0 Original line number Diff line number Diff line Loading @@ -2752,4 +2752,21 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { mKnownActivityEmbeddingCerts.add(knownCert.toUpperCase(Locale.US)); } } /** * Sets whether the application will use the {@link android.window.OnBackInvokedCallback} * navigation system instead of the {@link android.view.KeyEvent#KEYCODE_BACK} and related * callbacks. Intended to be used from tests only. * * @see #isOnBackInvokedCallbackEnabled() * @hide */ @TestApi public void setEnableOnBackInvokedCallback(boolean isEnable) { if (isEnable) { privateFlagsExt |= PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK; } else { privateFlagsExt &= ~PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK; } } }
core/java/android/inputmethodservice/IInputMethodWrapper.java +11 −4 Original line number Diff line number Diff line Loading @@ -37,6 +37,7 @@ import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethod; import android.view.inputmethod.InputMethodSession; import android.view.inputmethod.InputMethodSubtype; import android.window.ImeOnBackInvokedDispatcher; import com.android.internal.inputmethod.CancellationGroup; import com.android.internal.inputmethod.IInputMethodPrivilegedOperations; Loading Loading @@ -194,7 +195,9 @@ class IInputMethodWrapper extends IInputMethod.Stub case DO_START_INPUT: { final SomeArgs args = (SomeArgs) msg.obj; final IBinder startInputToken = (IBinder) args.arg1; final IInputContext inputContext = (IInputContext) args.arg2; final IInputContext inputContext = (IInputContext) ((SomeArgs) args.arg2).arg1; final ImeOnBackInvokedDispatcher imeDispatcher = (ImeOnBackInvokedDispatcher) ((SomeArgs) args.arg2).arg2; final EditorInfo info = (EditorInfo) args.arg3; final CancellationGroup cancellationGroup = (CancellationGroup) args.arg4; final boolean restarting = args.argi5 == 1; Loading @@ -205,7 +208,7 @@ class IInputMethodWrapper extends IInputMethod.Stub : null; info.makeCompatible(mTargetSdkVersion); inputMethod.dispatchStartInputWithToken(ic, info, restarting, startInputToken, navButtonFlags); navButtonFlags, imeDispatcher); args.recycle(); return; } Loading Loading @@ -348,13 +351,17 @@ class IInputMethodWrapper extends IInputMethod.Stub @Override public void startInput(IBinder startInputToken, IInputContext inputContext, EditorInfo attribute, boolean restarting, @InputMethodNavButtonFlags int navButtonFlags) { @InputMethodNavButtonFlags int navButtonFlags, @NonNull ImeOnBackInvokedDispatcher imeDispatcher) { if (mCancellationGroup == null) { Log.e(TAG, "startInput must be called after bindInput."); mCancellationGroup = new CancellationGroup(); } SomeArgs args = SomeArgs.obtain(); args.arg1 = inputContext; args.arg2 = imeDispatcher; mCaller.executeOrSendMessage(mCaller.obtainMessageOOOOII(DO_START_INPUT, startInputToken, inputContext, attribute, mCancellationGroup, restarting ? 1 : 0, navButtonFlags)); args, attribute, mCancellationGroup, restarting ? 1 : 0, navButtonFlags)); } @BinderThread Loading
core/java/android/inputmethodservice/InputMethodService.java +69 −1 Original line number Diff line number Diff line Loading @@ -101,6 +101,7 @@ import android.view.BatchedInputEventReceiver.SimpleBatchedInputEventReceiver; import android.view.Choreographer; import android.view.Gravity; import android.view.InputChannel; import android.view.InputDevice; import android.view.InputEventReceiver; import android.view.KeyCharacterMap; import android.view.KeyEvent; Loading Loading @@ -134,6 +135,9 @@ import android.widget.FrameLayout; import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.TextView; import android.window.ImeOnBackInvokedDispatcher; import android.window.OnBackInvokedCallback; import android.window.OnBackInvokedDispatcher; import android.window.WindowMetricsHelper; import com.android.internal.annotations.GuardedBy; Loading Loading @@ -344,6 +348,9 @@ public class InputMethodService extends AbstractInputMethodService { * A circular buffer of size MAX_EVENTS_BUFFER in case IME is taking too long to add ink view. **/ private RingBuffer<MotionEvent> mPendingEvents; private ImeOnBackInvokedDispatcher mImeDispatcher; private Boolean mBackCallbackRegistered = false; private final OnBackInvokedCallback mCompatBackCallback = this::compatHandleBack; /** * Returns whether {@link InputMethodService} is responsible for rendering the back button and Loading Loading @@ -797,7 +804,13 @@ public class InputMethodService extends AbstractInputMethodService { @Override public final void dispatchStartInputWithToken(@Nullable InputConnection inputConnection, @NonNull EditorInfo editorInfo, boolean restarting, @NonNull IBinder startInputToken, @InputMethodNavButtonFlags int navButtonFlags) { @NonNull IBinder startInputToken, @InputMethodNavButtonFlags int navButtonFlags, @NonNull ImeOnBackInvokedDispatcher imeDispatcher) { mImeDispatcher = imeDispatcher; if (mWindow != null) { mWindow.getOnBackInvokedDispatcher().setImeOnBackInvokedDispatcher( imeDispatcher); } mPrivOps.reportStartInputAsync(startInputToken); mNavigationBarController.onNavButtonFlagsChanged(navButtonFlags); if (restarting) { Loading Loading @@ -1496,6 +1509,10 @@ public class InputMethodService extends AbstractInputMethodService { Context.LAYOUT_INFLATER_SERVICE); Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.initSoftInputWindow"); mWindow = new SoftInputWindow(this, mTheme, mDispatcherState); if (mImeDispatcher != null) { mWindow.getOnBackInvokedDispatcher() .setImeOnBackInvokedDispatcher(mImeDispatcher); } mNavigationBarController.onSoftInputWindowCreated(mWindow); { final Window window = mWindow.getWindow(); Loading Loading @@ -1608,6 +1625,8 @@ public class InputMethodService extends AbstractInputMethodService { // when IME developers are doing something unsupported. InputMethodPrivilegedOperationsRegistry.remove(mToken); } unregisterCompatOnBackInvokedCallback(); mImeDispatcher = null; } /** Loading Loading @@ -2568,9 +2587,47 @@ public class InputMethodService extends AbstractInputMethodService { cancelImeSurfaceRemoval(); mInShowWindow = false; Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); registerCompatOnBackInvokedCallback(); } /** * Registers an {@link OnBackInvokedCallback} to handle back invocation when ahead-of-time * back dispatching is enabled. We keep the {@link KeyEvent#KEYCODE_BACK} based legacy code * around to handle back on older devices. */ private void registerCompatOnBackInvokedCallback() { if (mBackCallbackRegistered) { return; } if (mWindow != null) { mWindow.getOnBackInvokedDispatcher().registerOnBackInvokedCallback( OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCompatBackCallback); mBackCallbackRegistered = true; } } private void unregisterCompatOnBackInvokedCallback() { if (!mBackCallbackRegistered) { return; } if (mWindow != null) { mWindow.getOnBackInvokedDispatcher() .unregisterOnBackInvokedCallback(mCompatBackCallback); mBackCallbackRegistered = false; } } private KeyEvent createBackKeyEvent(int action, boolean isTracking) { final long when = SystemClock.uptimeMillis(); return new KeyEvent(when, when, action, KeyEvent.KEYCODE_BACK, 0 /* repeat */, 0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */, KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY | (isTracking ? KeyEvent.FLAG_TRACKING : 0), InputDevice.SOURCE_KEYBOARD); } private boolean prepareWindow(boolean showInput) { boolean doShowInput = false; mDecorViewVisible = true; Loading Loading @@ -2658,6 +2715,7 @@ public class InputMethodService extends AbstractInputMethodService { } mLastWasInFullscreenMode = mIsFullscreen; updateFullscreenMode(); unregisterCompatOnBackInvokedCallback(); } /** Loading Loading @@ -3797,4 +3855,14 @@ public class InputMethodService extends AbstractInputMethodService { proto.end(token); } }; private void compatHandleBack() { final KeyEvent downEvent = createBackKeyEvent( KeyEvent.ACTION_DOWN, false /* isTracking */); onKeyDown(KeyEvent.KEYCODE_BACK, downEvent); final boolean hasStartedTracking = (downEvent.getFlags() & KeyEvent.FLAG_START_TRACKING) != 0; final KeyEvent upEvent = createBackKeyEvent(KeyEvent.ACTION_UP, hasStartedTracking); onKeyUp(KeyEvent.KEYCODE_BACK, upEvent); } }
core/java/android/view/ViewRootImpl.java +22 −18 Original line number Diff line number Diff line Loading @@ -6095,6 +6095,28 @@ public final class ViewRootImpl implements ViewParent, @Override protected int onProcess(QueuedInputEvent q) { if (q.mEvent instanceof KeyEvent) { final KeyEvent event = (KeyEvent) q.mEvent; // If the new back dispatch is enabled, intercept KEYCODE_BACK before it reaches the // view tree or IME, and invoke the appropriate {@link OnBackInvokedCallback}. if (isBack(event) && mContext != null && WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(mContext)) { OnBackInvokedCallback topCallback = getOnBackInvokedDispatcher().getTopCallback(); if (event.getAction() == KeyEvent.ACTION_UP) { if (topCallback != null) { topCallback.onBackInvoked(); return FINISH_HANDLED; } } else { // Drop other actions such as {@link KeyEvent.ACTION_DOWN}. return FINISH_NOT_HANDLED; } } } if (mInputQueue != null && q.mEvent instanceof KeyEvent) { mInputQueue.sendInputEvent(q.mEvent, q, true, this); return DEFER; Loading Loading @@ -6446,24 +6468,6 @@ public final class ViewRootImpl implements ViewParent, return FINISH_HANDLED; } // If the new back dispatch is enabled, intercept KEYCODE_BACK before it reaches the // view tree and invoke the appropriate {@link OnBackInvokedCallback}. if (isBack(event) && mContext != null && WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(mContext)) { OnBackInvokedCallback topCallback = getOnBackInvokedDispatcher().getTopCallback(); if (event.getAction() == KeyEvent.ACTION_UP) { if (topCallback != null) { topCallback.onBackInvoked(); return FINISH_HANDLED; } } else { // Drop other actions such as {@link KeyEvent.ACTION_DOWN}. return FINISH_NOT_HANDLED; } } // Deliver the key to the view hierarchy. if (mView.dispatchKeyEvent(event)) { return FINISH_HANDLED; Loading