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

Commit 2bbc80f4 authored by wilsonshih's avatar wilsonshih
Browse files

Correct record activity snapshot condition.

Predictive back animation will happen when top activity has opt-in
enableOnBackInvokedCallback, and there will use activity snapshot for
previous activity.
So the correct condition is to record snapshot when opening activity
has opt-in onBackInvoked, instead of checking the closeing activity.

Bug: 259497289
Test: atest WindowOnBackInvokedDispatcherTest
Test: enable activity snapshot, and verify there will record activity
snapshot for close activity if the opening activity has opt-in
onBackInvoked.

Change-Id: I51814441bccd5e309cc3b310528b5c0bee3f24ba
parent 19bd7034
Loading
Loading
Loading
Loading
+74 −83
Original line number Diff line number Diff line
@@ -41,6 +41,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.Objects;
import java.util.TreeMap;
import java.util.function.Supplier;

/**
 * Provides window based implementation of {@link OnBackInvokedDispatcher}.
@@ -271,7 +272,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
     * Returns false if the legacy back behavior should be used.
     */
    public boolean isOnBackInvokedCallbackEnabled() {
        return Checker.isOnBackInvokedCallbackEnabled(mChecker.getContext());
        return isOnBackInvokedCallbackEnabled(mChecker.getContext());
    }

    /**
@@ -394,7 +395,18 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
     * {@link OnBackInvokedCallback}.
     */
    public static boolean isOnBackInvokedCallbackEnabled(@NonNull Context context) {
        return Checker.isOnBackInvokedCallbackEnabled(context);
        final Context originalContext = context;
        while ((context instanceof ContextWrapper) && !(context instanceof Activity)) {
            context = ((ContextWrapper) context).getBaseContext();
        }
        final ActivityInfo activityInfo = (context instanceof Activity)
                ? ((Activity) context).getActivityInfo() : null;
        final ApplicationInfo applicationInfo = context.getApplicationInfo();

        return WindowOnBackInvokedDispatcher
                .isOnBackInvokedCallbackEnabled(activityInfo, applicationInfo,
                        () -> originalContext.obtainStyledAttributes(
                                new int[] {android.R.attr.windowSwipeToDismiss}), true);
    }

    @Override
