Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 51d65509 authored by Louis Chang's avatar Louis Chang
Browse files

Use customized back-invoked-callback when intercepting back

The back navigation is intercepted when performing a back gesture
while Bubble expanded. Although the back preview animation is dropped
dropped in this case, it still gets the back-invoked-callback from
the client, which will still invoke Activity#onBackPressed by default
(via dispatch back key event in ViewRootImpl) when the back gesture
completed. Activity finishes itself when #onBackPressed is called.
So the bubble is removed vs. collapsed.

Therefore, here uses a customized back-invoked-callback when intercept
happens and avoid sending back key event to the client.

Bug: 427336494
Test: swipe back when bubble expanded
Test: BackNavigationControllerTests
Flag: EXEMPT bugfix
Change-Id: I20e5f6593c877af787d203436af8486ede3219f0
parent c3ec26a3
Loading
Loading
Loading
Loading
+77 −8
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION;
import static android.window.DesktopExperienceFlags.ENABLE_INDEPENDENT_BACK_IN_PROJECTED;
import static android.window.OnBackInvokedDispatcher.PRIORITY_DEFAULT;
import static android.window.SystemOverrideOnBackInvokedCallback.OVERRIDE_FINISH_AND_REMOVE_TASK;
import static android.window.SystemOverrideOnBackInvokedCallback.OVERRIDE_UNDEFINED;

@@ -60,8 +61,11 @@ import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.WindowInsets;
import android.window.BackAnimationAdapter;
import android.window.BackMotionEvent;
import android.window.BackNavigationInfo;
import android.window.IBackAnimationFinishedCallback;
import android.window.IBackAnimationHandoffHandler;
import android.window.IOnBackInvokedCallback;
import android.window.IWindowlessStartingSurfaceCallback;
import android.window.OnBackInvokedCallbackInfo;
import android.window.SystemOverrideOnBackInvokedCallback;
@@ -74,6 +78,7 @@ import com.android.internal.protolog.ProtoLog;
import com.android.window.flags.Flags;

import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Objects;
@@ -135,6 +140,62 @@ class BackNavigationController {
        }
    }

    class OnInterceptBackInvokedCallback extends IOnBackInvokedCallback.Stub {
        @NonNull
        final WeakReference<ActivityRecord> mActivityRecordRef;
        @Nullable
        final WeakReference<IOnBackInvokedCallback> mFallbackCallbackRef;

        OnInterceptBackInvokedCallback(@NonNull ActivityRecord r,
                @Nullable IOnBackInvokedCallback fallback) {
            mActivityRecordRef = new WeakReference<>(r);
            mFallbackCallbackRef = fallback != null ? new WeakReference<>(fallback) : null;
        }

        @Override
        public void onBackInvoked() {
            synchronized (mWindowManagerService.mGlobalLock) {
                final ActivityRecord r = mActivityRecordRef.get();
                boolean handled = false;
                if (r != null) {
                    handled = mWindowManagerService.mAtmService.mTaskOrganizerController
                            .handleInterceptBackPressedOnTaskRoot(r);
                }
                if (handled || mFallbackCallbackRef == null) {
                    return;
                }

                // Try the fallback callback if the back event was not handled.
                final IOnBackInvokedCallback fallbackCallback = mFallbackCallbackRef.get();
                if (fallbackCallback != null) {
                    try {
                        fallbackCallback.onBackInvoked();
                    } catch (RemoteException ex) {
                        Slog.w(TAG, "Failed invoking fallback callback for back");
                    }
                }
            }
        }

        @Override
        public void onBackCancelled() {}

        @Override
        public void onBackProgressed(BackMotionEvent backMotionEvent) {
        }

        @Override
        public void onBackStarted(BackMotionEvent backEvent) {
        }

        @Override
        public void setTriggerBack(boolean triggerBack) {
        }

        @Override
        public void setHandoffHandler(IBackAnimationHandoffHandler unused) {
        }
    }
    /**
     * Set up the necessary leashes and build a {@link BackNavigationInfo} instance for an upcoming
     * back gesture animation.
@@ -242,8 +303,22 @@ class BackNavigationController {
                ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Focus window is closing.");
                return null;
            }

            final boolean interceptBack = currentTask != null
                    && currentTask.mAtmService.mTaskOrganizerController
                            .shouldInterceptBackPressedOnRootTask(currentTask.getRootTask());
            final OnBackInvokedCallbackInfo callbackInfo;
            if (interceptBack) {
                final OnBackInvokedCallbackInfo info = window.getOnBackInvokedCallbackInfo();
                final IOnBackInvokedCallback callback =
                        new OnInterceptBackInvokedCallback(currentActivity,
                                info != null ? info.getCallback() : null);
                callbackInfo = new OnBackInvokedCallbackInfo(callback, PRIORITY_DEFAULT, false,
                        OVERRIDE_UNDEFINED);
            } else {
                // Now let's find if this window has a callback from the client side.
            final OnBackInvokedCallbackInfo callbackInfo = window.getOnBackInvokedCallbackInfo();
                callbackInfo = window.getOnBackInvokedCallbackInfo();
            }
            if (callbackInfo == null) {
                Slog.e(TAG, "No callback registered, returning null.");
                return null;
@@ -475,12 +550,6 @@ class BackNavigationController {
    static boolean getAnimatablePrevActivities(@NonNull Task currentTask,
            @NonNull ActivityRecord currentActivity,
            @NonNull ArrayList<ActivityRecord> outPrevActivities) {
        if (currentActivity.mAtmService
                .mTaskOrganizerController.shouldInterceptBackPressedOnRootTask(
                        currentTask.getRootTask())) {
            // The task organizer will handle back pressed, don't play animation.
            return false;
        }
        final ActivityRecord root = currentTask.getRootActivity(false /*ignoreRelinquishIdentity*/,
                true /*setToBottomIfNone*/);
        if (root != null && ActivityClientController.shouldMoveTaskToBack(currentActivity, root)) {
+15 −0
Original line number Diff line number Diff line
@@ -548,6 +548,21 @@ public class BackNavigationControllerTests extends WindowTestsBase {
        assertThat(backNavigationInfo.getOnBackInvokedCallback()).isEqualTo(appCallback);
    }

    @Test
    public void backTypeCallbackWhenInterceptBackPressedOnRootTask() {
        Task task = createTopTaskWithActivity();
        IOnBackInvokedCallback appCallback = withAppCallback(task);

        spyOn(mAtm.mTaskOrganizerController);
        Mockito.doReturn(true).when(
                mAtm.mTaskOrganizerController).shouldInterceptBackPressedOnRootTask(eq(task));
        BackNavigationInfo backNavigationInfo = startBackNavigation();
        assertThat(typeToString(backNavigationInfo.getType()))
                .isEqualTo(typeToString(BackNavigationInfo.TYPE_CALLBACK));
        // The callback should be replaced.
        assertThat(backNavigationInfo.getOnBackInvokedCallback()).isNotEqualTo(appCallback);
    }

    // TODO (b/259427810) Remove this test when we figure out new API
    @Test
    public void backAnimationSkipSharedElementTransition() {