Invoke callbacks based on back navigation type.

Test: m -j.
Test: Open and swipe back on pre-T and T apps.
Test: atest .../BackNavigationControllerTests.java
Test: atest .../BackAnimationControllerTest.java
Bug: b/195946584
Change-Id: I00cf7ab5b57760d57d30ac74dc5b3443a4203f38
This commit is contained in:
Shan Huang
2022-02-01 06:10:23 +00:00
parent d9b337e921
commit 642b0d8cdd
15 changed files with 364 additions and 105 deletions

View File

@@ -337,8 +337,10 @@ interface IWindowSession {
*
* @param window The token for the window to set the callback to.
* @param callback The {@link IOnBackInvokedCallback} to set.
* @param priority The priority of the callback.
*/
oneway void setOnBackInvokedCallback(IWindow window, IOnBackInvokedCallback callback);
oneway void setOnBackInvokedCallback(
IWindow window, IOnBackInvokedCallback callback, int priority);
/**
* Clears a touchable region set by {@link #setInsets}.

View File

@@ -27,8 +27,6 @@ import android.os.RemoteCallback;
import android.os.RemoteException;
import android.util.Log;
import android.util.MergedConfiguration;
import android.view.InsetsState;
import android.view.IWindow;
import android.window.ClientWindowFrames;
import android.window.IOnBackInvokedCallback;
@@ -511,7 +509,7 @@ public class WindowlessWindowManager implements IWindowSession {
@Override
public void setOnBackInvokedCallback(IWindow iWindow,
IOnBackInvokedCallback iOnBackInvokedCallback) throws RemoteException { }
IOnBackInvokedCallback iOnBackInvokedCallback, int priority) throws RemoteException { }
@Override
public boolean dropForAccessibility(IWindow window, int x, int y) {

View File

@@ -21,9 +21,11 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.WindowConfiguration;
import android.hardware.HardwareBuffer;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteCallback;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
/**
@@ -64,6 +66,12 @@ public final class BackNavigationInfo implements Parcelable {
*/
public static final int TYPE_CALLBACK = 4;
/**
* Key to access the boolean value passed in {#mOnBackNavigationDone} result bundle
* that represents if back navigation has been triggered.
*/
public static final String KEY_TRIGGER_BACK = "TriggerBack";
/**
* Defines the type of back destinations a back even can lead to. This is used to define the
* type of animation that need to be run on SystemUI.
@@ -79,13 +87,13 @@ public final class BackNavigationInfo implements Parcelable {
private final int mType;
@Nullable
private final SurfaceControl mDepartingWindowContainer;
private final RemoteAnimationTarget mDepartingAnimationTarget;
@Nullable
private final SurfaceControl mScreenshotSurface;
@Nullable
private final HardwareBuffer mScreenshotBuffer;
@Nullable
private final RemoteCallback mRemoteCallback;
private final RemoteCallback mOnBackNavigationDone;
@Nullable
private final WindowConfiguration mTaskWindowConfiguration;
@Nullable
@@ -96,8 +104,9 @@ public final class BackNavigationInfo implements Parcelable {
*
* @param type The {@link BackTargetType} of the destination (what will be
* displayed after the back action).
* @param topWindowLeash The leash to animate away the current topWindow. The consumer
* of the leash is responsible for removing it.
* @param departingAnimationTarget The remote animation target, containing a leash to animate
* away the departing window. The consumer of the leash is
* responsible for removing it.
* @param screenshotSurface The screenshot of the previous activity to be displayed.
* @param screenshotBuffer A buffer containing a screenshot used to display the activity.
* See {@link #getScreenshotHardwareBuffer()} for information
@@ -108,39 +117,39 @@ public final class BackNavigationInfo implements Parcelable {
* @param onBackInvokedCallback The back callback registered by the current top level window.
*/
public BackNavigationInfo(@BackTargetType int type,
@Nullable SurfaceControl topWindowLeash,
@Nullable RemoteAnimationTarget departingAnimationTarget,
@Nullable SurfaceControl screenshotSurface,
@Nullable HardwareBuffer screenshotBuffer,
@Nullable WindowConfiguration taskWindowConfiguration,
@Nullable RemoteCallback onBackNavigationDone,
@Nullable IOnBackInvokedCallback onBackInvokedCallback) {
@NonNull RemoteCallback onBackNavigationDone,
@NonNull IOnBackInvokedCallback onBackInvokedCallback) {
mType = type;
mDepartingWindowContainer = topWindowLeash;
mDepartingAnimationTarget = departingAnimationTarget;
mScreenshotSurface = screenshotSurface;
mScreenshotBuffer = screenshotBuffer;
mTaskWindowConfiguration = taskWindowConfiguration;
mRemoteCallback = onBackNavigationDone;
mOnBackNavigationDone = onBackNavigationDone;
mOnBackInvokedCallback = onBackInvokedCallback;
}
private BackNavigationInfo(@NonNull Parcel in) {
mType = in.readInt();
mDepartingWindowContainer = in.readTypedObject(SurfaceControl.CREATOR);
mDepartingAnimationTarget = in.readTypedObject(RemoteAnimationTarget.CREATOR);
mScreenshotSurface = in.readTypedObject(SurfaceControl.CREATOR);
mScreenshotBuffer = in.readTypedObject(HardwareBuffer.CREATOR);
mTaskWindowConfiguration = in.readTypedObject(WindowConfiguration.CREATOR);
mRemoteCallback = in.readTypedObject(RemoteCallback.CREATOR);
mOnBackNavigationDone = in.readTypedObject(RemoteCallback.CREATOR);
mOnBackInvokedCallback = IOnBackInvokedCallback.Stub.asInterface(in.readStrongBinder());
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mType);
dest.writeTypedObject(mDepartingWindowContainer, flags);
dest.writeTypedObject(mDepartingAnimationTarget, flags);
dest.writeTypedObject(mScreenshotSurface, flags);
dest.writeTypedObject(mScreenshotBuffer, flags);
dest.writeTypedObject(mTaskWindowConfiguration, flags);
dest.writeTypedObject(mRemoteCallback, flags);
dest.writeTypedObject(mOnBackNavigationDone, flags);
dest.writeStrongInterface(mOnBackInvokedCallback);
}
@@ -154,12 +163,13 @@ public final class BackNavigationInfo implements Parcelable {
}
/**
* Returns a leash to the top window container that needs to be animated. This can be null if
* the back animation is controlled by the application.
* Returns a {@link RemoteAnimationTarget}, containing a leash to the top window container
* that needs to be animated. This can be null if the back animation is controlled by
* the application.
*/
@Nullable
public SurfaceControl getDepartingWindowContainer() {
return mDepartingWindowContainer;
public RemoteAnimationTarget getDepartingAnimationTarget() {
return mDepartingAnimationTarget;
}
/**
@@ -212,10 +222,14 @@ public final class BackNavigationInfo implements Parcelable {
/**
* Callback to be called when the back preview is finished in order to notify the server that
* it can clean up the resources created for the animation.
*
* @param triggerBack Boolean indicating if back navigation has been triggered.
*/
public void onBackNavigationFinished() {
if (mRemoteCallback != null) {
mRemoteCallback.sendResult(null);
public void onBackNavigationFinished(boolean triggerBack) {
if (mOnBackNavigationDone != null) {
Bundle result = new Bundle();
result.putBoolean(KEY_TRIGGER_BACK, triggerBack);
mOnBackNavigationDone.sendResult(result);
}
}
@@ -240,11 +254,11 @@ public final class BackNavigationInfo implements Parcelable {
public String toString() {
return "BackNavigationInfo{"
+ "mType=" + typeToString(mType) + " (" + mType + ")"
+ ", mDepartingWindowContainer=" + mDepartingWindowContainer
+ ", mDepartingAnimationTarget=" + mDepartingAnimationTarget
+ ", mScreenshotSurface=" + mScreenshotSurface
+ ", mTaskWindowConfiguration= " + mTaskWindowConfiguration
+ ", mScreenshotBuffer=" + mScreenshotBuffer
+ ", mRemoteCallback=" + mRemoteCallback
+ ", mOnBackNavigationDone=" + mOnBackNavigationDone
+ ", mOnBackInvokedCallback=" + mOnBackInvokedCallback
+ '}';
}

View File

@@ -160,11 +160,11 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
}
try {
if (callback == null) {
mWindowSession.setOnBackInvokedCallback(mWindow, null);
mWindowSession.setOnBackInvokedCallback(mWindow, null, PRIORITY_DEFAULT);
} else {
int priority = mAllCallbacks.get(callback);
mWindowSession.setOnBackInvokedCallback(
mWindow, new OnBackInvokedCallbackWrapper(callback, priority));
mWindow, new OnBackInvokedCallbackWrapper(callback, priority), priority);
}
} catch (RemoteException e) {
Log.e(TAG, "Failed to set OnBackInvokedCallback to WM. Error: " + e);

View File

@@ -83,8 +83,10 @@ public class WindowOnBackInvokedDispatcherTest {
mDispatcher.registerOnBackInvokedCallback(
mCallback2, OnBackInvokedDispatcher.PRIORITY_DEFAULT);
verify(mWindowSession, times(2))
.setOnBackInvokedCallback(Mockito.eq(mWindow), captor.capture());
verify(mWindowSession, times(2)).setOnBackInvokedCallback(
Mockito.eq(mWindow),
captor.capture(),
Mockito.eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT));
captor.getAllValues().get(0).onBackStarted();
waitForIdle();
verify(mCallback1).onBackStarted();
@@ -106,8 +108,9 @@ public class WindowOnBackInvokedDispatcherTest {
mDispatcher.registerOnBackInvokedCallback(
mCallback2, OnBackInvokedDispatcher.PRIORITY_DEFAULT);
verify(mWindowSession)
.setOnBackInvokedCallback(Mockito.eq(mWindow), captor.capture());
verify(mWindowSession).setOnBackInvokedCallback(
Mockito.eq(mWindow), captor.capture(),
Mockito.eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY));
verifyNoMoreInteractions(mWindowSession);
captor.getValue().onBackStarted();
waitForIdle();
@@ -126,7 +129,10 @@ public class WindowOnBackInvokedDispatcherTest {
verifyZeroInteractions(mWindowSession);
mDispatcher.unregisterOnBackInvokedCallback(mCallback2);
verify(mWindowSession).setOnBackInvokedCallback(Mockito.eq(mWindow), isNull());
verify(mWindowSession).setOnBackInvokedCallback(
Mockito.eq(mWindow),
isNull(),
Mockito.eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT));
}
@@ -145,8 +151,10 @@ public class WindowOnBackInvokedDispatcherTest {
reset(mWindowSession);
mDispatcher.registerOnBackInvokedCallback(
mCallback2, OnBackInvokedDispatcher.PRIORITY_OVERLAY);
verify(mWindowSession)
.setOnBackInvokedCallback(Mockito.eq(mWindow), captor.capture());
verify(mWindowSession).setOnBackInvokedCallback(
Mockito.eq(mWindow),
captor.capture(),
Mockito.eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY));
captor.getValue().onBackStarted();
waitForIdle();
verify(mCallback2).onBackStarted();

View File

@@ -1567,6 +1567,12 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/RootWindowContainer.java"
},
"-432881038": {
"message": "startBackNavigation task=%s, topRunningActivity=%s, applicationBackCallback=%s, systemBackCallback=%s",
"level": "DEBUG",
"group": "WM_DEBUG_BACK_PREVIEW",
"at": "com\/android\/server\/wm\/BackNavigationController.java"
},
"-415865166": {
"message": "findFocusedWindow: Found new focus @ %s",
"level": "VERBOSE",
@@ -3691,12 +3697,6 @@
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/RemoteAnimationController.java"
},
"1898905572": {
"message": "startBackNavigation task=%s, topRunningActivity=%s, topWindow=%s backCallback=%s",
"level": "DEBUG",
"group": "WM_DEBUG_BACK_PREVIEW",
"at": "com\/android\/server\/wm\/BackNavigationController.java"
},
"1903353011": {
"message": "notifyAppStopped: %s",
"level": "VERBOSE",

View File

@@ -43,4 +43,11 @@ public interface BackAnimation {
default IBackAnimation createExternalInterface() {
return null;
}
/**
* Sets the threshold values that defining edge swipe behavior.
* @param triggerThreshold the min threshold to trigger back.
* @param progressThreshold the max threshold to keep progressing back animation.
*/
void setSwipeThresholds(float triggerThreshold, float progressThreshold);
}

View File

@@ -35,6 +35,7 @@ import android.os.RemoteException;
import android.os.SystemProperties;
import android.util.Log;
import android.view.MotionEvent;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.window.BackEvent;
import android.window.BackNavigationInfo;
@@ -54,6 +55,10 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
private static final String BACK_PREDICTABILITY_PROP = "persist.debug.back_predictability";
public static final boolean IS_ENABLED = SystemProperties
.getInt(BACK_PREDICTABILITY_PROP, 0) > 0;
private static final String BACK_PREDICTABILITY_PROGRESS_THRESHOLD_PROP =
"persist.debug.back_predictability_progress_threshold";
private static final int PROGRESS_THRESHOLD = SystemProperties
.getInt(BACK_PREDICTABILITY_PROGRESS_THRESHOLD_PROP, -1);
private static final String TAG = "BackAnimationController";
/**
@@ -80,6 +85,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
private final Context mContext;
@Nullable
private IOnBackInvokedCallback mBackToLauncherCallback;
private float mTriggerThreshold;
private float mProgressThreshold;
public BackAnimationController(
@ShellMainThread ShellExecutor shellExecutor,
@@ -136,6 +143,12 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
public void setTriggerBack(boolean triggerBack) {
mShellExecutor.execute(() -> BackAnimationController.this.setTriggerBack(triggerBack));
}
@Override
public void setSwipeThresholds(float triggerThreshold, float progressThreshold) {
mShellExecutor.execute(() -> BackAnimationController.this.setSwipeThresholds(
triggerThreshold, progressThreshold));
}
}
private static class IBackAnimationImpl extends IBackAnimation.Stub {
@@ -168,7 +181,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
}
private void setBackToLauncherCallback(IOnBackInvokedCallback callback) {
@VisibleForTesting
void setBackToLauncherCallback(IOnBackInvokedCallback callback) {
mBackToLauncherCallback = callback;
}
@@ -177,6 +191,14 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
private void onBackToLauncherAnimationFinished() {
if (mBackNavigationInfo != null) {
IOnBackInvokedCallback callback = mBackNavigationInfo.getOnBackInvokedCallback();
if (mTriggerBack) {
dispatchOnBackInvoked(callback);
} else {
dispatchOnBackCancelled(callback);
}
}
finishAnimation();
}
@@ -218,19 +240,27 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
private void onBackNavigationInfoReceived(@Nullable BackNavigationInfo backNavigationInfo) {
if (backNavigationInfo == null
|| backNavigationInfo.getDepartingWindowContainer() == null) {
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Received backNavigationInfo:%s", backNavigationInfo);
if (backNavigationInfo == null) {
Log.e(TAG, "Received BackNavigationInfo is null.");
finishAnimation();
return;
}
HardwareBuffer hardwareBuffer = backNavigationInfo.getScreenshotHardwareBuffer();
if (hardwareBuffer != null) {
displayTargetScreenshot(hardwareBuffer,
backNavigationInfo.getTaskWindowConfiguration());
int backType = backNavigationInfo.getType();
IOnBackInvokedCallback targetCallback = null;
if (backType == BackNavigationInfo.TYPE_CROSS_ACTIVITY) {
HardwareBuffer hardwareBuffer = backNavigationInfo.getScreenshotHardwareBuffer();
if (hardwareBuffer != null) {
displayTargetScreenshot(hardwareBuffer,
backNavigationInfo.getTaskWindowConfiguration());
}
mTransaction.apply();
} else if (backType == BackNavigationInfo.TYPE_RETURN_TO_HOME) {
targetCallback = mBackToLauncherCallback;
} else if (backType == BackNavigationInfo.TYPE_CALLBACK) {
targetCallback = mBackNavigationInfo.getOnBackInvokedCallback();
}
mTransaction.apply();
dispatchOnBackStarted(targetCallback);
}
/**
@@ -272,23 +302,103 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
int deltaX = Math.round(event.getX() - mInitTouchLocation.x);
int deltaY = Math.round(event.getY() - mInitTouchLocation.y);
ProtoLog.v(WM_SHELL_BACK_PREVIEW, "Runner move: %d %d", deltaX, deltaY);
SurfaceControl topWindowLeash = mBackNavigationInfo.getDepartingWindowContainer();
mTransaction.setPosition(topWindowLeash, deltaX, deltaY);
mTouchEventDelta.set(deltaX, deltaY);
mTransaction.apply();
float progressThreshold = PROGRESS_THRESHOLD >= 0 ? PROGRESS_THRESHOLD : mProgressThreshold;
float progress = Math.min(Math.max(Math.abs(deltaX) / progressThreshold, 0), 1);
int backType = mBackNavigationInfo.getType();
RemoteAnimationTarget animationTarget = mBackNavigationInfo.getDepartingAnimationTarget();
BackEvent backEvent = new BackEvent(0, 0, progress, swipeEdge, animationTarget);
IOnBackInvokedCallback targetCallback = null;
if (backType == BackNavigationInfo.TYPE_RETURN_TO_HOME) {
targetCallback = mBackToLauncherCallback;
} else if (backType == BackNavigationInfo.TYPE_CROSS_TASK
|| backType == BackNavigationInfo.TYPE_CROSS_ACTIVITY) {
if (animationTarget != null) {
mTransaction.setPosition(animationTarget.leash, deltaX, deltaY);
mTouchEventDelta.set(deltaX, deltaY);
mTransaction.apply();
}
} else if (backType == BackNavigationInfo.TYPE_CALLBACK) {
targetCallback = mBackNavigationInfo.getOnBackInvokedCallback();
}
dispatchOnBackProgressed(targetCallback, backEvent);
}
private void onGestureFinished() {
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "onGestureFinished() mTriggerBack == %s", mTriggerBack);
if (mBackGestureStarted) {
if (!mBackGestureStarted || mBackNavigationInfo == null) {
return;
}
int backType = mBackNavigationInfo.getType();
boolean shouldDispatchToLauncher = backType == BackNavigationInfo.TYPE_RETURN_TO_HOME
&& mBackToLauncherCallback != null;
IOnBackInvokedCallback targetCallback = shouldDispatchToLauncher
? mBackToLauncherCallback
: mBackNavigationInfo.getOnBackInvokedCallback();
if (mTriggerBack) {
dispatchOnBackInvoked(targetCallback);
} else {
dispatchOnBackCancelled(targetCallback);
}
if (backType == BackNavigationInfo.TYPE_CALLBACK) {
finishAnimation();
} else if (backType == BackNavigationInfo.TYPE_RETURN_TO_HOME
&& !shouldDispatchToLauncher) {
// Launcher callback missing. Simply finish animation.
finishAnimation();
} else if (backType == BackNavigationInfo.TYPE_CROSS_ACTIVITY
|| backType == BackNavigationInfo.TYPE_CROSS_TASK) {
if (mTriggerBack) {
prepareTransition();
} else {
resetPositionAnimated();
}
}
mBackGestureStarted = false;
mTriggerBack = false;
}
private static void dispatchOnBackStarted(IOnBackInvokedCallback callback) {
if (callback == null) {
return;
}
try {
callback.onBackStarted();
} catch (RemoteException e) {
Log.e(TAG, "dispatchOnBackStarted error: ", e);
}
}
private static void dispatchOnBackInvoked(IOnBackInvokedCallback callback) {
if (callback == null) {
return;
}
try {
callback.onBackInvoked();
} catch (RemoteException e) {
Log.e(TAG, "dispatchOnBackInvoked error: ", e);
}
}
private static void dispatchOnBackCancelled(IOnBackInvokedCallback callback) {
if (callback == null) {
return;
}
try {
callback.onBackCancelled();
} catch (RemoteException e) {
Log.e(TAG, "dispatchOnBackCancelled error: ", e);
}
}
private static void dispatchOnBackProgressed(
IOnBackInvokedCallback callback, BackEvent backEvent) {
if (callback == null) {
return;
}
try {
callback.onBackProgressed(backEvent);
} catch (RemoteException e) {
Log.e(TAG, "dispatchOnBackProgressed error: ", e);
}
}
/**
@@ -309,9 +419,12 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
float fraction = animation1.getAnimatedFraction();
int deltaX = Math.round(mTouchEventDelta.x - (mTouchEventDelta.x * fraction));
int deltaY = Math.round(mTouchEventDelta.y - (mTouchEventDelta.y * fraction));
mTransaction.setPosition(mBackNavigationInfo.getDepartingWindowContainer(),
deltaX, deltaY);
mTransaction.apply();
RemoteAnimationTarget animationTarget =
mBackNavigationInfo.getDepartingAnimationTarget();
if (animationTarget != null) {
mTransaction.setPosition(animationTarget.leash, deltaX, deltaY);
mTransaction.apply();
}
});
animation.addListener(new AnimatorListenerAdapter() {
@@ -337,25 +450,34 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
mTriggerBack = triggerBack;
}
private void setSwipeThresholds(float triggerThreshold, float progressThreshold) {
mProgressThreshold = progressThreshold;
mTriggerThreshold = triggerThreshold;
}
private void finishAnimation() {
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: finishAnimation()");
mBackGestureStarted = false;
mTouchEventDelta.set(0, 0);
mInitTouchLocation.set(0, 0);
BackNavigationInfo backNavigationInfo = mBackNavigationInfo;
boolean triggerBack = mTriggerBack;
mBackNavigationInfo = null;
mTriggerBack = false;
if (backNavigationInfo == null) {
return;
}
SurfaceControl topWindowLeash = backNavigationInfo.getDepartingWindowContainer();
if (topWindowLeash != null && topWindowLeash.isValid()) {
mTransaction.remove(topWindowLeash);
RemoteAnimationTarget animationTarget = backNavigationInfo.getDepartingAnimationTarget();
if (animationTarget != null && mTriggerBack) {
if (animationTarget.leash != null && animationTarget.leash.isValid()) {
mTransaction.remove(animationTarget.leash);
}
}
SurfaceControl screenshotSurface = backNavigationInfo.getScreenshotSurface();
if (screenshotSurface != null && screenshotSurface.isValid()) {
mTransaction.remove(screenshotSurface);
}
mTransaction.apply();
backNavigationInfo.onBackNavigationFinished();
backNavigationInfo.onBackNavigationFinished(triggerBack);
}
}

View File

@@ -16,6 +16,7 @@
package com.android.wm.shell.back;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -24,14 +25,18 @@ import static org.mockito.Mockito.verify;
import android.app.IActivityTaskManager;
import android.app.WindowConfiguration;
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.testing.AndroidTestingRunner;
import android.view.MotionEvent;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.window.BackEvent;
import android.window.BackNavigationInfo;
import android.window.IOnBackInvokedCallback;
import androidx.test.filters.SmallTest;
@@ -41,6 +46,7 @@ import com.android.wm.shell.common.ShellExecutor;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -62,6 +68,9 @@ public class BackAnimationControllerTest {
@Mock
private IActivityTaskManager mActivityTaskManager;
@Mock
private IOnBackInvokedCallback mIOnBackInvokedCallback;
private BackAnimationController mController;
@Before
@@ -71,12 +80,13 @@ public class BackAnimationControllerTest {
mShellExecutor, mTransaction, mActivityTaskManager, mContext);
}
private void createNavigationInfo(SurfaceControl topWindowLeash,
private void createNavigationInfo(RemoteAnimationTarget topAnimationTarget,
SurfaceControl screenshotSurface,
HardwareBuffer hardwareBuffer) {
HardwareBuffer hardwareBuffer,
int backType) {
BackNavigationInfo navigationInfo = new BackNavigationInfo(
BackNavigationInfo.TYPE_RETURN_TO_HOME,
topWindowLeash,
backType,
topAnimationTarget,
screenshotSurface,
hardwareBuffer,
new WindowConfiguration(),
@@ -89,12 +99,20 @@ public class BackAnimationControllerTest {
}
}
@Test
public void screenshotAttachedAndVisible() {
RemoteAnimationTarget createAnimationTarget() {
SurfaceControl topWindowLeash = new SurfaceControl();
return new RemoteAnimationTarget(-1, RemoteAnimationTarget.MODE_CLOSING, topWindowLeash,
false, new Rect(), new Rect(), -1,
new Point(0, 0), new Rect(), new Rect(), new WindowConfiguration(),
true, null, null, null, false, -1);
}
@Test
public void crossActivity_screenshotAttachedAndVisible() {
SurfaceControl screenshotSurface = new SurfaceControl();
HardwareBuffer hardwareBuffer = mock(HardwareBuffer.class);
createNavigationInfo(topWindowLeash, screenshotSurface, hardwareBuffer);
createNavigationInfo(createAnimationTarget(), screenshotSurface, hardwareBuffer,
BackNavigationInfo.TYPE_CROSS_ACTIVITY);
mController.onMotionEvent(
MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0),
BackEvent.EDGE_LEFT);
@@ -104,18 +122,48 @@ public class BackAnimationControllerTest {
}
@Test
public void surfaceMovesWithGesture() {
SurfaceControl topWindowLeash = new SurfaceControl();
public void crossActivity_surfaceMovesWithGesture() {
SurfaceControl screenshotSurface = new SurfaceControl();
HardwareBuffer hardwareBuffer = mock(HardwareBuffer.class);
createNavigationInfo(topWindowLeash, screenshotSurface, hardwareBuffer);
RemoteAnimationTarget animationTarget = createAnimationTarget();
createNavigationInfo(animationTarget, screenshotSurface, hardwareBuffer,
BackNavigationInfo.TYPE_CROSS_ACTIVITY);
mController.onMotionEvent(
MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0),
BackEvent.EDGE_LEFT);
mController.onMotionEvent(
MotionEvent.obtain(10, 0, MotionEvent.ACTION_MOVE, 100, 100, 0),
BackEvent.EDGE_LEFT);
verify(mTransaction).setPosition(topWindowLeash, 100, 100);
verify(mTransaction).setPosition(animationTarget.leash, 100, 100);
verify(mTransaction, atLeastOnce()).apply();
}
@Test
public void backToHome_dispatchesEvents() throws RemoteException {
mController.setBackToLauncherCallback(mIOnBackInvokedCallback);
RemoteAnimationTarget animationTarget = createAnimationTarget();
createNavigationInfo(animationTarget, null, null,
BackNavigationInfo.TYPE_RETURN_TO_HOME);
// Check that back start is dispatched.
mController.onMotionEvent(
MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0),
BackEvent.EDGE_LEFT);
verify(mIOnBackInvokedCallback).onBackStarted();
// Check that back progress is dispatched.
mController.onMotionEvent(
MotionEvent.obtain(10, 0, MotionEvent.ACTION_MOVE, 100, 100, 0),
BackEvent.EDGE_LEFT);
ArgumentCaptor<BackEvent> backEventCaptor = ArgumentCaptor.forClass(BackEvent.class);
verify(mIOnBackInvokedCallback).onBackProgressed(backEventCaptor.capture());
assertEquals(animationTarget, backEventCaptor.getValue().getDepartingAnimationTarget());
// Check that back invocation is dispatched.
mController.setTriggerBack(true); // Fake trigger back
mController.onMotionEvent(
MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0),
BackEvent.EDGE_LEFT);
verify(mIOnBackInvokedCallback).onBackInvoked();
}
}

View File

@@ -43,6 +43,8 @@
<dimen name="navigation_edge_panel_height">96dp</dimen>
<!-- The threshold to drag to trigger the edge action -->
<dimen name="navigation_edge_action_drag_threshold">16dp</dimen>
<!-- The threshold to progress back animation for edge swipe -->
<dimen name="navigation_edge_action_progress_threshold">400dp</dimen>
<!-- The minimum display position of the arrow on the screen -->
<dimen name="navigation_edge_arrow_min_y">64dp</dimen>
<!-- The amount by which the arrow is shifted to avoid the finger-->

View File

@@ -162,7 +162,8 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl
// The amount the arrow is shifted to avoid the finger.
private int mFingerOffset;
private final float mSwipeThreshold;
private final float mSwipeTriggerThreshold;
private final float mSwipeProgressThreshold;
private final Path mArrowPath = new Path();
private final Point mDisplaySize = new Point();
@@ -352,10 +353,15 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl
loadColors(context);
updateArrowDirection();
mSwipeThreshold = context.getResources()
mSwipeTriggerThreshold = context.getResources()
.getDimension(R.dimen.navigation_edge_action_drag_threshold);
setVisibility(GONE);
mSwipeProgressThreshold = context.getResources()
.getDimension(R.dimen.navigation_edge_action_progress_threshold);
if (mBackAnimation != null) {
mBackAnimation.setSwipeThresholds(mSwipeTriggerThreshold, mSwipeProgressThreshold);
}
setVisibility(GONE);
Executor backgroundExecutor = Dependency.get(Dependency.BACKGROUND_EXECUTOR);
boolean isPrimaryDisplay = mContext.getDisplayId() == DEFAULT_DISPLAY;
mRegionSamplingHelper = new RegionSamplingHelper(this,
@@ -730,7 +736,7 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl
mPreviousTouchTranslation = touchTranslation;
// Apply a haptic on drag slop passed
if (!mDragSlopPassed && touchTranslation > mSwipeThreshold) {
if (!mDragSlopPassed && touchTranslation > mSwipeTriggerThreshold) {
mDragSlopPassed = true;
mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
mVibrationTime = SystemClock.uptimeMillis();

View File

@@ -22,11 +22,14 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.WindowConfiguration;
import android.content.ComponentName;
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.util.Slog;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.window.BackNavigationInfo;
import android.window.IOnBackInvokedCallback;
@@ -88,34 +91,39 @@ class BackNavigationController {
ActivityRecord activityRecord;
SurfaceControl animationLeashParent;
WindowConfiguration taskWindowConfiguration;
SurfaceControl animLeash;
HardwareBuffer screenshotBuffer = null;
int prevTaskId;
int prevUserId;
IOnBackInvokedCallback callback;
IOnBackInvokedCallback applicationCallback = null;
IOnBackInvokedCallback systemCallback = null;
RemoteAnimationTarget topAppTarget;
SurfaceControl animLeash;
synchronized (task.mWmService.mGlobalLock) {
activityRecord = task.topRunningActivity();
removedWindowContainer = activityRecord;
taskWindowConfiguration = task.getTaskInfo().configuration.windowConfiguration;
WindowState topChild = activityRecord.getTopChild();
callback = topChild.getOnBackInvokedCallback();
WindowState window = task.getWindow(WindowState::isFocused);
if (window != null) {
applicationCallback = window.getApplicationOnBackInvokedCallback();
systemCallback = window.getSystemOnBackInvokedCallback();
}
ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "startBackNavigation task=%s, "
+ "topRunningActivity=%s, topWindow=%s backCallback=%s",
task, activityRecord, topChild,
callback != null ? callback.getClass().getSimpleName() : null);
+ "topRunningActivity=%s, applicationBackCallback=%s, "
+ "systemBackCallback=%s",
task, activityRecord, applicationCallback, systemCallback);
// For IME and Home, either a callback is registered, or we do nothing. In both cases,
// we don't need to pass the leashes below.
if (task.getDisplayContent().getImeContainer().isVisible()
|| activityRecord.isActivityTypeHome()) {
if (callback != null) {
if (applicationCallback != null) {
return new BackNavigationInfo(BackNavigationInfo.TYPE_CALLBACK,
null /* topWindowLeash */, null /* screenshotSurface */,
null /* screenshotBuffer */, null /* taskWindowConfiguration */,
null /* onBackNavigationDone */, callback /* onBackInvokedCallback */);
null /* onBackNavigationDone */,
applicationCallback /* onBackInvokedCallback */);
} else {
return null;
}
@@ -124,8 +132,12 @@ class BackNavigationController {
prev = task.getActivity(
(r) -> !r.finishing && r.getTask() == task && !r.isTopRunningActivity());
if (callback != null) {
backType = BackNavigationInfo.TYPE_CALLBACK;
if (applicationCallback != null) {
return new BackNavigationInfo(BackNavigationInfo.TYPE_CALLBACK,
null /* topWindowLeash */, null /* screenshotSurface */,
null /* screenshotBuffer */, null /* taskWindowConfiguration */,
null /* onBackNavigationDone */,
applicationCallback /* onBackInvokedCallback */);
} else if (prev != null) {
backType = BackNavigationInfo.TYPE_CROSS_ACTIVITY;
} else if (task.returnsToHomeRootTask()) {
@@ -159,6 +171,11 @@ class BackNavigationController {
screenshotBuffer = getActivitySnapshot(task, prev.mActivityComponent);
}
// Only create a new leash if no leash has been created.
// Otherwise return null for animation target to avoid conflict.
if (removedWindowContainer.hasCommittedReparentToAnimationLeash()) {
return null;
}
// Prepare a leash to animate the current top window
animLeash = removedWindowContainer.makeAnimationLeash()
.setName("BackPreview Leash for " + removedWindowContainer)
@@ -166,8 +183,25 @@ class BackNavigationController {
.setBLASTLayer()
.build();
removedWindowContainer.reparentSurfaceControl(tx, animLeash);
animationLeashParent = removedWindowContainer.getAnimationLeashParent();
topAppTarget = new RemoteAnimationTarget(
task.mTaskId,
RemoteAnimationTarget.MODE_CLOSING,
animLeash,
false /* isTransluscent */,
new Rect() /* clipRect */,
new Rect() /* contentInsets */,
activityRecord.getPrefixOrderIndex(),
new Point(0, 0) /* position */,
new Rect() /* localBounds */,
new Rect() /* screenSpaceBounds */,
removedWindowContainer.getWindowConfiguration(),
true /* isNotInRecent */,
null,
null,
task.getTaskInfo(),
false,
activityRecord.windowType);
}
SurfaceControl.Builder builder = new SurfaceControl.Builder()
@@ -187,7 +221,7 @@ class BackNavigationController {
// The Animation leash needs to be above the screenshot surface, but the animation leash
// needs to be added before to be in the synchronized block.
tx.setLayer(animLeash, 1);
tx.setLayer(topAppTarget.leash, 1);
tx.apply();
WindowContainer<?> finalRemovedWindowContainer = removedWindowContainer;
@@ -200,11 +234,13 @@ class BackNavigationController {
return null;
}
final IOnBackInvokedCallback callback =
applicationCallback != null ? applicationCallback : systemCallback;
RemoteCallback onBackNavigationDone = new RemoteCallback(
result -> resetSurfaces(finalRemovedWindowContainer
));
return new BackNavigationInfo(backType,
animLeash,
topAppTarget,
screenshotSurface,
screenshotBuffer,
taskWindowConfiguration,

View File

@@ -69,6 +69,7 @@ import android.view.InputChannel;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.InsetsVisibilities;
import android.view.OnBackInvokedDispatcher;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.View;
@@ -905,15 +906,17 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
}
@Override
public void setOnBackInvokedCallback(IWindow window,
IOnBackInvokedCallback onBackInvokedCallback) throws RemoteException {
public void setOnBackInvokedCallback(
IWindow window,
IOnBackInvokedCallback onBackInvokedCallback,
@OnBackInvokedDispatcher.Priority int priority) throws RemoteException {
synchronized (mService.mGlobalLock) {
WindowState windowState = mService.windowForClientLocked(this, window, false);
if (windowState == null) {
Slog.e(TAG_WM,
"setOnBackInvokedCallback(): Can't find window state for window:" + window);
} else {
windowState.setOnBackInvokedCallback(onBackInvokedCallback);
windowState.setOnBackInvokedCallback(onBackInvokedCallback, priority);
}
}
}

View File

@@ -850,7 +850,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
/**
* @see #setOnBackInvokedCallback(IOnBackInvokedCallback)
*/
private IOnBackInvokedCallback mOnBackInvokedCallback;
private IOnBackInvokedCallback mApplicationOnBackInvokedCallback;
private IOnBackInvokedCallback mSystemOnBackInvokedCallback;
@Override
WindowState asWindowState() {
@@ -1073,15 +1074,25 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
* called when a back navigation action is initiated.
* @see BackNavigationController
*/
void setOnBackInvokedCallback(@Nullable IOnBackInvokedCallback onBackInvokedCallback) {
void setOnBackInvokedCallback(
@Nullable IOnBackInvokedCallback onBackInvokedCallback, int priority) {
ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "%s: Setting back callback %s",
this, onBackInvokedCallback);
mOnBackInvokedCallback = onBackInvokedCallback;
if (priority >= 0) {
mApplicationOnBackInvokedCallback = onBackInvokedCallback;
} else {
mSystemOnBackInvokedCallback = onBackInvokedCallback;
}
}
@Nullable
IOnBackInvokedCallback getOnBackInvokedCallback() {
return mOnBackInvokedCallback;
IOnBackInvokedCallback getApplicationOnBackInvokedCallback() {
return mApplicationOnBackInvokedCallback;
}
@Nullable
IOnBackInvokedCallback getSystemOnBackInvokedCallback() {
return mSystemOnBackInvokedCallback;
}
interface PowerManagerWrapper {
@@ -2385,7 +2396,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
dc.getDisplayPolicy().removeWindowLw(this);
disposeInputChannel();
mOnBackInvokedCallback = null;
mSystemOnBackInvokedCallback = null;
mApplicationOnBackInvokedCallback = null;
mSession.windowRemovedLocked();
try {
@@ -2439,7 +2451,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
try {
disposeInputChannel();
mOnBackInvokedCallback = null;
mSystemOnBackInvokedCallback = null;
mApplicationOnBackInvokedCallback = null;
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
"Remove %s: mSurfaceController=%s mAnimatingExit=%b mRemoveOnExit=%b "

View File

@@ -97,7 +97,7 @@ public class BackNavigationControllerTests extends WindowTestsBase {
BackNavigationInfo backNavigationInfo =
mBackNavigationController.startBackNavigation(task, new StubTransaction());
assertThat(backNavigationInfo).isNotNull();
assertThat(backNavigationInfo.getDepartingWindowContainer()).isNotNull();
assertThat(backNavigationInfo.getDepartingAnimationTarget()).isNotNull();
assertThat(backNavigationInfo.getScreenshotSurface()).isNotNull();
assertThat(backNavigationInfo.getScreenshotHardwareBuffer()).isNotNull();
assertThat(backNavigationInfo.getTaskWindowConfiguration()).isNotNull();