@@ -426,7 +438,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
         */
        public boolean checkApplicationCallbackRegistration(int priority,
                OnBackInvokedCallback callback) {
            if (!isOnBackInvokedCallbackEnabled(getContext())
            if (!WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(getContext())
                    && !(callback instanceof CompatOnBackInvokedCallback)) {
                Log.w(TAG,
                        "OnBackInvokedCallback is not enabled for the application."
@@ -445,12 +457,17 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
        private Context getContext() {
            return mContext.get();
        }
    }

        private static boolean isOnBackInvokedCallbackEnabled(@Nullable Context context) {
    /**
     * @hide
     */
    public static boolean isOnBackInvokedCallbackEnabled(@Nullable ActivityInfo activityInfo,
            @NonNull ApplicationInfo applicationInfo,
            @NonNull Supplier<TypedArray> windowAttrSupplier, boolean recycleTypedArray) {
        // new back is enabled if the feature flag is enabled AND the app does not explicitly
        // request legacy back.
            boolean featureFlagEnabled = ENABLE_PREDICTIVE_BACK;
            if (!featureFlagEnabled) {
        if (!ENABLE_PREDICTIVE_BACK) {
            return false;
        }

@@ -458,56 +475,31 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
            return true;
        }

            // If the context is null, return false to use legacy back.
            if (context == null) {
                Log.w(TAG, "OnBackInvokedCallback is not enabled because context is null.");
                return false;
            }

            boolean requestsPredictiveBack = false;

            // Check if the context is from an activity.
            Context originalContext = context;
            while ((context instanceof ContextWrapper) && !(context instanceof Activity)) {
                context = ((ContextWrapper) context).getBaseContext();
            }

            boolean shouldCheckActivity = false;

            if (context instanceof Activity) {
                final Activity activity = (Activity) context;

                final ActivityInfo activityInfo = activity.getActivityInfo();
                if (activityInfo != null) {
                    if (activityInfo.hasOnBackInvokedCallbackEnabled()) {
                        shouldCheckActivity = true;
        boolean requestsPredictiveBack;
        // Activity
        if (activityInfo != null && activityInfo.hasOnBackInvokedCallbackEnabled()) {
            requestsPredictiveBack = activityInfo.isOnBackInvokedCallbackEnabled();

            if (DEBUG) {
                Log.d(TAG, TextUtils.formatSimple(
                        "Activity: %s isPredictiveBackEnabled=%s",
                                    activity.getComponentName(),
                        activityInfo.getComponentName(),
                        requestsPredictiveBack));
            }
                    }
                } else {
                    Log.w(TAG, "The ActivityInfo is null, so we cannot verify if this Activity"
                            + " has the 'android:enableOnBackInvokedCallback' attribute."
                            + " The application attribute will be used as a fallback.");
                }
            return requestsPredictiveBack;
        }

            if (!shouldCheckActivity) {
                final ApplicationInfo applicationInfo = context.getApplicationInfo();
        // Application
        requestsPredictiveBack = applicationInfo.isOnBackInvokedCallbackEnabled();

        if (DEBUG) {
            Log.d(TAG, TextUtils.formatSimple("App: %s requestsPredictiveBack=%s",
                    applicationInfo.packageName,
                    requestsPredictiveBack));
        }
        if (requestsPredictiveBack) {
            return true;
        }

                if (PREDICTIVE_BACK_FALLBACK_WINDOW_ATTRIBUTE && !requestsPredictiveBack) {
        if (PREDICTIVE_BACK_FALLBACK_WINDOW_ATTRIBUTE) {
            // Compatibility check for legacy window style flag used by Wear OS.
            // Note on compatibility behavior:
            // 1. windowSwipeToDismiss should be respected for all apps not opted in.
@@ -518,14 +510,16 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
            //    setTrigger(true)
            // Use the original context to resolve the styled attribute so that they stay
            // true to the window.
                    TypedArray windowAttr =
                            originalContext.obtainStyledAttributes(
                                    new int[] {android.R.attr.windowSwipeToDismiss});
            TypedArray windowAttr = windowAttrSupplier.get();
            boolean windowSwipeToDismiss = true;
            if (windowAttr != null) {
                if (windowAttr.getIndexCount() > 0) {
                    windowSwipeToDismiss = windowAttr.getBoolean(0, true);
                }
                if (recycleTypedArray) {
                    windowAttr.recycle();
                }
            }

            if (DEBUG) {
                Log.i(TAG, "falling back to windowSwipeToDismiss: " + windowSwipeToDismiss);
@@ -533,9 +527,6 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {

            requestsPredictiveBack = windowSwipeToDismiss;
        }
            }

        return requestsPredictiveBack;
    }
}
}
+7 −0
Original line number Diff line number Diff line
@@ -353,6 +353,7 @@ import android.window.SplashScreenView.SplashScreenViewParcelable;
import android.window.TaskSnapshot;
import android.window.TransitionInfo.AnimationOptions;
import android.window.WindowContainerToken;
import android.window.WindowOnBackInvokedDispatcher;

import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -979,6 +980,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
    int mAllowedTouchUid;
    // Whether client has requested a scene transition when exiting.
    final boolean mHasSceneTransition;
    // Whether the app has opt-in enableOnBackInvokedCallback.
    final boolean mOptInOnBackInvoked;

    // Whether the ActivityEmbedding is enabled on the app.
    private final boolean mAppActivityEmbeddingSplitsEnabled;
@@ -2234,6 +2237,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
            // No such property name.
        }
        mAppActivityEmbeddingSplitsEnabled = appActivityEmbeddingEnabled;

        mOptInOnBackInvoked = WindowOnBackInvokedDispatcher
                .isOnBackInvokedCallbackEnabled(info, info.applicationInfo,
                        () -> ent != null ? ent.array : null, false);
    }

    /**
+63 −7
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;

import android.os.Trace;
import android.util.ArrayMap;
import android.view.WindowManager;
import android.window.TaskSnapshot;

@@ -80,6 +81,7 @@ class SnapshotController {
        if (!isTransitionOpen && !isTransitionClose && type < TRANSIT_FIRST_CUSTOM) {
            return;
        }
        ActivitiesByTask activityTargets = null;
        for (int i = changeInfos.size() - 1; i >= 0; --i) {
            Transition.ChangeInfo info = changeInfos.get(i);
            // Intentionally skip record snapshot for changes originated from PiP.
@@ -98,14 +100,68 @@ class SnapshotController {
                final TaskFragment tf = info.mContainer.asTaskFragment();
                final ActivityRecord ar = tf != null ? tf.getTopMostActivity()
                        : info.mContainer.asActivityRecord();
                if (ar != null && !ar.isVisibleRequested() && ar.getTask().isVisibleRequested()) {
                    final WindowState mainWindow = ar.findMainWindow(false);
                    // Only capture activity snapshot if this app has adapted to back predict
                    if (mainWindow != null
                            && mainWindow.getOnBackInvokedCallbackInfo() != null
                            && mainWindow.getOnBackInvokedCallbackInfo().isSystemCallback()) {
                        mActivitySnapshotController.recordSnapshot(ar);
                if (ar != null && ar.getTask().isVisibleRequested()) {
                    if (activityTargets == null) {
                        activityTargets = new ActivitiesByTask();
                    }
                    activityTargets.put(ar);
                }
            }
        }
        if (activityTargets != null) {
            activityTargets.recordSnapshot(mActivitySnapshotController);
        }
    }

    private static class ActivitiesByTask {
        final ArrayMap<Task, OpenCloseActivities> mActivitiesMap = new ArrayMap<>();

        void put(ActivityRecord ar) {
            OpenCloseActivities activities = mActivitiesMap.get(ar.getTask());
            if (activities == null) {
                activities = new OpenCloseActivities();
                mActivitiesMap.put(ar.getTask(), activities);
            }
            activities.add(ar);
        }

        void recordSnapshot(ActivitySnapshotController controller) {
            for (int i = mActivitiesMap.size() - 1; i >= 0; i--) {
                final OpenCloseActivities pair = mActivitiesMap.valueAt(i);
                pair.recordSnapshot(controller);
            }
        }

        static class OpenCloseActivities {
            final ArrayList<ActivityRecord> mOpenActivities = new ArrayList<>();
            final ArrayList<ActivityRecord> mCloseActivities = new ArrayList<>();

            void add(ActivityRecord ar) {
                if (ar.isVisibleRequested()) {
                    mOpenActivities.add(ar);
                } else {
                    mCloseActivities.add(ar);
                }
            }

            boolean allOpensOptInOnBackInvoked() {
                if (mOpenActivities.isEmpty()) {
                    return false;
                }
                for (int i = mOpenActivities.size() - 1; i >= 0; --i) {
                    if (!mOpenActivities.get(i).mOptInOnBackInvoked) {
                        return false;
                    }
                }
                return true;
            }

            void recordSnapshot(ActivitySnapshotController controller) {
                if (!allOpensOptInOnBackInvoked() || mCloseActivities.isEmpty()) {
                    return;
                }
                for (int i = mCloseActivities.size() - 1; i >= 0; --i) {
                    controller.recordSnapshot(mCloseActivities.get(i));
                }
            }
        }