Enable IME predictive back animation in all apps
Bug: 341013064
Test: atest FrameworksCoreTests:WindowOnBackInvokedDispatcherTest
Test: Manual, i.e. testing IME behaviour in test app when using onKeyPreIme.
Change-Id: I698577f0a1de69a0f5db1006e52f669cee2df5a1
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index bdada11..a37c453 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -7344,6 +7344,8 @@
final KeyEvent event = (KeyEvent)q.mEvent;
if (mView.dispatchKeyEventPreIme(event)) {
return FINISH_HANDLED;
+ } else if (q.forPreImeOnly()) {
+ return FINISH_NOT_HANDLED;
}
return FORWARD;
}
@@ -9850,6 +9852,7 @@
public static final int FLAG_RESYNTHESIZED = 1 << 4;
public static final int FLAG_UNHANDLED = 1 << 5;
public static final int FLAG_MODIFIED_FOR_COMPATIBILITY = 1 << 6;
+ public static final int FLAG_PRE_IME_ONLY = 1 << 7;
public QueuedInputEvent mNext;
@@ -9857,6 +9860,13 @@
public InputEventReceiver mReceiver;
public int mFlags;
+ public boolean forPreImeOnly() {
+ if ((mFlags & FLAG_PRE_IME_ONLY) != 0) {
+ return true;
+ }
+ return false;
+ }
+
public boolean shouldSkipIme() {
if ((mFlags & FLAG_DELIVER_POST_IME) != 0) {
return true;
@@ -9883,6 +9893,7 @@
hasPrevious = flagToString("FINISHED_HANDLED", FLAG_FINISHED_HANDLED, hasPrevious, sb);
hasPrevious = flagToString("RESYNTHESIZED", FLAG_RESYNTHESIZED, hasPrevious, sb);
hasPrevious = flagToString("UNHANDLED", FLAG_UNHANDLED, hasPrevious, sb);
+ hasPrevious = flagToString("FLAG_PRE_IME_ONLY", FLAG_PRE_IME_ONLY, hasPrevious, sb);
if (!hasPrevious) {
sb.append("0");
}
@@ -9939,7 +9950,7 @@
}
@UnsupportedAppUsage
- void enqueueInputEvent(InputEvent event,
+ QueuedInputEvent enqueueInputEvent(InputEvent event,
InputEventReceiver receiver, int flags, boolean processImmediately) {
QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
@@ -9978,6 +9989,7 @@
} else {
scheduleProcessInputEvents();
}
+ return q;
}
private void scheduleProcessInputEvents() {
@@ -12274,29 +12286,45 @@
+ "IWindow:%s Session:%s",
mOnBackInvokedDispatcher, mBasePackageName, mWindow, mWindowSession));
}
- mOnBackInvokedDispatcher.attachToWindow(mWindowSession, mWindow,
+ mOnBackInvokedDispatcher.attachToWindow(mWindowSession, mWindow, this,
mImeBackAnimationController);
}
- private void sendBackKeyEvent(int action) {
+ /**
+ * Sends {@link KeyEvent#ACTION_DOWN ACTION_DOWN} and {@link KeyEvent#ACTION_UP ACTION_UP}
+ * back key events
+ *
+ * @param preImeOnly whether the back events should be sent to the pre-ime stage only
+ * @return whether the event was handled (i.e. onKeyPreIme consumed it if preImeOnly=true)
+ */
+ public boolean injectBackKeyEvents(boolean preImeOnly) {
+ boolean consumed;
+ try {
+ processingBackKey(true);
+ sendBackKeyEvent(KeyEvent.ACTION_DOWN, preImeOnly);
+ consumed = sendBackKeyEvent(KeyEvent.ACTION_UP, preImeOnly);
+ } finally {
+ processingBackKey(false);
+ }
+ return consumed;
+ }
+
+ private boolean sendBackKeyEvent(int action, boolean preImeOnly) {
long when = SystemClock.uptimeMillis();
final KeyEvent ev = 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,
InputDevice.SOURCE_KEYBOARD);
- enqueueInputEvent(ev, null /* receiver */, 0 /* flags */, true /* processImmediately */);
+ int flags = preImeOnly ? QueuedInputEvent.FLAG_PRE_IME_ONLY : 0;
+ QueuedInputEvent q = enqueueInputEvent(ev, null /* receiver */, flags,
+ true /* processImmediately */);
+ return (q.mFlags & QueuedInputEvent.FLAG_FINISHED_HANDLED) != 0;
}
private void registerCompatOnBackInvokedCallback() {
mCompatOnBackInvokedCallback = () -> {
- try {
- processingBackKey(true);
- sendBackKeyEvent(KeyEvent.ACTION_DOWN);
- sendBackKeyEvent(KeyEvent.ACTION_UP);
- } finally {
- processingBackKey(false);
- }
+ injectBackKeyEvents(/* preImeOnly */ false);
};
if (mOnBackInvokedDispatcher.hasImeOnBackInvokedDispatcher()) {
Log.d(TAG, "Skip registering CompatOnBackInvokedCallback on IME dispatcher");
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 0ff52f1..5ef7f70 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -37,6 +37,7 @@
import android.view.IWindowSession;
import android.view.ImeBackAnimationController;
import android.view.MotionEvent;
+import android.view.ViewRootImpl;
import androidx.annotation.VisibleForTesting;
@@ -49,6 +50,7 @@
import java.util.HashMap;
import java.util.Objects;
import java.util.TreeMap;
+import java.util.function.BooleanSupplier;
import java.util.function.Supplier;
/**
@@ -68,6 +70,7 @@
public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
private IWindowSession mWindowSession;
private IWindow mWindow;
+ private ViewRootImpl mViewRoot;
@VisibleForTesting
public final BackTouchTracker mTouchTracker = new BackTouchTracker();
@VisibleForTesting
@@ -134,10 +137,12 @@
* is attached a window.
*/
public void attachToWindow(@NonNull IWindowSession windowSession, @NonNull IWindow window,
+ @Nullable ViewRootImpl viewRoot,
@Nullable ImeBackAnimationController imeBackAnimationController) {
synchronized (mLock) {
mWindowSession = windowSession;
mWindow = window;
+ mViewRoot = viewRoot;
mImeBackAnimationController = imeBackAnimationController;
if (!mAllCallbacks.isEmpty()) {
setTopOnBackInvokedCallback(getTopCallback());
@@ -151,6 +156,7 @@
clear();
mWindow = null;
mWindowSession = null;
+ mViewRoot = null;
mImeBackAnimationController = null;
}
}
@@ -176,8 +182,6 @@
return;
}
if (callback instanceof ImeOnBackInvokedDispatcher.ImeOnBackInvokedCallback) {
- // Fall back to compat back key injection if legacy back behaviour should be used.
- if (!isOnBackInvokedCallbackEnabled()) return;
if (callback instanceof ImeOnBackInvokedDispatcher.DefaultImeOnBackAnimationCallback
&& mImeBackAnimationController != null) {
// register ImeBackAnimationController instead to play predictive back animation
@@ -300,6 +304,14 @@
}
}
+ private boolean callOnKeyPreIme() {
+ if (mViewRoot != null && !isOnBackInvokedCallbackEnabled(mViewRoot.mContext)) {
+ return mViewRoot.injectBackKeyEvents(/*preImeOnly*/ true);
+ } else {
+ return false;
+ }
+ }
+
private void setTopOnBackInvokedCallback(@Nullable OnBackInvokedCallback callback) {
if (mWindowSession == null || mWindow == null) {
return;
@@ -308,8 +320,8 @@
OnBackInvokedCallbackInfo callbackInfo = null;
if (callback != null) {
int priority = mAllCallbacks.get(callback);
- final IOnBackInvokedCallback iCallback = new OnBackInvokedCallbackWrapper(
- callback, mTouchTracker, mProgressAnimator, mHandler);
+ final IOnBackInvokedCallback iCallback = new OnBackInvokedCallbackWrapper(callback,
+ mTouchTracker, mProgressAnimator, mHandler, this::callOnKeyPreIme);
callbackInfo = new OnBackInvokedCallbackInfo(
iCallback,
priority,
@@ -399,16 +411,20 @@
private final BackTouchTracker mTouchTracker;
@NonNull
private final Handler mHandler;
+ @NonNull
+ private final BooleanSupplier mOnKeyPreIme;
OnBackInvokedCallbackWrapper(
@NonNull OnBackInvokedCallback callback,
@NonNull BackTouchTracker touchTracker,
@NonNull BackProgressAnimator progressAnimator,
- @NonNull Handler handler) {
+ @NonNull Handler handler,
+ @NonNull BooleanSupplier onKeyPreIme) {
mCallback = new WeakReference<>(callback);
mTouchTracker = touchTracker;
mProgressAnimator = progressAnimator;
mHandler = handler;
+ mOnKeyPreIme = onKeyPreIme;
}
@Override
@@ -451,6 +467,7 @@
public void onBackInvoked() throws RemoteException {
mHandler.post(() -> {
mTouchTracker.reset();
+ if (consumedByOnKeyPreIme()) return;
boolean isInProgress = mProgressAnimator.isBackAnimationInProgress();
mProgressAnimator.reset();
// TODO(b/333957271): Re-introduce auto fling progress generation.
@@ -467,6 +484,30 @@
});
}
+ private boolean consumedByOnKeyPreIme() {
+ final OnBackInvokedCallback callback = mCallback.get();
+ if (callback instanceof ImeBackAnimationController
+ || callback instanceof ImeOnBackInvokedDispatcher.ImeOnBackInvokedCallback) {
+ // call onKeyPreIme API if the current callback is an IME callback and the app has
+ // not set enableOnBackInvokedCallback="false"
+ try {
+ boolean consumed = mOnKeyPreIme.getAsBoolean();
+ if (consumed) {
+ // back event intercepted by app in onKeyPreIme -> cancel the IME animation.
+ final OnBackAnimationCallback animationCallback =
+ getBackAnimationCallback();
+ if (animationCallback != null) {
+ mProgressAnimator.onBackCancelled(animationCallback::onBackCancelled);
+ }
+ return true;
+ }
+ } catch (Exception e) {
+ Log.d(TAG, "Failed to call onKeyPreIme", e);
+ }
+ }
+ return false;
+ }
+
@Override
public void setTriggerBack(boolean triggerBack) throws RemoteException {
mTouchTracker.setTriggerBack(triggerBack);
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index 50d7f59..1aada40 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -111,7 +111,7 @@
doReturn(mApplicationInfo).when(mContext).getApplicationInfo();
mDispatcher = new WindowOnBackInvokedDispatcher(mContext, Looper.getMainLooper());
- mDispatcher.attachToWindow(mWindowSession, mWindow, mImeBackAnimationController);
+ mDispatcher.attachToWindow(mWindowSession, mWindow, null, mImeBackAnimationController);
}
private void waitForIdle() {
@@ -454,25 +454,26 @@
@Test
public void registerImeCallbacks_onBackInvokedCallbackEnabled() throws RemoteException {
- mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mDefaultImeCallback);
+ verifyImeCallackRegistrations();
+ }
+
+ @Test
+ public void registerImeCallbacks_onBackInvokedCallbackDisabled() throws RemoteException {
+ doReturn(false).when(mApplicationInfo).isOnBackInvokedCallbackEnabled();
+ verifyImeCallackRegistrations();
+ }
+
+ private void verifyImeCallackRegistrations() throws RemoteException {
+ // verify default callback is replaced with ImeBackAnimationController
+ mDispatcher.registerOnBackInvokedCallbackUnchecked(mDefaultImeCallback, PRIORITY_DEFAULT);
assertCallbacksSize(/* default */ 1, /* overlay */ 0);
assertSetCallbackInfo();
assertTopCallback(mImeBackAnimationController);
- mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mImeCallback);
+ // verify regular ime callback is successfully registered
+ mDispatcher.registerOnBackInvokedCallbackUnchecked(mImeCallback, PRIORITY_DEFAULT);
assertCallbacksSize(/* default */ 2, /* overlay */ 0);
assertSetCallbackInfo();
assertTopCallback(mImeCallback);
}
-
- @Test
- public void registerImeCallbacks_legacyBack() throws RemoteException {
- doReturn(false).when(mApplicationInfo).isOnBackInvokedCallbackEnabled();
-
- mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mDefaultImeCallback);
- assertNoSetCallbackInfo();
-
- mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mImeCallback);
- assertNoSetCallbackInfo();
- }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index a39a1a8..c67d1ec 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -550,7 +550,7 @@
}).when(appWindow.mSession).setOnBackInvokedCallbackInfo(eq(appWindow.mClient), any());
addToWindowMap(appWindow, true);
- dispatcher.attachToWindow(appWindow.mSession, appWindow.mClient, null);
+ dispatcher.attachToWindow(appWindow.mSession, appWindow.mClient, null, null);
OnBackInvokedCallback appCallback = createBackCallback(appLatch);