Loading core/java/android/window/WindowOnBackInvokedDispatcher.java +74 −83 Original line number Diff line number Diff line Loading @@ -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}. Loading Loading @@ -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()); } /** Loading Loading @@ -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 Loading Loading @@ -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." Loading @@ -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; } Loading @@ -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. Loading @@ -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); Loading @@ -533,9 +527,6 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { requestsPredictiveBack = windowSwipeToDismiss; } } return requestsPredictiveBack; } } } services/core/java/com/android/server/wm/ActivityRecord.java +7 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -978,6 +979,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; Loading Loading @@ -2231,6 +2234,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); } /** Loading services/core/java/com/android/server/wm/SnapshotController.java +63 −7 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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. Loading @@ -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)); } } } Loading Loading
core/java/android/window/WindowOnBackInvokedDispatcher.java +74 −83 Original line number Diff line number Diff line Loading @@ -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}. Loading Loading @@ -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()); } /** Loading Loading @@ -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 Loading Loading @@ -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." Loading @@ -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; } Loading @@ -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. Loading @@ -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); Loading @@ -533,9 +527,6 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { requestsPredictiveBack = windowSwipeToDismiss; } } return requestsPredictiveBack; } } }
services/core/java/com/android/server/wm/ActivityRecord.java +7 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -978,6 +979,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; Loading Loading @@ -2231,6 +2234,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); } /** Loading
services/core/java/com/android/server/wm/SnapshotController.java +63 −7 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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. Loading @@ -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)); } } } Loading