Merge "Add orientation listener to all biometric overlays." into sc-dev am: b172012858 am: 9f2f76aad7

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/15237859

Change-Id: I22f40286852ad3346033adef3273d040f4357cdd
diff --git a/Android.bp b/Android.bp
index 157bdcb..fb522973 100644
--- a/Android.bp
+++ b/Android.bp
@@ -112,6 +112,7 @@
         ":framework_native_aidl",
         ":gatekeeper_aidl",
         ":gsiservice_aidl",
+        ":guiconstants_aidl",
         ":idmap2_aidl",
         ":idmap2_core_aidl",
         ":incidentcompanion_aidl",
diff --git a/core/api/current.txt b/core/api/current.txt
index cfc4ea9..b0a95c4 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -31072,8 +31072,8 @@
     ctor public Environment();
     method public static java.io.File getDataDirectory();
     method public static java.io.File getDownloadCacheDirectory();
-    method @Deprecated public static java.io.File getExternalStorageDirectory();
-    method @Deprecated public static java.io.File getExternalStoragePublicDirectory(String);
+    method public static java.io.File getExternalStorageDirectory();
+    method public static java.io.File getExternalStoragePublicDirectory(String);
     method public static String getExternalStorageState();
     method public static String getExternalStorageState(java.io.File);
     method @NonNull public static java.io.File getRootDirectory();
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index dd7c6db..2eafa93 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1281,6 +1281,12 @@
 
 package android.inputmethodservice {
 
+  public abstract class AbstractInputMethodService extends android.window.WindowProviderService implements android.view.KeyEvent.Callback {
+    method public final int getInitialDisplayId();
+    method @Nullable public final android.os.Bundle getWindowContextOptions();
+    method public final int getWindowType();
+  }
+
   @UiContext public class InputMethodService extends android.inputmethodservice.AbstractInputMethodService {
     field public static final long FINISH_INPUT_NO_FALLBACK_CONNECTION = 156215187L; // 0x94fa793L
   }
@@ -2120,6 +2126,7 @@
   public final class DeviceConfig {
     field public static final String NAMESPACE_ALARM_MANAGER = "alarm_manager";
     field public static final String NAMESPACE_ANDROID = "android";
+    field public static final String NAMESPACE_APP_COMPAT_OVERRIDES = "app_compat_overrides";
     field public static final String NAMESPACE_CONSTRAIN_DISPLAY_APIS = "constrain_display_apis";
     field public static final String NAMESPACE_DEVICE_IDLE = "device_idle";
     field public static final String NAMESPACE_JOB_SCHEDULER = "jobscheduler";
@@ -3256,6 +3263,7 @@
   @UiContext public abstract class WindowProviderService extends android.app.Service {
     ctor public WindowProviderService();
     method public final void attachToWindowToken(@NonNull android.os.IBinder);
+    method @NonNull public int getInitialDisplayId();
     method @Nullable public android.os.Bundle getWindowContextOptions();
     method public abstract int getWindowType();
   }
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index b31d8f70..765a2ba 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -16,6 +16,8 @@
 
 package android.accessibilityservice;
 
+import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
+
 import android.accessibilityservice.GestureDescription.MotionEventGenerator;
 import android.annotation.CallbackExecutor;
 import android.annotation.ColorInt;
@@ -27,6 +29,7 @@
 import android.app.Service;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
+import android.content.ContextWrapper;
 import android.content.Intent;
 import android.content.pm.ParceledListSlice;
 import android.graphics.Bitmap;
@@ -36,6 +39,7 @@
 import android.hardware.HardwareBuffer;
 import android.hardware.display.DisplayManager;
 import android.os.Build;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -961,30 +965,31 @@
         }
     }
 
+    @NonNull
     @Override
     public Context createDisplayContext(Display display) {
-        final Context context = super.createDisplayContext(display);
-        final int displayId = display.getDisplayId();
-        setDefaultTokenInternal(context, displayId);
-        return context;
+        return new AccessibilityContext(super.createDisplayContext(display), mConnectionId);
     }
 
-    private void setDefaultTokenInternal(Context context, int displayId) {
-        final WindowManagerImpl wm = (WindowManagerImpl) context.getSystemService(WINDOW_SERVICE);
-        final IAccessibilityServiceConnection connection =
-                AccessibilityInteractionClient.getInstance(this).getConnection(mConnectionId);
-        IBinder token = null;
-        if (connection != null) {
-            synchronized (mLock) {
-                try {
-                    token = connection.getOverlayWindowToken(displayId);
-                } catch (RemoteException re) {
-                    Log.w(LOG_TAG, "Failed to get window token", re);
-                    re.rethrowFromSystemServer();
-                }
-            }
-            wm.setDefaultToken(token);
+    @NonNull
+    @Override
+    public Context createWindowContext(int type, @Nullable Bundle options) {
+        final Context context = super.createWindowContext(type, options);
+        if (type != TYPE_ACCESSIBILITY_OVERLAY) {
+            return context;
         }
+        return new AccessibilityContext(context, mConnectionId);
+    }
+
+    @NonNull
+    @Override
+    public Context createWindowContext(@NonNull Display display, int type,
+            @Nullable Bundle options) {
+        final Context context = super.createWindowContext(display, type, options);
+        if (type != TYPE_ACCESSIBILITY_OVERLAY) {
+            return context;
+        }
+        return new AccessibilityContext(context, mConnectionId);
     }
 
     /**
@@ -2069,6 +2074,10 @@
         if (WINDOW_SERVICE.equals(name)) {
             if (mWindowManager == null) {
                 mWindowManager = (WindowManager) getBaseContext().getSystemService(name);
+                final WindowManagerImpl wm = (WindowManagerImpl) mWindowManager;
+                // Set e default token obtained from the connection to ensure client could use
+                // accessibility overlay.
+                wm.setDefaultToken(mWindowToken);
             }
             return mWindowManager;
         }
@@ -2177,8 +2186,10 @@
 
                 // The client may have already obtained the window manager, so
                 // update the default token on whatever manager we gave them.
-                final WindowManagerImpl wm = (WindowManagerImpl) getSystemService(WINDOW_SERVICE);
-                wm.setDefaultToken(windowToken);
+                if (mWindowManager != null) {
+                    final WindowManagerImpl wm = (WindowManagerImpl) mWindowManager;
+                    wm.setDefaultToken(mWindowToken);
+                }
             }
 
             @Override
@@ -2675,4 +2686,58 @@
             }
         }
     }
+
+    private static class AccessibilityContext extends ContextWrapper {
+        private final int mConnectionId;
+
+        private AccessibilityContext(Context base, int connectionId) {
+            super(base);
+            mConnectionId = connectionId;
+            setDefaultTokenInternal(this, getDisplayId());
+        }
+
+        @NonNull
+        @Override
+        public Context createDisplayContext(Display display) {
+            return new AccessibilityContext(super.createDisplayContext(display), mConnectionId);
+        }
+
+        @NonNull
+        @Override
+        public Context createWindowContext(int type, @Nullable Bundle options) {
+            final Context context = super.createWindowContext(type, options);
+            if (type != TYPE_ACCESSIBILITY_OVERLAY) {
+                return context;
+            }
+            return new AccessibilityContext(context, mConnectionId);
+        }
+
+        @NonNull
+        @Override
+        public Context createWindowContext(@NonNull Display display, int type,
+                @Nullable Bundle options) {
+            final Context context = super.createWindowContext(display, type, options);
+            if (type != TYPE_ACCESSIBILITY_OVERLAY) {
+                return context;
+            }
+            return new AccessibilityContext(context, mConnectionId);
+        }
+
+        private void setDefaultTokenInternal(Context context, int displayId) {
+            final WindowManagerImpl wm = (WindowManagerImpl) context.getSystemService(
+                    WINDOW_SERVICE);
+            final IAccessibilityServiceConnection connection =
+                    AccessibilityInteractionClient.getConnection(mConnectionId);
+            IBinder token = null;
+            if (connection != null) {
+                try {
+                    token = connection.getOverlayWindowToken(displayId);
+                } catch (RemoteException re) {
+                    Log.w(LOG_TAG, "Failed to get window token", re);
+                    re.rethrowFromSystemServer();
+                }
+                wm.setDefaultToken(token);
+            }
+        }
+    }
 }
diff --git a/core/java/android/accessibilityservice/AccessibilityTrace.java b/core/java/android/accessibilityservice/AccessibilityTrace.java
new file mode 100644
index 0000000..f28015a
--- /dev/null
+++ b/core/java/android/accessibilityservice/AccessibilityTrace.java
@@ -0,0 +1,216 @@
+/**
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.accessibilityservice;
+
+import java.util.AbstractMap;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Interface to log accessibility trace.
+ *
+ * @hide
+ */
+public interface AccessibilityTrace {
+    String NAME_ACCESSIBILITY_SERVICE_CONNECTION = "IAccessibilityServiceConnection";
+    String NAME_ACCESSIBILITY_SERVICE_CLIENT = "IAccessibilityServiceClient";
+    String NAME_ACCESSIBILITY_MANAGER = "IAccessibilityManager";
+    String NAME_ACCESSIBILITY_MANAGER_CLIENT = "IAccessibilityManagerClient";
+    String NAME_ACCESSIBILITY_INTERACTION_CONNECTION = "IAccessibilityInteractionConnection";
+    String NAME_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK =
+            "IAccessibilityInteractionConnectionCallback";
+    String NAME_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK = "IRemoteMagnificationAnimationCallback";
+    String NAME_WINDOW_MAGNIFICATION_CONNECTION = "IWindowMagnificationConnection";
+    String NAME_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK = "IWindowMagnificationConnectionCallback";
+    String NAME_WINDOW_MANAGER_INTERNAL = "WindowManagerInternal";
+    String NAME_WINDOWS_FOR_ACCESSIBILITY_CALLBACK = "WindowsForAccessibilityCallback";
+    String NAME_MAGNIFICATION_CALLBACK = "MagnificationCallbacks";
+    String NAME_INPUT_FILTER = "InputFilter";
+    String NAME_GESTURE = "Gesture";
+    String NAME_ACCESSIBILITY_SERVICE = "AccessibilityService";
+    String NAME_PACKAGE_BROADCAST_RECEIVER = "PMBroadcastReceiver";
+    String NAME_USER_BROADCAST_RECEIVER = "UserBroadcastReceiver";
+    String NAME_FINGERPRINT = "FingerprintGesture";
+    String NAME_ACCESSIBILITY_INTERACTION_CLIENT = "AccessibilityInteractionClient";
+
+    String NAME_ALL_LOGGINGS = "AllLoggings";
+    String NAME_NONE = "None";
+
+    long FLAGS_ACCESSIBILITY_SERVICE_CONNECTION = 0x0000000000000001L;
+    long FLAGS_ACCESSIBILITY_SERVICE_CLIENT = 0x0000000000000002L;
+    long FLAGS_ACCESSIBILITY_MANAGER = 0x0000000000000004L;
+    long FLAGS_ACCESSIBILITY_MANAGER_CLIENT = 0x0000000000000008L;
+    long FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION = 0x0000000000000010L;
+    long FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK = 0x0000000000000020L;
+    long FLAGS_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK = 0x0000000000000040L;
+    long FLAGS_WINDOW_MAGNIFICATION_CONNECTION = 0x0000000000000080L;
+    long FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK = 0x0000000000000100L;
+    long FLAGS_WINDOW_MANAGER_INTERNAL = 0x0000000000000200L;
+    long FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK = 0x0000000000000400L;
+    long FLAGS_MAGNIFICATION_CALLBACK = 0x0000000000000800L;
+    long FLAGS_INPUT_FILTER = 0x0000000000001000L;
+    long FLAGS_GESTURE = 0x0000000000002000L;
+    long FLAGS_ACCESSIBILITY_SERVICE = 0x0000000000004000L;
+    long FLAGS_PACKAGE_BROADCAST_RECEIVER = 0x0000000000008000L;
+    long FLAGS_USER_BROADCAST_RECEIVER = 0x0000000000010000L;
+    long FLAGS_FINGERPRINT = 0x0000000000020000L;
+    long FLAGS_ACCESSIBILITY_INTERACTION_CLIENT = 0x0000000000040000L;
+
+    long FLAGS_LOGGING_NONE = 0x0000000000000000L;
+    long FLAGS_LOGGING_ALL = 0xFFFFFFFFFFFFFFFFL;
+
+    long FLAGS_ACCESSIBILITY_MANAGER_CLIENT_STATES = FLAGS_ACCESSIBILITY_INTERACTION_CLIENT
+            | FLAGS_ACCESSIBILITY_SERVICE
+            | FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION
+            | FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK;
+
+    Map<String, Long> sNamesToFlags = Map.ofEntries(
+            new AbstractMap.SimpleEntry<String, Long>(
+                    NAME_ACCESSIBILITY_SERVICE_CONNECTION, FLAGS_ACCESSIBILITY_SERVICE_CONNECTION),
+            new AbstractMap.SimpleEntry<String, Long>(
+                    NAME_ACCESSIBILITY_SERVICE_CLIENT, FLAGS_ACCESSIBILITY_SERVICE_CLIENT),
+            new AbstractMap.SimpleEntry<String, Long>(
+                    NAME_ACCESSIBILITY_MANAGER, FLAGS_ACCESSIBILITY_MANAGER),
+            new AbstractMap.SimpleEntry<String, Long>(
+                    NAME_ACCESSIBILITY_MANAGER_CLIENT, FLAGS_ACCESSIBILITY_MANAGER_CLIENT),
+            new AbstractMap.SimpleEntry<String, Long>(
+                    NAME_ACCESSIBILITY_INTERACTION_CONNECTION,
+                    FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION),
+            new AbstractMap.SimpleEntry<String, Long>(
+                    NAME_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK,
+                    FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK),
+            new AbstractMap.SimpleEntry<String, Long>(
+                    NAME_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK,
+                    FLAGS_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK),
+            new AbstractMap.SimpleEntry<String, Long>(
+                    NAME_WINDOW_MAGNIFICATION_CONNECTION, FLAGS_WINDOW_MAGNIFICATION_CONNECTION),
+            new AbstractMap.SimpleEntry<String, Long>(
+                    NAME_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK,
+                    FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK),
+            new AbstractMap.SimpleEntry<String, Long>(
+                    NAME_WINDOW_MANAGER_INTERNAL, FLAGS_WINDOW_MANAGER_INTERNAL),
+            new AbstractMap.SimpleEntry<String, Long>(
+                    NAME_WINDOWS_FOR_ACCESSIBILITY_CALLBACK,
+                    FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK),
+            new AbstractMap.SimpleEntry<String, Long>(
+                    NAME_MAGNIFICATION_CALLBACK, FLAGS_MAGNIFICATION_CALLBACK),
+            new AbstractMap.SimpleEntry<String, Long>(NAME_INPUT_FILTER, FLAGS_INPUT_FILTER),
+            new AbstractMap.SimpleEntry<String, Long>(NAME_GESTURE, FLAGS_GESTURE),
+            new AbstractMap.SimpleEntry<String, Long>(
+                    NAME_ACCESSIBILITY_SERVICE, FLAGS_ACCESSIBILITY_SERVICE),
+            new AbstractMap.SimpleEntry<String, Long>(
+                    NAME_PACKAGE_BROADCAST_RECEIVER, FLAGS_PACKAGE_BROADCAST_RECEIVER),
+            new AbstractMap.SimpleEntry<String, Long>(
+                    NAME_USER_BROADCAST_RECEIVER, FLAGS_USER_BROADCAST_RECEIVER),
+            new AbstractMap.SimpleEntry<String, Long>(NAME_FINGERPRINT, FLAGS_FINGERPRINT),
+            new AbstractMap.SimpleEntry<String, Long>(
+                    NAME_ACCESSIBILITY_INTERACTION_CLIENT, FLAGS_ACCESSIBILITY_INTERACTION_CLIENT),
+            new AbstractMap.SimpleEntry<String, Long>(NAME_NONE, FLAGS_LOGGING_NONE),
+            new AbstractMap.SimpleEntry<String, Long>(NAME_ALL_LOGGINGS, FLAGS_LOGGING_ALL));
+
+    /**
+     * Get the flags of the logging types by the given names.
+     * The names list contains logging type names in lower case.
+     */
+    static long getLoggingFlagsFromNames(List<String> names) {
+        long types = FLAGS_LOGGING_NONE;
+        for (String name : names) {
+            long flag = sNamesToFlags.get(name);
+            types |= flag;
+        }
+        return types;
+    }
+
+    /**
+     * Get the list of the names of logging types by the given flags.
+     */
+    static List<String> getNamesOfLoggingTypes(long flags) {
+        List<String> list = new ArrayList<String>();
+
+        for (Map.Entry<String, Long> entry : sNamesToFlags.entrySet()) {
+            if ((entry.getValue() & flags) != FLAGS_LOGGING_NONE) {
+                list.add(entry.getKey());
+            }
+        }
+
+        return list;
+    }
+
+    /**
+     * Whether the trace is enabled for any logging type.
+     */
+    boolean isA11yTracingEnabled();
+
+    /**
+     * Whether the trace is enabled for any of the given logging type.
+     */
+    boolean isA11yTracingEnabledForTypes(long typeIdFlags);
+
+    /**
+     * Get trace state to be sent to AccessibilityManager.
+     */
+    int getTraceStateForAccessibilityManagerClientState();
+
+    /**
+     * Start tracing for the given logging types.
+     */
+    void startTrace(long flagss);
+
+    /**
+     * Stop tracing.
+     */
+    void stopTrace();
+
+    /**
+     * Log one trace entry.
+     * @param where A string to identify this log entry, which can be used to search through the
+     *        tracing file.
+     * @param loggingFlags Flags to identify which logging types this entry belongs to. This
+     *        can be used to filter the log entries when generating tracing file.
+     */
+    void logTrace(String where, long loggingFlags);
+
+    /**
+     * Log one trace entry.
+     * @param where A string to identify this log entry, which can be used to filter/search
+     *        through the tracing file.
+     * @param loggingFlags Flags to identify which logging types this entry belongs to. This
+     *        can be used to filter the log entries when generating tracing file.
+     * @param callingParams The parameters for the method to be logged.
+     */
+    void logTrace(String where, long loggingFlags, String callingParams);
+
+    /**
+     * Log one trace entry. Accessibility services using AccessibilityInteractionClient to
+     * make screen content related requests use this API to log entry when receive callback.
+     * @param timestamp The timestamp when a callback is received.
+     * @param where A string to identify this log entry, which can be used to filter/search
+     *        through the tracing file.
+     * @param loggingFlags Flags to identify which logging types this entry belongs to. This
+     *        can be used to filter the log entries when generating tracing file.
+     * @param callingParams The parameters for the callback.
+     * @param processId The process id of the calling component.
+     * @param threadId The threadId of the calling component.
+     * @param callingUid The calling uid of the callback.
+     * @param callStack The call stack of the callback.
+     * @param ignoreStackElements ignore these call stack element
+     */
+    void logTrace(long timestamp, String where, long loggingFlags, String callingParams,
+            int processId, long threadId, int callingUid, StackTraceElement[] callStack,
+            Set<String> ignoreStackElements);
+}
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index 923b6f4..1e76bbf 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -118,6 +118,6 @@
 
     void setFocusAppearance(int strokeWidth, int color);
 
-    oneway void logTrace(long timestamp, String where, String callingParams, int processId,
-        long threadId, int callingUid, in Bundle serializedCallingStackInBundle);
+    oneway void logTrace(long timestamp, String where, long loggingTypes, String callingParams,
+        int processId, long threadId, int callingUid, in Bundle serializedCallingStackInBundle);
 }
diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java
index 4a7fcd2..a836625 100644
--- a/core/java/android/app/ActivityTaskManager.java
+++ b/core/java/android/app/ActivityTaskManager.java
@@ -476,6 +476,19 @@
     }
 
     /**
+     * Detaches the navigation bar from the app it was attached to during a transition.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS)
+    public void detachNavigationBarFromApp(@NonNull IBinder transition) {
+        try {
+            getService().detachNavigationBarFromApp(transition);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Information you can retrieve about a root task in the system.
      * @hide
      */
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 3915abe..f6bcfd6 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -18,7 +18,6 @@
 
 import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
 import static android.app.ConfigurationController.createNewConfigAndUpdateIfNotNull;
-import static android.app.ConfigurationController.freeTextLayoutCachesIfNeeded;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.app.servertransaction.ActivityLifecycleItem.ON_CREATE;
@@ -31,6 +30,10 @@
 import static android.content.ContentResolver.DEPRECATE_DATA_COLUMNS;
 import static android.content.ContentResolver.DEPRECATE_DATA_PREFIX;
 import static android.view.Display.INVALID_DISPLAY;
+import static android.window.ConfigurationHelper.diffPublicWithSizeBuckets;
+import static android.window.ConfigurationHelper.freeTextLayoutCachesIfNeeded;
+import static android.window.ConfigurationHelper.isDifferentDisplay;
+import static android.window.ConfigurationHelper.shouldUpdateResources;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
 
@@ -88,10 +91,8 @@
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.HardwareRenderer;
-import android.graphics.Rect;
 import android.graphics.Typeface;
 import android.hardware.display.DisplayManagerGlobal;
-import android.inputmethodservice.InputMethodService;
 import android.media.MediaFrameworkInitializer;
 import android.media.MediaFrameworkPlatformInitializer;
 import android.media.MediaServiceManager;
@@ -183,6 +184,7 @@
 import android.window.SizeConfigurationBuckets;
 import android.window.SplashScreen;
 import android.window.SplashScreenView;
+import android.window.WindowProviderService;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -5428,6 +5430,12 @@
                     // behave properly when activity is relaunching.
                     r.window.clearContentView();
                 } else {
+                    final ViewRootImpl viewRoot = v.getViewRootImpl();
+                    if (viewRoot != null) {
+                        // Clear the callback to avoid the destroyed activity from receiving
+                        // configuration changes that are no longer effective.
+                        viewRoot.setActivityConfigCallback(null);
+                    }
                     wm.removeViewImmediate(v);
                 }
             }
@@ -5751,7 +5759,7 @@
     }
 
     @Override
-    public ArrayList<ComponentCallbacks2> collectComponentCallbacks(boolean includeActivities) {
+    public ArrayList<ComponentCallbacks2> collectComponentCallbacks(boolean includeUiContexts) {
         ArrayList<ComponentCallbacks2> callbacks
                 = new ArrayList<ComponentCallbacks2>();
 
@@ -5760,7 +5768,7 @@
             for (int i=0; i<NAPP; i++) {
                 callbacks.add(mAllApplications.get(i));
             }
-            if (includeActivities) {
+            if (includeUiContexts) {
                 for (int i = mActivities.size() - 1; i >= 0; i--) {
                     final Activity a = mActivities.valueAt(i).activity;
                     if (a != null && !a.mFinished) {
@@ -5770,11 +5778,12 @@
             }
             final int NSVC = mServices.size();
             for (int i=0; i<NSVC; i++) {
-                final ComponentCallbacks2 serviceComp = mServices.valueAt(i);
-                if (serviceComp instanceof InputMethodService) {
-                    mHasImeComponent = true;
+                final Service service = mServices.valueAt(i);
+                // If {@code includeUiContext} is set to false, WindowProviderService should not be
+                // collected because WindowProviderService is a UI Context.
+                if (includeUiContexts || !(service instanceof WindowProviderService)) {
+                    callbacks.add(service);
                 }
-                callbacks.add(serviceComp);
             }
         }
         synchronized (mProviderMap) {
@@ -5829,35 +5838,25 @@
         // change callback, see also PinnedStackTests#testConfigurationChangeOrderDuringTransition
         handleWindowingModeChangeIfNeeded(activity, newConfig);
 
-        final boolean movedToDifferentDisplay = isDifferentDisplay(activity, displayId);
-        boolean shouldReportChange = false;
-        if (activity.mCurrentConfig == null) {
-            shouldReportChange = true;
-        } else {
-            // If the new config is the same as the config this Activity is already running with and
-            // the override config also didn't change, then don't bother calling
-            // onConfigurationChanged.
-            // TODO(b/173090263): Use diff instead after the improvement of AssetManager and
-            // ResourcesImpl constructions.
-            int diff = activity.mCurrentConfig.diffPublicOnly(newConfig);
-            final ActivityClientRecord cr = getActivityClient(activityToken);
-            diff = SizeConfigurationBuckets.filterDiff(diff, activity.mCurrentConfig, newConfig,
-                    cr != null ? cr.mSizeConfigurations : null);
-
-            if (diff == 0) {
-                if (!shouldUpdateWindowMetricsBounds(activity.mCurrentConfig, newConfig)
-                        && !movedToDifferentDisplay
-                        && mResourcesManager.isSameResourcesOverrideConfig(
-                                activityToken, amOverrideConfig)) {
-                    // Nothing significant, don't proceed with updating and reporting.
-                    return null;
-                }
-            } else if ((~activity.mActivityInfo.getRealConfigChanged() & diff) == 0) {
+        final boolean movedToDifferentDisplay = isDifferentDisplay(activity.getDisplayId(),
+                displayId);
+        final ActivityClientRecord r = mActivities.get(activityToken);
+        final int diff = diffPublicWithSizeBuckets(activity.mCurrentConfig,
+                newConfig, r != null ? r.mSizeConfigurations : null);
+        final boolean hasPublicConfigChange = diff != 0;
+        // TODO(b/173090263): Use diff instead after the improvement of AssetManager and
+        // ResourcesImpl constructions.
+        final boolean shouldUpdateResources = hasPublicConfigChange
+                || shouldUpdateResources(activityToken, activity.mCurrentConfig, newConfig,
+                amOverrideConfig, movedToDifferentDisplay, hasPublicConfigChange);
+        final boolean shouldReportChange = hasPublicConfigChange
                 // If this activity doesn't handle any of the config changes, then don't bother
                 // calling onConfigurationChanged. Otherwise, report to the activity for the
                 // changes.
-                shouldReportChange = true;
-            }
+                && (~activity.mActivityInfo.getRealConfigChanged() & diff) == 0;
+        // Nothing significant, don't proceed with updating and reporting.
+        if (!shouldUpdateResources) {
+            return null;
         }
 
         // Propagate the configuration change to ResourcesManager and Activity.
@@ -5908,26 +5907,6 @@
         return configToReport;
     }
 
-    // TODO(b/173090263): Remove this method after the improvement of AssetManager and ResourcesImpl
-    // constructions.
-    /**
-     * Returns {@code true} if the metrics reported by {@link android.view.WindowMetrics} APIs
-     * should be updated.
-     *
-     * @see WindowManager#getCurrentWindowMetrics()
-     * @see WindowManager#getMaximumWindowMetrics()
-     */
-    private static boolean shouldUpdateWindowMetricsBounds(@NonNull Configuration currentConfig,
-            @NonNull Configuration newConfig) {
-        final Rect currentBounds = currentConfig.windowConfiguration.getBounds();
-        final Rect newBounds = newConfig.windowConfiguration.getBounds();
-
-        final Rect currentMaxBounds = currentConfig.windowConfiguration.getMaxBounds();
-        final Rect newMaxBounds = newConfig.windowConfiguration.getMaxBounds();
-
-        return !currentBounds.equals(newBounds) || !currentMaxBounds.equals(newMaxBounds);
-    }
-
     public final void applyConfigurationToResources(Configuration config) {
         synchronized (mResourcesManager) {
             mResourcesManager.applyConfigurationToResources(config, null);
@@ -6071,7 +6050,8 @@
             // display.
             displayId = r.activity.getDisplayId();
         }
-        final boolean movedToDifferentDisplay = isDifferentDisplay(r.activity, displayId);
+        final boolean movedToDifferentDisplay = isDifferentDisplay(
+                r.activity.getDisplayId(), displayId);
         if (r.overrideConfig != null && !r.overrideConfig.isOtherSeqNewer(overrideConfig)
                 && !movedToDifferentDisplay) {
             if (DEBUG_CONFIGURATION) {
@@ -6107,14 +6087,6 @@
         mSomeActivitiesChanged = true;
     }
 
-    /**
-     * Checks if the display id of activity is different from the given one. Note that
-     * {@link Display#INVALID_DISPLAY} means no difference.
-     */
-    private static boolean isDifferentDisplay(@NonNull Activity activity, int displayId) {
-        return displayId != INVALID_DISPLAY && displayId != activity.getDisplayId();
-    }
-
     final void handleProfilerControl(boolean start, ProfilerInfo profilerInfo, int profileType) {
         if (start) {
             try {
@@ -6294,7 +6266,7 @@
 
     final void handleLowMemory() {
         final ArrayList<ComponentCallbacks2> callbacks =
-                collectComponentCallbacks(true /* includeActivities */);
+                collectComponentCallbacks(true /* includeUiContexts */);
 
         final int N = callbacks.size();
         for (int i=0; i<N; i++) {
@@ -6327,7 +6299,7 @@
         }
 
         final ArrayList<ComponentCallbacks2> callbacks =
-                collectComponentCallbacks(true /* includeActivities */);
+                collectComponentCallbacks(true /* includeUiContexts */);
 
         final int N = callbacks.size();
         for (int i = 0; i < N; i++) {
@@ -7546,12 +7518,6 @@
 
         ViewRootImpl.ConfigChangedCallback configChangedCallback = (Configuration globalConfig) -> {
             synchronized (mResourcesManager) {
-                // TODO (b/135719017): Temporary log for debugging IME service.
-                if (Build.IS_DEBUGGABLE && mHasImeComponent) {
-                    Log.d(TAG, "ViewRootImpl.ConfigChangedCallback for IME, "
-                            + "config=" + globalConfig);
-                }
-
                 // We need to apply this change to the resources immediately, because upon returning
                 // the view hierarchy will be informed about it.
                 if (mResourcesManager.applyConfigurationToResources(globalConfig,
@@ -7906,11 +7872,6 @@
         return mDensityCompatMode;
     }
 
-    @Override
-    public boolean hasImeComponent() {
-        return mHasImeComponent;
-    }
-
     // ------------------ Regular JNI ------------------------
     private native void nPurgePendingResources();
     private native void nDumpGraphicsInfo(FileDescriptor fd);
diff --git a/core/java/android/app/ActivityThreadInternal.java b/core/java/android/app/ActivityThreadInternal.java
index d91933c..bc698f6 100644
--- a/core/java/android/app/ActivityThreadInternal.java
+++ b/core/java/android/app/ActivityThreadInternal.java
@@ -32,11 +32,9 @@
 
     boolean isInDensityCompatMode();
 
-    boolean hasImeComponent();
-
     boolean isCachedProcessState();
 
     Application getApplication();
 
-    ArrayList<ComponentCallbacks2> collectComponentCallbacks(boolean includeActivities);
+    ArrayList<ComponentCallbacks2> collectComponentCallbacks(boolean includeUiContexts);
 }
diff --git a/core/java/android/app/ConfigurationController.java b/core/java/android/app/ConfigurationController.java
index f79e078..8637e31 100644
--- a/core/java/android/app/ConfigurationController.java
+++ b/core/java/android/app/ConfigurationController.java
@@ -17,24 +17,20 @@
 package android.app;
 
 import static android.app.ActivityThread.DEBUG_CONFIGURATION;
+import static android.window.ConfigurationHelper.freeTextLayoutCachesIfNeeded;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.ComponentCallbacks2;
 import android.content.Context;
-import android.content.pm.ActivityInfo;
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
-import android.graphics.Canvas;
 import android.graphics.HardwareRenderer;
-import android.inputmethodservice.InputMethodService;
-import android.os.Build;
 import android.os.LocaleList;
 import android.os.Trace;
 import android.util.DisplayMetrics;
-import android.util.Log;
 import android.util.Slog;
 import android.view.ContextThemeWrapper;
 import android.view.WindowManagerGlobal;
@@ -169,12 +165,7 @@
                 mPendingConfiguration = null;
             }
 
-            final boolean hasIme = mActivityThread.hasImeComponent();
             if (config == null) {
-                // TODO (b/135719017): Temporary log for debugging IME service.
-                if (Build.IS_DEBUGGABLE && hasIme) {
-                    Log.w(TAG, "handleConfigurationChanged for IME app but config is null");
-                }
                 return;
             }
 
@@ -205,12 +196,6 @@
                 mConfiguration = new Configuration();
             }
             if (!mConfiguration.isOtherSeqNewer(config) && compat == null) {
-                // TODO (b/135719017): Temporary log for debugging IME service.
-                if (Build.IS_DEBUGGABLE && hasIme) {
-                    Log.w(TAG, "handleConfigurationChanged for IME app but config seq is obsolete "
-                            + ", config=" + config
-                            + ", mConfiguration=" + mConfiguration);
-                }
                 return;
             }
 
@@ -228,7 +213,7 @@
         }
 
         final ArrayList<ComponentCallbacks2> callbacks =
-                mActivityThread.collectComponentCallbacks(false /* includeActivities */);
+                mActivityThread.collectComponentCallbacks(false /* includeUiContexts */);
 
         freeTextLayoutCachesIfNeeded(configDiff);
 
@@ -238,13 +223,6 @@
                 ComponentCallbacks2 cb = callbacks.get(i);
                 if (!equivalent) {
                     performConfigurationChanged(cb, config);
-                } else {
-                    // TODO (b/135719017): Temporary log for debugging IME service.
-                    if (Build.IS_DEBUGGABLE && cb instanceof InputMethodService) {
-                        Log.w(TAG, "performConfigurationChanged didn't callback to IME "
-                                + ", configDiff=" + configDiff
-                                + ", mConfiguration=" + mConfiguration);
-                    }
                 }
             }
         }
@@ -326,16 +304,4 @@
         return newConfig;
     }
 
-    /** Ask test layout engine to free its caches if there is a locale change. */
-    static void freeTextLayoutCachesIfNeeded(int configDiff) {
-        if (configDiff != 0) {
-            boolean hasLocaleConfigChange = ((configDiff & ActivityInfo.CONFIG_LOCALE) != 0);
-            if (hasLocaleConfigChange) {
-                Canvas.freeTextLayoutCaches();
-                if (DEBUG_CONFIGURATION) {
-                    Slog.v(TAG, "Cleared TextLayout Caches");
-                }
-            }
-        }
-    }
 }
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index f52fdc5..249a606 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -2721,10 +2721,13 @@
         // need to override their display in ResourcesManager.
         baseContext.mForceDisplayOverrideInResources = false;
         baseContext.mContextType = CONTEXT_TYPE_WINDOW_CONTEXT;
-        baseContext.mDisplay = display;
 
         final Resources windowContextResources = createWindowContextResources(baseContext);
         baseContext.setResources(windowContextResources);
+        // Associate the display with window context resources so that configuration update from
+        // the server side will also apply to the display's metrics.
+        baseContext.mDisplay = ResourcesManager.getInstance()
+                .getAdjustedDisplay(display.getDisplayId(), windowContextResources);
 
         return baseContext;
     }
@@ -3187,12 +3190,6 @@
     @UnsupportedAppUsage
     final void setOuterContext(@NonNull Context context) {
         mOuterContext = context;
-        // TODO(b/149463653): check if we still need this method after migrating IMS to
-        //  WindowContext.
-        if (mOuterContext.isUiContext() && mContextType <= CONTEXT_TYPE_DISPLAY_CONTEXT) {
-            mContextType = CONTEXT_TYPE_WINDOW_CONTEXT;
-            mIsConfigurationBasedContext = true;
-        }
     }
 
     @UnsupportedAppUsage
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 74d51a0..2be7803 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -330,4 +330,18 @@
      * When the Picture-in-picture state has changed.
      */
     void onPictureInPictureStateChanged(in PictureInPictureUiState pipState);
+
+    /**
+     * Re-attach navbar to the display during a recents transition.
+     * TODO(188595497): Remove this once navbar attachment is in shell.
+     */
+    void detachNavigationBarFromApp(in IBinder transition);
+
+    /**
+     * Marks a process as a delegate for the currently playing remote transition animation. This
+     * must be called from a process that is already a remote transition player or delegate. Any
+     * marked delegates are cleaned-up automatically at the end of the transition.
+     * @param caller is the IApplicationThread representing the calling process.
+     */
+    void setRunningRemoteTransitionDelegate(in IApplicationThread caller);
 }
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 20afffc..af75c69 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -693,7 +693,7 @@
      * @return true if activity resources override config matches the provided one or they are both
      *         null, false otherwise.
      */
-    boolean isSameResourcesOverrideConfig(@Nullable IBinder activityToken,
+    public boolean isSameResourcesOverrideConfig(@Nullable IBinder activityToken,
             @Nullable Configuration overrideConfig) {
         synchronized (mLock) {
             final ActivityResources activityResources
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 444cc4e..1569e60 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -17,6 +17,7 @@
 package android.app;
 
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -117,6 +118,12 @@
     public int displayId;
 
     /**
+     * The feature id of {@link com.android.server.wm.TaskDisplayArea} this task is associated with.
+     * @hide
+     */
+    public int displayAreaFeatureId = FEATURE_UNDEFINED;
+
+    /**
      * The recent activity values for the highest activity in the stack to have set the values.
      * {@link Activity#setTaskDescription(android.app.ActivityManager.TaskDescription)}.
      */
@@ -232,6 +239,12 @@
      */
     public boolean isVisible;
 
+    /**
+     * Whether this task is sleeping due to sleeping display.
+     * @hide
+     */
+    public boolean isSleeping;
+
     TaskInfo() {
         // Do nothing
     }
@@ -318,11 +331,10 @@
     }
 
     /**
-      * Returns {@code true} if parameters that are important for task organizers have changed
-      * and {@link com.android.server.wm.TaskOrginizerController} needs to notify listeners
-      * about that.
-      * @hide
-      */
+     * Returns {@code true} if the parameters that are important for task organizers are equal
+     * between this {@link TaskInfo} and {@param that}.
+     * @hide
+     */
     public boolean equalsForTaskOrganizer(@Nullable TaskInfo that) {
         if (that == null) {
             return false;
@@ -330,12 +342,14 @@
         return topActivityType == that.topActivityType
                 && isResizeable == that.isResizeable
                 && supportsMultiWindow == that.supportsMultiWindow
+                && displayAreaFeatureId == that.displayAreaFeatureId
                 && Objects.equals(positionInParent, that.positionInParent)
                 && Objects.equals(pictureInPictureParams, that.pictureInPictureParams)
                 && getWindowingMode() == that.getWindowingMode()
                 && Objects.equals(taskDescription, that.taskDescription)
                 && isFocused == that.isFocused
-                && isVisible == that.isVisible;
+                && isVisible == that.isVisible
+                && isSleeping == that.isSleeping;
     }
 
     /**
@@ -389,8 +403,10 @@
         parentTaskId = source.readInt();
         isFocused = source.readBoolean();
         isVisible = source.readBoolean();
+        isSleeping = source.readBoolean();
         topActivityInSizeCompat = source.readBoolean();
         mTopActivityLocusId = source.readTypedObject(LocusId.CREATOR);
+        displayAreaFeatureId = source.readInt();
     }
 
     /**
@@ -426,8 +442,10 @@
         dest.writeInt(parentTaskId);
         dest.writeBoolean(isFocused);
         dest.writeBoolean(isVisible);
+        dest.writeBoolean(isSleeping);
         dest.writeBoolean(topActivityInSizeCompat);
         dest.writeTypedObject(mTopActivityLocusId, flags);
+        dest.writeInt(displayAreaFeatureId);
     }
 
     @Override
@@ -453,8 +471,10 @@
                 + " parentTaskId=" + parentTaskId
                 + " isFocused=" + isFocused
                 + " isVisible=" + isVisible
+                + " isSleeping=" + isSleeping
                 + " topActivityInSizeCompat=" + topActivityInSizeCompat
-                + " locusId= " + mTopActivityLocusId
+                + " locusId=" + mTopActivityLocusId
+                + " displayAreaFeatureId=" + displayAreaFeatureId
                 + "}";
     }
 }
diff --git a/core/java/android/app/compat/PackageOverride.java b/core/java/android/app/compat/PackageOverride.java
index fad6cd3..ebc2945 100644
--- a/core/java/android/app/compat/PackageOverride.java
+++ b/core/java/android/app/compat/PackageOverride.java
@@ -24,6 +24,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
 
 /**
  * An app compat override applied to a given package and change id pairing.
@@ -139,6 +140,22 @@
 
     /** @hide */
     @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        PackageOverride that = (PackageOverride) o;
+        return mMinVersionCode == that.mMinVersionCode && mMaxVersionCode == that.mMaxVersionCode
+                && mEnabled == that.mEnabled;
+    }
+
+    /** @hide */
+    @Override
+    public int hashCode() {
+        return Objects.hash(mMinVersionCode, mMaxVersionCode, mEnabled);
+    }
+
+    /** @hide */
+    @Override
     public String toString() {
         if (mMinVersionCode == Long.MIN_VALUE && mMaxVersionCode == Long.MAX_VALUE) {
             return Boolean.toString(mEnabled);
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 2724166..a647983 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -3056,9 +3056,6 @@
                 return true;
             }
             return false;
-        } else if (profile == BluetoothProfile.LE_AUDIO) {
-            BluetoothLeAudio leAudio = new BluetoothLeAudio(context, listener, this);
-            return true;
         } else if (profile == BluetoothProfile.VOLUME_CONTROL) {
             BluetoothVolumeControl vcs = new BluetoothVolumeControl(context, listener, this);
             return true;
@@ -3151,10 +3148,6 @@
                 BluetoothHearingAid hearingAid = (BluetoothHearingAid) proxy;
                 hearingAid.close();
                 break;
-            case BluetoothProfile.LE_AUDIO:
-                BluetoothLeAudio leAudio = (BluetoothLeAudio) proxy;
-                leAudio.close();
-                break;
             case BluetoothProfile.VOLUME_CONTROL:
                 BluetoothVolumeControl vcs = (BluetoothVolumeControl) proxy;
                 vcs.close();
diff --git a/core/java/android/inputmethodservice/AbstractInputMethodService.java b/core/java/android/inputmethodservice/AbstractInputMethodService.java
index 3cd13a2..17d4ae6 100644
--- a/core/java/android/inputmethodservice/AbstractInputMethodService.java
+++ b/core/java/android/inputmethodservice/AbstractInputMethodService.java
@@ -18,16 +18,23 @@
 
 import android.annotation.MainThread;
 import android.annotation.NonNull;
-import android.app.Service;
+import android.annotation.Nullable;
+import android.content.Context;
 import android.content.Intent;
+import android.content.res.Configuration;
+import android.os.Bundle;
 import android.os.IBinder;
+import android.os.RemoteException;
 import android.util.proto.ProtoOutputStream;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputContentInfo;
 import android.view.inputmethod.InputMethod;
 import android.view.inputmethod.InputMethodSession;
+import android.window.WindowProviderService;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -44,9 +51,22 @@
  * implement.  This base class takes care of reporting your InputMethod from
  * the service when clients bind to it, but provides no standard implementation
  * of the InputMethod interface itself.  Derived classes must implement that
- * interface.
+ * interface.</p>
+ *
+ * <p>After {@link android.os.Build.VERSION_CODES#S}, the maximum possible area to show the soft
+ * input may not be the entire screen. For example, some devices may support to show the soft input
+ * on only half of screen.</p>
+ *
+ * <p>In that case, moving the soft input from one half screen to another will trigger a
+ * {@link android.content.res.Resources} update to match the new {@link Configuration} and
+ * this {@link AbstractInputMethodService} may also receive a
+ * {@link #onConfigurationChanged(Configuration)} callback if there's notable configuration changes
+ * </p>
+ *
+ * @see android.content.ComponentCallbacks#onConfigurationChanged(Configuration)
+ * @see Context#isUiContext Context#isUiContext to see the concept of UI Context.
  */
-public abstract class AbstractInputMethodService extends Service
+public abstract class AbstractInputMethodService extends WindowProviderService
         implements KeyEvent.Callback {
     private InputMethod mInputMethod;
     
@@ -272,9 +292,33 @@
     public void notifyUserActionIfNecessary() {
     }
 
+    // TODO(b/149463653): remove it in T. We missed the API deadline in S.
     /** @hide */
     @Override
     public final boolean isUiContext() {
         return true;
     }
+
+    /** @hide */
+    @Override
+    public final int getWindowType() {
+        return WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+    }
+
+    /** @hide */
+    @Override
+    @Nullable
+    public final Bundle getWindowContextOptions() {
+        return null;
+    }
+
+    /** @hide */
+    @Override
+    public final int getInitialDisplayId() {
+        try {
+            return WindowManagerGlobal.getWindowManagerService().getImeDisplayId();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index 9198eb7..89612fe 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -170,8 +170,8 @@
             case DO_INITIALIZE_INTERNAL: {
                 SomeArgs args = (SomeArgs) msg.obj;
                 try {
-                    inputMethod.initializeInternal((IBinder) args.arg1, msg.arg1,
-                            (IInputMethodPrivilegedOperations) args.arg2, (int) args.arg3);
+                    inputMethod.initializeInternal((IBinder) args.arg1,
+                            (IInputMethodPrivilegedOperations) args.arg2, msg.arg1);
                 } finally {
                     args.recycle();
                 }
@@ -279,11 +279,10 @@
 
     @BinderThread
     @Override
-    public void initializeInternal(IBinder token, int displayId,
-            IInputMethodPrivilegedOperations privOps, int configChanges) {
+    public void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privOps,
+            int configChanges) {
         mCaller.executeOrSendMessage(
-                mCaller.obtainMessageIOOO(DO_INITIALIZE_INTERNAL, displayId, token, privOps,
-                        configChanges));
+                mCaller.obtainMessageIOO(DO_INITIALIZE_INTERNAL, configChanges, token, privOps));
     }
 
     @BinderThread
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 881e0cf..42fa9fb 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -589,7 +589,7 @@
          */
         @MainThread
         @Override
-        public final void initializeInternal(@NonNull IBinder token, int displayId,
+        public final void initializeInternal(@NonNull IBinder token,
                 IInputMethodPrivilegedOperations privilegedOperations, int configChanges) {
             if (InputMethodPrivilegedOperationsRegistry.isRegistered(token)) {
                 Log.w(TAG, "The token has already registered, ignore this initialization.");
@@ -599,7 +599,6 @@
             mConfigTracker.onInitialize(configChanges);
             mPrivOps.set(privilegedOperations);
             InputMethodPrivilegedOperationsRegistry.put(token, mPrivOps);
-            updateInputMethodDisplay(displayId);
             attachToken(token);
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
@@ -629,29 +628,13 @@
                 throw new IllegalStateException(
                         "attachToken() must be called at most once. token=" + token);
             }
+            attachToWindowToken(token);
             mToken = token;
             mWindow.setToken(token);
         }
 
         /**
          * {@inheritDoc}
-         * @hide
-         */
-        @MainThread
-        @Override
-        public void updateInputMethodDisplay(int displayId) {
-            if (getDisplayId() == displayId) {
-                return;
-            }
-            // Update display for adding IME window to the right display.
-            // TODO(b/111364446) Need to address context lifecycle issue if need to re-create
-            // for update resources & configuration correctly when show soft input
-            // in non-default display.
-            updateDisplay(displayId);
-        }
-
-        /**
-         * {@inheritDoc}
          *
          * <p>Calls {@link InputMethodService#onBindInput()} when done.</p>
          */
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 2ed0bad..308e6d5 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -189,13 +189,11 @@
         }
 
         @UnsupportedAppUsage
-        @Deprecated
         public File getExternalStorageDirectory() {
             return getExternalDirs()[0];
         }
 
         @UnsupportedAppUsage
-        @Deprecated
         public File getExternalStoragePublicDirectory(String type) {
             return buildExternalStoragePublicDirs(type)[0];
         }
@@ -698,11 +696,7 @@
      *
      * @see #getExternalStorageState()
      * @see #isExternalStorageRemovable()
-     * @deprecated Alternatives such as {@link Context#getExternalFilesDir(String)},
-     *             {@link MediaStore}, or {@link Intent#ACTION_OPEN_DOCUMENT} offer better
-     *             performance.
      */
-    @Deprecated
     public static File getExternalStorageDirectory() {
         throwIfUserRequired();
         return sCurrentUser.getExternalDirs()[0];
@@ -1009,11 +1003,7 @@
      * @return Returns the File path for the directory. Note that this directory
      *         may not yet exist, so you must make sure it exists before using
      *         it such as with {@link File#mkdirs File.mkdirs()}.
-     * @deprecated Alternatives such as {@link Context#getExternalFilesDir(String)},
-     *             {@link MediaStore}, or {@link Intent#ACTION_OPEN_DOCUMENT} offer better
-     *             performance.
      */
-    @Deprecated
     public static File getExternalStoragePublicDirectory(String type) {
         throwIfUserRequired();
         return sCurrentUser.buildExternalStoragePublicDirs(type)[0];
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 431bf4c..e7e53b3 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -597,6 +597,14 @@
     @TestApi
     public static final String NAMESPACE_CONSTRAIN_DISPLAY_APIS = "constrain_display_apis";
 
+    /**
+     * Namespace for App Compat Overrides related features.
+     *
+     * @hide
+     */
+    @TestApi
+    public static final String NAMESPACE_APP_COMPAT_OVERRIDES = "app_compat_overrides";
+
     private static final Object sLock = new Object();
     @GuardedBy("sLock")
     private static ArrayMap<OnPropertiesChangedListener, Pair<String, Executor>> sListeners =
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 8021636..f4539c2 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -814,9 +814,10 @@
      * @param displayId The display associated with the window context
      * @param options A bundle used to pass window-related options and choose the right DisplayArea
      *
-     * @return {@code true} if the WindowContext is attached to the DisplayArea successfully.
+     * @return the DisplayArea's {@link android.app.res.Configuration} if the WindowContext is
+     * attached to the DisplayArea successfully. {@code null}, otherwise.
      */
-    boolean attachWindowContextToDisplayArea(IBinder clientToken, int type, int displayId,
+    Configuration attachWindowContextToDisplayArea(IBinder clientToken, int type, int displayId,
             in Bundle options);
 
     /**
@@ -865,4 +866,11 @@
     void unregisterCrossWindowBlurEnabledListener(ICrossWindowBlurEnabledListener listener);
 
     boolean isTaskSnapshotSupported();
+
+    /**
+     * Returns the preferred display ID to show software keyboard.
+     *
+     * @see android.window.WindowProviderService#getLaunchedDisplayId
+     */
+    int getImeDisplayId();
 }
diff --git a/core/java/android/view/InputWindowHandle.java b/core/java/android/view/InputWindowHandle.java
index 5a34a92..1cb1439 100644
--- a/core/java/android/view/InputWindowHandle.java
+++ b/core/java/android/view/InputWindowHandle.java
@@ -20,8 +20,8 @@
 
 import android.annotation.Nullable;
 import android.graphics.Region;
+import android.gui.TouchOcclusionMode;
 import android.os.IBinder;
-import android.os.TouchOcclusionMode;
 
 import java.lang.ref.WeakReference;
 
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 69ff64f..40942ea7 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -4008,6 +4008,22 @@
         public float orientation;
 
         /**
+         * The movement of x position of a motion event.
+         *
+         * @see MotionEvent#AXIS_RELATIVE_X
+         * @hide
+         */
+        public float relativeX;
+
+        /**
+         * The movement of y position of a motion event.
+         *
+         * @see MotionEvent#AXIS_RELATIVE_Y
+         * @hide
+         */
+        public float relativeY;
+
+        /**
          * Clears the contents of this object.
          * Resets all axes to zero.
          */
@@ -4023,6 +4039,8 @@
             toolMajor = 0;
             toolMinor = 0;
             orientation = 0;
+            relativeX = 0;
+            relativeY = 0;
         }
 
         /**
@@ -4053,6 +4071,8 @@
             toolMajor = other.toolMajor;
             toolMinor = other.toolMinor;
             orientation = other.orientation;
+            relativeX = other.relativeX;
+            relativeY = other.relativeY;
         }
 
         /**
@@ -4084,6 +4104,10 @@
                     return toolMinor;
                 case AXIS_ORIENTATION:
                     return orientation;
+                case AXIS_RELATIVE_X:
+                    return relativeX;
+                case AXIS_RELATIVE_Y:
+                    return relativeY;
                 default: {
                     if (axis < 0 || axis > 63) {
                         throw new IllegalArgumentException("Axis out of range.");
@@ -4137,6 +4161,12 @@
                 case AXIS_ORIENTATION:
                     orientation = value;
                     break;
+                case AXIS_RELATIVE_X:
+                    relativeX = value;
+                    break;
+                case AXIS_RELATIVE_Y:
+                    relativeY = value;
+                    break;
                 default: {
                     if (axis < 0 || axis > 63) {
                         throw new IllegalArgumentException("Axis out of range.");
diff --git a/core/java/android/view/RemoteAnimationAdapter.java b/core/java/android/view/RemoteAnimationAdapter.java
index a78036f..e1cc604 100644
--- a/core/java/android/view/RemoteAnimationAdapter.java
+++ b/core/java/android/view/RemoteAnimationAdapter.java
@@ -17,6 +17,7 @@
 package android.view;
 
 import android.app.ActivityOptions;
+import android.app.IApplicationThread;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
 import android.os.Parcel;
@@ -58,6 +59,9 @@
     private int mCallingPid;
     private int mCallingUid;
 
+    /** @see #getCallingApplication */
+    private IApplicationThread mCallingApplication;
+
     /**
      * @param runner The interface that gets notified when we actually need to start the animation.
      * @param duration The duration of the animation.
@@ -81,11 +85,19 @@
         this(runner, duration, statusBarTransitionDelay, false /* changeNeedsSnapshot */);
     }
 
+    @UnsupportedAppUsage
+    public RemoteAnimationAdapter(IRemoteAnimationRunner runner, long duration,
+            long statusBarTransitionDelay, IApplicationThread callingApplication) {
+        this(runner, duration, statusBarTransitionDelay, false /* changeNeedsSnapshot */);
+        mCallingApplication = callingApplication;
+    }
+
     public RemoteAnimationAdapter(Parcel in) {
         mRunner = IRemoteAnimationRunner.Stub.asInterface(in.readStrongBinder());
         mDuration = in.readLong();
         mStatusBarTransitionDelay = in.readLong();
         mChangeNeedsSnapshot = in.readBoolean();
+        mCallingApplication = IApplicationThread.Stub.asInterface(in.readStrongBinder());
     }
 
     public IRemoteAnimationRunner getRunner() {
@@ -126,6 +138,15 @@
         return mCallingUid;
     }
 
+    /**
+     * Gets the ApplicationThread that will run the animation. Instead it is intended to pass the
+     * calling information among client processes (eg. shell + launcher) through one-way binder
+     * calls (where binder itself doesn't track calling information).
+     */
+    public IApplicationThread getCallingApplication() {
+        return mCallingApplication;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -137,6 +158,7 @@
         dest.writeLong(mDuration);
         dest.writeLong(mStatusBarTransitionDelay);
         dest.writeBoolean(mChangeNeedsSnapshot);
+        dest.writeStrongInterface(mCallingApplication);
     }
 
     public static final @android.annotation.NonNull Creator<RemoteAnimationAdapter> CREATOR
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 4e2f37f..b300f30 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -162,6 +162,8 @@
             IBinder displayToken, long nativeSurfaceObject);
     private static native void nativeSetDisplayLayerStack(long transactionObj,
             IBinder displayToken, int layerStack);
+    private static native void nativeSetDisplayFlags(long transactionObj,
+            IBinder displayToken, int flags);
     private static native void nativeSetDisplayProjection(long transactionObj,
             IBinder displayToken, int orientation,
             int l, int t, int r, int b,
@@ -548,6 +550,15 @@
      */
     private static final int SURFACE_OPAQUE = 0x02;
 
+    /* flags used with setDisplayFlags() (keep in sync with DisplayDevice.h) */
+
+    /**
+     * DisplayDevice flag: This display's transform is sent to inputflinger and used for input
+     * dispatch. This flag is used to disambiguate displays which share a layerstack.
+     * @hide
+     */
+    public static final int DISPLAY_RECEIVES_INPUT = 0x01;
+
     // Display power modes.
     /**
      * Display power mode off: used while blanking the screen.
@@ -3167,6 +3178,17 @@
         /**
          * @hide
          */
+        public Transaction setDisplayFlags(IBinder displayToken, int flags) {
+            if (displayToken == null) {
+                throw new IllegalArgumentException("displayToken must not be null");
+            }
+            nativeSetDisplayFlags(mNativeObject, displayToken, flags);
+            return this;
+        }
+
+        /**
+         * @hide
+         */
         public Transaction setDisplayProjection(IBinder displayToken,
                 int orientation, Rect layerStackRect, Rect displayRect) {
             if (displayToken == null) {
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 4f2cf6d..f04530f 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -1559,12 +1559,21 @@
      * @hide
      */
     public void setResizeBackgroundColor(int bgColor) {
+        setResizeBackgroundColor(mTmpTransaction, bgColor);
+        mTmpTransaction.apply();
+    }
+
+    /**
+     * Version of {@link #setResizeBackgroundColor(int)} that allows you to provide
+     * {@link SurfaceControl.Transaction}.
+     * @hide
+     */
+    public void setResizeBackgroundColor(@NonNull SurfaceControl.Transaction t, int bgColor) {
         if (mBackgroundControl == null) {
             return;
         }
-
         mBackgroundColor = bgColor;
-        updateBackgroundColor(mTmpTransaction).apply();
+        updateBackgroundColor(t);
     }
 
     @UnsupportedAppUsage
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 2806850..34c4e5d 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -291,6 +291,21 @@
      */
     private static final int SCROLL_CAPTURE_REQUEST_TIMEOUT_MILLIS = 2500;
 
+    /**
+     * If set to {@code true}, the new logic to layout system bars as normal window and to use
+     * layout result to get insets will be applied. Otherwise, the old hard-coded window logic will
+     * be applied.
+     */
+    private static final String USE_FLEXIBLE_INSETS = "persist.debug.flexible_insets";
+
+    /**
+     * A flag to indicate to use the new generalized insets window logic, or the old hard-coded
+     * insets window layout logic.
+     * {@hide}
+     */
+    public static final boolean INSETS_LAYOUT_GENERALIZATION =
+            SystemProperties.getBoolean(USE_FLEXIBLE_INSETS, false);
+
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     static final ThreadLocal<HandlerActionQueue> sRunQueues = new ThreadLocal<HandlerActionQueue>();
 
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 55beae0f..fcfb0ab 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -97,6 +97,7 @@
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
+import android.graphics.Insets;
 import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -394,6 +395,11 @@
      */
     int TRANSIT_KEYGUARD_UNOCCLUDE = 9;
     /**
+     * A window is starting to enter PiP.
+     * @hide
+     */
+    int TRANSIT_PIP = 10;
+    /**
      * The first slot for custom transition types. Callers (like Shell) can make use of custom
      * transition types for dealing with special cases. These types are effectively ignored by
      * Core and will just be passed along as part of TransitionInfo objects. An example is
@@ -402,7 +408,7 @@
      * implementation.
      * @hide
      */
-    int TRANSIT_FIRST_CUSTOM = 10;
+    int TRANSIT_FIRST_CUSTOM = 11;
 
     /**
      * @hide
@@ -418,6 +424,7 @@
             TRANSIT_KEYGUARD_GOING_AWAY,
             TRANSIT_KEYGUARD_OCCLUDE,
             TRANSIT_KEYGUARD_UNOCCLUDE,
+            TRANSIT_PIP,
             TRANSIT_FIRST_CUSTOM
     })
     @Retention(RetentionPolicy.SOURCE)
@@ -467,6 +474,13 @@
     int TRANSIT_FLAG_KEYGUARD_LOCKED = 0x40;
 
     /**
+     * Transition flag: Indicates that this transition is for recents animation.
+     * TODO(b/188669821): Remove once special-case logic moves to shell.
+     * @hide
+     */
+    int TRANSIT_FLAG_IS_RECENTS = 0x80;
+
+    /**
      * @hide
      */
     @IntDef(flag = true, prefix = { "TRANSIT_FLAG_" }, value = {
@@ -476,7 +490,8 @@
             TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION,
             TRANSIT_FLAG_APP_CRASHED,
             TRANSIT_FLAG_OPEN_BEHIND,
-            TRANSIT_FLAG_KEYGUARD_LOCKED
+            TRANSIT_FLAG_KEYGUARD_LOCKED,
+            TRANSIT_FLAG_IS_RECENTS
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface TransitionFlags {}
@@ -3468,6 +3483,22 @@
         public @InsetsState.InternalInsetsType int[] providesInsetsTypes;
 
         /**
+         * If specified, the insets provided by this window will be our window frame minus the
+         * insets specified by providedInternalInsets.
+         *
+         * @hide
+         */
+        public Insets providedInternalInsets = Insets.NONE;
+
+        /**
+         * {@link LayoutParams} to be applied to the window when layout with a assigned rotation.
+         * This will make layout during rotation change smoothly.
+         *
+         * @hide
+         */
+        public LayoutParams[] paramsForRotation;
+
+        /**
          * Specifies types of insets that this window should avoid overlapping during layout.
          *
          * @param types which {@link WindowInsets.Type}s of insets that this window should avoid.
@@ -3566,6 +3597,18 @@
             return mFitInsetsIgnoringVisibility;
         }
 
+        private void checkNonRecursiveParams() {
+            if (paramsForRotation == null) {
+                return;
+            }
+            for (int i = paramsForRotation.length - 1; i >= 0; i--) {
+                if (paramsForRotation[i].paramsForRotation != null) {
+                    throw new IllegalArgumentException(
+                            "Params cannot contain params recursively.");
+                }
+            }
+        }
+
         public LayoutParams() {
             super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
             type = TYPE_APPLICATION;
@@ -3820,6 +3863,14 @@
             } else {
                 out.writeInt(0);
             }
+            providedInternalInsets.writeToParcel(out, 0 /* parcelableFlags */);
+            if (paramsForRotation != null) {
+                checkNonRecursiveParams();
+                out.writeInt(paramsForRotation.length);
+                out.writeTypedArray(paramsForRotation, 0 /* parcelableFlags */);
+            } else {
+                out.writeInt(0);
+            }
         }
 
         public static final @android.annotation.NonNull Parcelable.Creator<LayoutParams> CREATOR
@@ -3891,6 +3942,12 @@
                 providesInsetsTypes = new int[insetsTypesLength];
                 in.readIntArray(providesInsetsTypes);
             }
+            providedInternalInsets = Insets.CREATOR.createFromParcel(in);
+            int paramsForRotationLength = in.readInt();
+            if (paramsForRotationLength > 0) {
+                paramsForRotation = new LayoutParams[paramsForRotationLength];
+                in.readTypedArray(paramsForRotation, LayoutParams.CREATOR);
+            }
         }
 
         @SuppressWarnings({"PointlessBitwiseExpression"})
@@ -4187,6 +4244,17 @@
                 changes |= LAYOUT_CHANGED;
             }
 
+            if (!providedInternalInsets.equals(o.providedInternalInsets)) {
+                providedInternalInsets = o.providedInternalInsets;
+                changes |= LAYOUT_CHANGED;
+            }
+
+            if (!Arrays.equals(paramsForRotation, o.paramsForRotation)) {
+                paramsForRotation = o.paramsForRotation;
+                checkNonRecursiveParams();
+                changes |= LAYOUT_CHANGED;
+            }
+
             return changes;
         }
 
@@ -4382,6 +4450,18 @@
                     sb.append(InsetsState.typeToString(providesInsetsTypes[i]));
                 }
             }
+            if (!providedInternalInsets.equals(Insets.NONE)) {
+                sb.append(" providedInternalInsets=");
+                sb.append(providedInternalInsets);
+            }
+            if (paramsForRotation != null && paramsForRotation.length != 0) {
+                sb.append(System.lineSeparator());
+                sb.append(prefix).append("  paramsForRotation=");
+                for (int i = 0; i < paramsForRotation.length; ++i) {
+                    if (i > 0) sb.append(' ');
+                    sb.append(paramsForRotation[i].toString());
+                }
+            }
 
             sb.append('}');
             return sb.toString();
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index dd81dd9..aac09b8 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -16,6 +16,9 @@
 
 package android.view.accessibility;
 
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_INTERACTION_CLIENT;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK;
+
 import android.accessibilityservice.IAccessibilityServiceConnection;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -86,6 +89,7 @@
     public static final int NO_ID = -1;
 
     public static final String CALL_STACK = "call_stack";
+    public static final String IGNORE_CALL_STACK = "ignore_call_stack";
 
     private static final String LOG_TAG = "AccessibilityInteractionClient";
 
@@ -121,6 +125,12 @@
 
     private volatile int mInteractionId = -1;
     private volatile int mCallingUid = Process.INVALID_UID;
+    // call stack for IAccessibilityInteractionConnectionCallback APIs. These callback APIs are
+    // shared by multiple requests APIs in IAccessibilityServiceConnection. To correctly log the
+    // request API which triggers the callback, we log trace entries for callback after the
+    // request API thread waiting for the callback returns. To log the correct callback stack in
+    // the request API thread, we save the callback stack in this member variables.
+    private List<StackTraceElement> mCallStackOfCallback;
 
     private AccessibilityNodeInfo mFindAccessibilityNodeInfoResult;
 
@@ -307,18 +317,30 @@
                         if (DEBUG) {
                             Log.i(LOG_TAG, "Window cache hit");
                         }
+                        if (shouldTraceClient()) {
+                            logTraceClient(connection, "getWindow cache",
+                                    "connectionId=" + connectionId + ";accessibilityWindowId="
+                                    + accessibilityWindowId + ";bypassCache=false");
+                        }
                         return window;
                     }
                     if (DEBUG) {
                         Log.i(LOG_TAG, "Window cache miss");
                     }
                 }
+
                 final long identityToken = Binder.clearCallingIdentity();
                 try {
                     window = connection.getWindow(accessibilityWindowId);
                 } finally {
                     Binder.restoreCallingIdentity(identityToken);
                 }
+                if (shouldTraceClient()) {
+                    logTraceClient(connection, "getWindow", "connectionId=" + connectionId
+                            + ";accessibilityWindowId=" + accessibilityWindowId + ";bypassCache="
+                            + bypassCache);
+                }
+
                 if (window != null) {
                     if (!bypassCache) {
                         sAccessibilityCache.addWindow(window);
@@ -368,6 +390,10 @@
                     if (DEBUG) {
                         Log.i(LOG_TAG, "Windows cache hit");
                     }
+                    if (shouldTraceClient()) {
+                        logTraceClient(
+                                connection, "getWindows cache", "connectionId=" + connectionId);
+                    }
                     return windows;
                 }
                 if (DEBUG) {
@@ -379,6 +405,9 @@
                 } finally {
                     Binder.restoreCallingIdentity(identityToken);
                 }
+                if (shouldTraceClient()) {
+                    logTraceClient(connection, "getWindows", "connectionId=" + connectionId);
+                }
                 if (windows != null) {
                     sAccessibilityCache.setWindowsOnAllDisplays(windows);
                     return windows;
@@ -472,6 +501,15 @@
                             Log.i(LOG_TAG, "Node cache hit for "
                                     + idToString(accessibilityWindowId, accessibilityNodeId));
                         }
+                        if (shouldTraceClient()) {
+                            logTraceClient(connection,
+                                    "findAccessibilityNodeInfoByAccessibilityId cache",
+                                    "connectionId=" + connectionId + ";accessibilityWindowId="
+                                    + accessibilityWindowId + ";accessibilityNodeId="
+                                    + accessibilityNodeId + ";bypassCache=" + bypassCache
+                                    + ";prefetchFlags=" + prefetchFlags + ";arguments="
+                                    + arguments);
+                        }
                         return cachedInfo;
                     }
                     if (DEBUG) {
@@ -488,6 +526,14 @@
                     prefetchFlags &= ~AccessibilityNodeInfo.FLAG_PREFETCH_MASK;
                 }
                 final int interactionId = mInteractionIdCounter.getAndIncrement();
+                if (shouldTraceClient()) {
+                    logTraceClient(connection, "findAccessibilityNodeInfoByAccessibilityId",
+                            "InteractionId:" + interactionId + "connectionId=" + connectionId
+                            + ";accessibilityWindowId=" + accessibilityWindowId
+                            + ";accessibilityNodeId=" + accessibilityNodeId + ";bypassCache="
+                            + bypassCache + ";prefetchFlags=" + prefetchFlags + ";arguments="
+                            + arguments);
+                }
                 final String[] packageNames;
                 final long identityToken = Binder.clearCallingIdentity();
                 try {
@@ -500,16 +546,10 @@
                 if (packageNames != null) {
                     AccessibilityNodeInfo info =
                             getFindAccessibilityNodeInfoResultAndClear(interactionId);
-                    if (mAccessibilityManager != null
-                            && mAccessibilityManager.isAccessibilityTracingEnabled()) {
-                        logTrace(connection, "findAccessibilityNodeInfoByAccessibilityId",
-                                "InteractionId:" + interactionId + ";Result: " + info
-                                        + ";connectionId=" + connectionId
-                                        + ";accessibilityWindowId="
-                                        + accessibilityWindowId + ";accessibilityNodeId="
-                                        + accessibilityNodeId + ";bypassCache=" + bypassCache
-                                        + ";prefetchFlags=" + prefetchFlags
-                                        + ";arguments=" + arguments);
+                    if (shouldTraceCallback()) {
+                        logTraceCallback(connection, "findAccessibilityNodeInfoByAccessibilityId",
+                                "InteractionId:" + interactionId + ";connectionId="
+                                + connectionId + ";Result: " + info);
                     }
                     if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_MASK) != 0
                             && info != null) {
@@ -571,6 +611,14 @@
                 final String[] packageNames;
                 final long identityToken = Binder.clearCallingIdentity();
                 try {
+                    if (shouldTraceClient()) {
+                        logTraceClient(connection, "findAccessibilityNodeInfosByViewId",
+                                "InteractionId=" + interactionId + ";connectionId=" + connectionId
+                                + ";accessibilityWindowId=" + accessibilityWindowId
+                                + ";accessibilityNodeId=" + accessibilityNodeId + ";viewId="
+                                + viewId);
+                    }
+
                     packageNames = connection.findAccessibilityNodeInfosByViewId(
                             accessibilityWindowId, accessibilityNodeId, viewId, interactionId, this,
                             Thread.currentThread().getId());
@@ -581,13 +629,10 @@
                 if (packageNames != null) {
                     List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
                             interactionId);
-                    if (mAccessibilityManager != null
-                            && mAccessibilityManager.isAccessibilityTracingEnabled()) {
-                        logTrace(connection, "findAccessibilityNodeInfosByViewId", "InteractionId="
-                                + interactionId + ":Result: " + infos + ";connectionId="
-                                + connectionId + ";accessibilityWindowId=" + accessibilityWindowId
-                                + ";accessibilityNodeId=" + accessibilityNodeId + ";viewId="
-                                + viewId);
+                    if (shouldTraceCallback()) {
+                        logTraceCallback(connection, "findAccessibilityNodeInfosByViewId",
+                                "InteractionId=" + interactionId + ";connectionId=" + connectionId
+                                + ":Result: " + infos);
                     }
                     if (infos != null) {
                         finalizeAndCacheAccessibilityNodeInfos(infos, connectionId,
@@ -630,6 +675,12 @@
             IAccessibilityServiceConnection connection = getConnection(connectionId);
             if (connection != null) {
                 final int interactionId = mInteractionIdCounter.getAndIncrement();
+                if (shouldTraceClient()) {
+                    logTraceClient(connection, "findAccessibilityNodeInfosByText",
+                            "InteractionId:" + interactionId + "connectionId=" + connectionId
+                            + ";accessibilityWindowId=" + accessibilityWindowId
+                            + ";accessibilityNodeId=" + accessibilityNodeId + ";text=" + text);
+                }
                 final String[] packageNames;
                 final long identityToken = Binder.clearCallingIdentity();
                 try {
@@ -643,12 +694,10 @@
                 if (packageNames != null) {
                     List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
                             interactionId);
-                    if (mAccessibilityManager != null
-                            && mAccessibilityManager.isAccessibilityTracingEnabled()) {
-                        logTrace(connection, "findAccessibilityNodeInfosByText", "InteractionId="
-                                + interactionId + ":Result: " + infos + ";connectionId="
-                                + connectionId + ";accessibilityWindowId=" + accessibilityWindowId
-                                + ";accessibilityNodeId=" + accessibilityNodeId + ";text=" + text);
+                    if (shouldTraceCallback()) {
+                        logTraceCallback(connection, "findAccessibilityNodeInfosByText",
+                                "InteractionId=" + interactionId + ";connectionId=" + connectionId
+                                + ";Result: " + infos);
                     }
                     if (infos != null) {
                         finalizeAndCacheAccessibilityNodeInfos(infos, connectionId,
@@ -690,6 +739,13 @@
             IAccessibilityServiceConnection connection = getConnection(connectionId);
             if (connection != null) {
                 final int interactionId = mInteractionIdCounter.getAndIncrement();
+                if (shouldTraceClient()) {
+                    logTraceClient(connection, "findFocus",
+                            "InteractionId:" + interactionId + "connectionId=" + connectionId
+                            + ";accessibilityWindowId=" + accessibilityWindowId
+                            + ";accessibilityNodeId=" + accessibilityNodeId + ";focusType="
+                            + focusType);
+                }
                 final String[] packageNames;
                 final long identityToken = Binder.clearCallingIdentity();
                 try {
@@ -703,13 +759,9 @@
                 if (packageNames != null) {
                     AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
                             interactionId);
-                    if (mAccessibilityManager != null
-                            && mAccessibilityManager.isAccessibilityTracingEnabled()) {
-                        logTrace(connection, "findFocus", "InteractionId=" + interactionId
-                                + ":Result: " + info + ";connectionId=" + connectionId
-                                + ";accessibilityWindowId=" + accessibilityWindowId
-                                + ";accessibilityNodeId=" + accessibilityNodeId + ";focusType="
-                                + focusType);
+                    if (shouldTraceCallback()) {
+                        logTraceCallback(connection, "findFocus", "InteractionId=" + interactionId
+                                + ";connectionId=" + connectionId + ";Result:" + info);
                     }
                     finalizeAndCacheAccessibilityNodeInfo(info, connectionId, false, packageNames);
                     return info;
@@ -747,6 +799,13 @@
             IAccessibilityServiceConnection connection = getConnection(connectionId);
             if (connection != null) {
                 final int interactionId = mInteractionIdCounter.getAndIncrement();
+                if (shouldTraceClient()) {
+                    logTraceClient(connection, "focusSearch",
+                            "InteractionId:" + interactionId + "connectionId=" + connectionId
+                            + ";accessibilityWindowId=" + accessibilityWindowId
+                            + ";accessibilityNodeId=" + accessibilityNodeId + ";direction="
+                            + direction);
+                }
                 final String[] packageNames;
                 final long identityToken = Binder.clearCallingIdentity();
                 try {
@@ -761,13 +820,9 @@
                     AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
                             interactionId);
                     finalizeAndCacheAccessibilityNodeInfo(info, connectionId, false, packageNames);
-                    if (mAccessibilityManager != null
-                            && mAccessibilityManager.isAccessibilityTracingEnabled()) {
-                        logTrace(connection, "focusSearch", "InteractionId=" + interactionId
-                                + ":Result: " + info + ";connectionId=" + connectionId
-                                + ";accessibilityWindowId=" + accessibilityWindowId
-                                + ";accessibilityNodeId=" + accessibilityNodeId + ";direction="
-                                + direction);
+                    if (shouldTraceCallback()) {
+                        logTraceCallback(connection, "focusSearch", "InteractionId=" + interactionId
+                                + ";connectionId=" + connectionId + ";Result:" + info);
                     }
                     return info;
                 }
@@ -803,6 +858,13 @@
             IAccessibilityServiceConnection connection = getConnection(connectionId);
             if (connection != null) {
                 final int interactionId = mInteractionIdCounter.getAndIncrement();
+                if (shouldTraceClient()) {
+                    logTraceClient(connection, "performAccessibilityAction",
+                            "InteractionId:" + interactionId + "connectionId=" + connectionId
+                            + ";accessibilityWindowId=" + accessibilityWindowId
+                            + ";accessibilityNodeId=" + accessibilityNodeId + ";action=" + action
+                            + ";arguments=" + arguments);
+                }
                 final boolean success;
                 final long identityToken = Binder.clearCallingIdentity();
                 try {
@@ -816,13 +878,10 @@
                 if (success) {
                     final boolean result =
                             getPerformAccessibilityActionResultAndClear(interactionId);
-                    if (mAccessibilityManager != null
-                            && mAccessibilityManager.isAccessibilityTracingEnabled()) {
-                        logTrace(connection, "performAccessibilityAction", "InteractionId="
-                                + interactionId + ":Result: " + result + ";connectionId="
-                                + connectionId + ";accessibilityWindowId=" + accessibilityWindowId
-                                + ";accessibilityNodeId=" + accessibilityNodeId + ";action="
-                                + action + ";arguments=" + arguments);
+                    if (shouldTraceCallback()) {
+                        logTraceCallback(connection, "performAccessibilityAction",
+                                "InteractionId=" + interactionId + ";connectionId=" + connectionId
+                                + ";Result: " + result);
                     }
                     return result;
                 }
@@ -886,6 +945,8 @@
                 mFindAccessibilityNodeInfoResult = info;
                 mInteractionId = interactionId;
                 mCallingUid = Binder.getCallingUid();
+                mCallStackOfCallback = new ArrayList<StackTraceElement>(
+                        Arrays.asList(Thread.currentThread().getStackTrace()));
             }
             mInstanceLock.notifyAll();
         }
@@ -936,6 +997,8 @@
                 }
                 mInteractionId = interactionId;
                 mCallingUid = Binder.getCallingUid();
+                mCallStackOfCallback = new ArrayList<StackTraceElement>(
+                    Arrays.asList(Thread.currentThread().getStackTrace()));
             }
             mInstanceLock.notifyAll();
         }
@@ -975,13 +1038,15 @@
             finalizeAndCacheAccessibilityNodeInfos(
                     infos, connectionIdWaitingForPrefetchResultCopy, false,
                     packageNamesForNextPrefetchResultCopy);
-            if (mAccessibilityManager != null
-                    && mAccessibilityManager.isAccessibilityTracingEnabled()) {
+            if (shouldTraceCallback()) {
                 logTrace(getConnection(connectionIdWaitingForPrefetchResultCopy),
                         "setPrefetchAccessibilityNodeInfoResult",
-                        "InteractionId:" + interactionId + ";Result: " + infos
-                                + ";connectionId=" + connectionIdWaitingForPrefetchResultCopy,
-                        Binder.getCallingUid());
+                        "InteractionId:" + interactionId + ";connectionId="
+                        + connectionIdWaitingForPrefetchResultCopy + ";Result: " + infos,
+                        Binder.getCallingUid(),
+                        Arrays.asList(Thread.currentThread().getStackTrace()),
+                        new HashSet<String>(Arrays.asList("getStackTrace")),
+                        FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK);
             }
         } else if (DEBUG) {
             Log.w(LOG_TAG, "Prefetching for interaction with id " + interactionId + " dropped "
@@ -1013,6 +1078,8 @@
                 mPerformAccessibilityActionResult = succeeded;
                 mInteractionId = interactionId;
                 mCallingUid = Binder.getCallingUid();
+                mCallStackOfCallback = new ArrayList<StackTraceElement>(
+                    Arrays.asList(Thread.currentThread().getStackTrace()));
             }
             mInstanceLock.notifyAll();
         }
@@ -1222,24 +1289,45 @@
         return true;
     }
 
+    private boolean shouldTraceClient() {
+        return (mAccessibilityManager != null)
+                && mAccessibilityManager.isA11yInteractionClientTraceEnabled();
+    }
+
+    private boolean shouldTraceCallback() {
+        return (mAccessibilityManager != null)
+                && mAccessibilityManager.isA11yInteractionConnectionCBTraceEnabled();
+    }
+
     private void logTrace(
             IAccessibilityServiceConnection connection, String method, String params,
-            int callingUid) {
+            int callingUid, List<StackTraceElement> callStack, HashSet<String> ignoreSet,
+            long logTypes) {
         try {
             Bundle b = new Bundle();
-            ArrayList<StackTraceElement> callStack = new ArrayList<StackTraceElement>(
-                    Arrays.asList(Thread.currentThread().getStackTrace()));
-            b.putSerializable(CALL_STACK, callStack);
+            b.putSerializable(CALL_STACK, new ArrayList<StackTraceElement>(callStack));
+            if (ignoreSet != null) {
+                b.putSerializable(IGNORE_CALL_STACK, ignoreSet);
+            }
             connection.logTrace(SystemClock.elapsedRealtimeNanos(),
-                    LOG_TAG + ".callback for " + method, params, Process.myPid(),
-                    Thread.currentThread().getId(), callingUid, b);
+                    LOG_TAG + "." + method,
+                    logTypes, params, Process.myPid(), Thread.currentThread().getId(),
+                    callingUid, b);
         } catch (RemoteException e) {
             Log.e(LOG_TAG, "Failed to log trace. " + e);
         }
     }
 
-    private void logTrace(
+    private void logTraceCallback(
             IAccessibilityServiceConnection connection, String method, String params) {
-        logTrace(connection, method, params, mCallingUid);
+        logTrace(connection, method + " callback", params, mCallingUid, mCallStackOfCallback,
+                new HashSet<String>(Arrays.asList("getStackTrace")),
+                FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK);
+    }
+
+    private void logTraceClient(
+            IAccessibilityServiceConnection connection, String method, String params) {
+        logTrace(connection, method, params, Binder.getCallingUid(),
+                Collections.emptyList(), null, FLAGS_ACCESSIBILITY_INTERACTION_CLIENT);
     }
 }
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index f9cdbd3..17fad7e 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -111,7 +111,13 @@
     public static final int STATE_FLAG_REQUEST_MULTI_FINGER_GESTURES = 0x00000010;
 
     /** @hide */
-    public static final int STATE_FLAG_ACCESSIBILITY_TRACING_ENABLED = 0x00000020;
+    public static final int STATE_FLAG_TRACE_A11Y_INTERACTION_CONNECTION_ENABLED = 0x00000100;
+    /** @hide */
+    public static final int STATE_FLAG_TRACE_A11Y_INTERACTION_CONNECTION_CB_ENABLED = 0x00000200;
+    /** @hide */
+    public static final int STATE_FLAG_TRACE_A11Y_INTERACTION_CLIENT_ENABLED = 0x00000400;
+    /** @hide */
+    public static final int STATE_FLAG_TRACE_A11Y_SERVICE_ENABLED = 0x00000800;
 
     /** @hide */
     public static final int DALTONIZER_DISABLED = -1;
@@ -235,8 +241,8 @@
     @UnsupportedAppUsage(trackingBug = 123768939L)
     boolean mIsHighTextContrastEnabled;
 
-    // Whether accessibility tracing is enabled or not
-    boolean mIsAccessibilityTracingEnabled = false;
+    // accessibility tracing state
+    int mAccessibilityTracingState = 0;
 
     AccessibilityPolicy mAccessibilityPolicy;
 
@@ -1029,13 +1035,50 @@
     }
 
     /**
-     * Gets accessibility tracing enabled state.
+     * Gets accessibility interaction connection tracing enabled state.
      *
      * @hide
      */
-    public boolean isAccessibilityTracingEnabled() {
+    public boolean isA11yInteractionConnectionTraceEnabled() {
         synchronized (mLock) {
-            return mIsAccessibilityTracingEnabled;
+            return ((mAccessibilityTracingState
+                    & STATE_FLAG_TRACE_A11Y_INTERACTION_CONNECTION_ENABLED) != 0);
+        }
+    }
+
+    /**
+     * Gets accessibility interaction connection callback tracing enabled state.
+     *
+     * @hide
+     */
+    public boolean isA11yInteractionConnectionCBTraceEnabled() {
+        synchronized (mLock) {
+            return ((mAccessibilityTracingState
+                    & STATE_FLAG_TRACE_A11Y_INTERACTION_CONNECTION_CB_ENABLED) != 0);
+        }
+    }
+
+    /**
+     * Gets accessibility interaction client tracing enabled state.
+     *
+     * @hide
+     */
+    public boolean isA11yInteractionClientTraceEnabled() {
+        synchronized (mLock) {
+            return ((mAccessibilityTracingState
+                    & STATE_FLAG_TRACE_A11Y_INTERACTION_CLIENT_ENABLED) != 0);
+        }
+    }
+
+    /**
+     * Gets accessibility service tracing enabled state.
+     *
+     * @hide
+     */
+    public boolean isA11yServiceTraceEnabled() {
+        synchronized (mLock) {
+            return ((mAccessibilityTracingState
+                    & STATE_FLAG_TRACE_A11Y_SERVICE_ENABLED) != 0);
         }
     }
 
@@ -1233,8 +1276,6 @@
                 (stateFlags & STATE_FLAG_TOUCH_EXPLORATION_ENABLED) != 0;
         final boolean highTextContrastEnabled =
                 (stateFlags & STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED) != 0;
-        final boolean accessibilityTracingEnabled =
-                (stateFlags & STATE_FLAG_ACCESSIBILITY_TRACING_ENABLED) != 0;
 
         final boolean wasEnabled = isEnabled();
         final boolean wasTouchExplorationEnabled = mIsTouchExplorationEnabled;
@@ -1257,7 +1298,7 @@
             notifyHighTextContrastStateChanged();
         }
 
-        updateAccessibilityTracingState(accessibilityTracingEnabled);
+        updateAccessibilityTracingState(stateFlags);
     }
 
     /**
@@ -1715,11 +1756,11 @@
     }
 
     /**
-     * Update mIsAccessibilityTracingEnabled.
+     * Update mAccessibilityTracingState.
      */
-    private void updateAccessibilityTracingState(boolean enabled) {
+    private void updateAccessibilityTracingState(int stateFlag) {
         synchronized (mLock) {
-            mIsAccessibilityTracingEnabled = enabled;
+            mAccessibilityTracingState = stateFlag;
         }
     }
 
diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java
index d2db0df..5b2068f 100644
--- a/core/java/android/view/inputmethod/InputMethod.java
+++ b/core/java/android/view/inputmethod/InputMethod.java
@@ -96,8 +96,6 @@
      *
      * @param token special token for the system to identify
      *              {@link InputMethodService}
-     * @param displayId The id of the display that current IME shown.
-     *                  Used for {{@link #updateInputMethodDisplay(int)}}
      * @param privilegedOperations IPC endpoint to do some privileged
      *                             operations that are allowed only to the
      *                             current IME.
@@ -105,9 +103,8 @@
      * @hide
      */
     @MainThread
-    default void initializeInternal(IBinder token, int displayId,
+    default void initializeInternal(IBinder token,
             IInputMethodPrivilegedOperations privilegedOperations, int configChanges) {
-        updateInputMethodDisplay(displayId);
         attachToken(token);
     }
 
@@ -143,16 +140,6 @@
     public void attachToken(IBinder token);
 
     /**
-     * Update context display according to given displayId.
-     *
-     * @param displayId The id of the display that need to update for context.
-     * @hide
-     */
-    @MainThread
-    default void updateInputMethodDisplay(int displayId) {
-    }
-
-    /**
      * Bind a new application environment in to the input method, so that it
      * can later start and stop input processing.
      * Typically this method is called when this input method is enabled in an
diff --git a/core/java/android/window/ConfigurationHelper.java b/core/java/android/window/ConfigurationHelper.java
new file mode 100644
index 0000000..9a07975
--- /dev/null
+++ b/core/java/android/window/ConfigurationHelper.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+import static android.view.Display.INVALID_DISPLAY;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ResourcesManager;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.view.Display;
+import android.view.WindowManager;
+
+/**
+ * A helper class to maintain {@link android.content.res.Configuration} related methods used both
+ * in {@link android.app.Activity} and {@link WindowContext}.
+ *
+ * @hide
+ */
+public class ConfigurationHelper {
+    private ConfigurationHelper() {}
+
+    /** Ask text layout engine to free its caches if there is a locale change. */
+    public static void freeTextLayoutCachesIfNeeded(int configDiff) {
+        if ((configDiff & ActivityInfo.CONFIG_LOCALE) != 0) {
+            Canvas.freeTextLayoutCaches();
+        }
+    }
+
+    /**
+     * A helper method to filter out {@link ActivityInfo#CONFIG_SCREEN_SIZE} if the
+     * {@link Configuration#diffPublicOnly(Configuration) diff} of two {@link Configuration}
+     * doesn't cross the boundary.
+     *
+     * @see SizeConfigurationBuckets#filterDiff(int, Configuration, Configuration,
+     * SizeConfigurationBuckets)
+     */
+    public static int diffPublicWithSizeBuckets(@Nullable Configuration currentConfig,
+            @NonNull Configuration newConfig, @Nullable SizeConfigurationBuckets buckets) {
+        // If current configuration is null, it is definitely different from updated Configuration.
+        if (currentConfig == null) {
+            return 0xffffffff;
+        }
+        int publicDiff = currentConfig.diffPublicOnly(newConfig);
+        return SizeConfigurationBuckets.filterDiff(publicDiff, currentConfig, newConfig, buckets);
+    }
+
+    /**
+     * Returns {@code true} if the {@link android.content.res.Resources} associated with
+     * a {@code token} needs to be updated.
+     *
+     * @param token A {@link Context#getActivityToken() activity token} or
+     * {@link Context#getWindowContextToken() window context token}
+     * @param config The original {@link Configuration}
+     * @param newConfig The updated Configuration
+     * @param displayChanged a flag to indicate there's a display change
+     * @param configChanged a flag to indicate there's a Configuration change.
+     *
+     * @see ResourcesManager#updateResourcesForActivity(IBinder, Configuration, int)
+     */
+    public static boolean shouldUpdateResources(IBinder token, @Nullable Configuration config,
+            @NonNull Configuration newConfig, @NonNull Configuration overrideConfig,
+            boolean displayChanged, @Nullable Boolean configChanged) {
+        // The configuration has not yet been initialized. We should update it.
+        if (config == null) {
+            return true;
+        }
+        // If the token associated context is moved to another display, we should update the
+        // ResourcesKey.
+        if (displayChanged) {
+            return true;
+        }
+        // If the new config is the same as the config this Activity is already running with and
+        // the override config also didn't change, then don't update the Resources
+        if (!ResourcesManager.getInstance().isSameResourcesOverrideConfig(token, overrideConfig)) {
+            return true;
+        }
+        // If there's a update on WindowConfiguration#mBounds or maxBounds, we should update the
+        // Resources to make WindowMetrics API report the updated result.
+        if (shouldUpdateWindowMetricsBounds(config, newConfig)) {
+            return true;
+        }
+        return configChanged == null ? config.diff(newConfig) != 0 : configChanged;
+    }
+
+    /**
+     * Returns {@code true} if {@code displayId} is different from {@code newDisplayId}.
+     * Note that {@link Display#INVALID_DISPLAY} means no difference.
+     */
+    public static boolean isDifferentDisplay(int displayId, int newDisplayId) {
+        return newDisplayId != INVALID_DISPLAY && displayId != newDisplayId;
+    }
+
+    // TODO(b/173090263): Remove this method after the improvement of AssetManager and ResourcesImpl
+    // constructions.
+    /**
+     * Returns {@code true} if the metrics reported by {@link android.view.WindowMetrics} APIs
+     * should be updated.
+     *
+     * @see WindowManager#getCurrentWindowMetrics()
+     * @see WindowManager#getMaximumWindowMetrics()
+     */
+    private static boolean shouldUpdateWindowMetricsBounds(@NonNull Configuration currentConfig,
+            @NonNull Configuration newConfig) {
+        final Rect currentBounds = currentConfig.windowConfiguration.getBounds();
+        final Rect newBounds = newConfig.windowConfiguration.getBounds();
+
+        final Rect currentMaxBounds = currentConfig.windowConfiguration.getMaxBounds();
+        final Rect newMaxBounds = newConfig.windowConfiguration.getMaxBounds();
+
+        return !currentBounds.equals(newBounds) || !currentMaxBounds.equals(newMaxBounds);
+    }
+}
diff --git a/core/java/android/window/DisplayAreaInfo.java b/core/java/android/window/DisplayAreaInfo.java
index 358467f..1a7aab6 100644
--- a/core/java/android/window/DisplayAreaInfo.java
+++ b/core/java/android/window/DisplayAreaInfo.java
@@ -16,6 +16,8 @@
 
 package android.window;
 
+import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
+
 import android.annotation.NonNull;
 import android.annotation.TestApi;
 import android.content.res.Configuration;
@@ -43,8 +45,17 @@
      */
     public final int displayId;
 
+    /**
+     * The feature id of this display area.
+     */
     public final int featureId;
 
+    /**
+     * The feature id of the root display area this display area is associated with.
+     * @hide
+     */
+    public int rootDisplayAreaId = FEATURE_UNDEFINED;
+
     public DisplayAreaInfo(@NonNull WindowContainerToken token, int displayId, int featureId) {
         this.token = token;
         this.displayId = displayId;
@@ -56,6 +67,7 @@
         configuration.readFromParcel(in);
         displayId = in.readInt();
         featureId = in.readInt();
+        rootDisplayAreaId = in.readInt();
     }
 
     @Override
@@ -64,6 +76,7 @@
         configuration.writeToParcel(dest, flags);
         dest.writeInt(displayId);
         dest.writeInt(featureId);
+        dest.writeInt(rootDisplayAreaId);
     }
 
     @NonNull
diff --git a/core/java/android/window/DisplayAreaOrganizer.java b/core/java/android/window/DisplayAreaOrganizer.java
index 8784399..e674655 100644
--- a/core/java/android/window/DisplayAreaOrganizer.java
+++ b/core/java/android/window/DisplayAreaOrganizer.java
@@ -34,6 +34,15 @@
 public class DisplayAreaOrganizer extends WindowOrganizer {
 
     /**
+     * Key to specify the {@link com.android.server.wm.RootDisplayArea} to attach a window to.
+     * It will be used by the function passed in from
+     * {@link com.android.server.wm.DisplayAreaPolicyBuilder#setSelectRootForWindowFunc(BiFunction)}
+     * to find the Root DA to attach the window.
+     * @hide
+     */
+    public static final String KEY_ROOT_DISPLAY_AREA_ID = "root_display_area_id";
+
+    /**
      * The value in display area indicating that no value has been set.
      */
     public static final int FEATURE_UNDEFINED = -1;
diff --git a/core/java/android/window/IRemoteTransitionFinishedCallback.aidl b/core/java/android/window/IRemoteTransitionFinishedCallback.aidl
index 02aa1a9..7864c24 100644
--- a/core/java/android/window/IRemoteTransitionFinishedCallback.aidl
+++ b/core/java/android/window/IRemoteTransitionFinishedCallback.aidl
@@ -16,14 +16,18 @@
 
 package android.window;
 
+import android.view.SurfaceControl;
 import android.window.WindowContainerTransaction;
 
 /**
  * Interface to be invoked by the controlling process when a remote transition has finished.
  *
  * @see IRemoteTransition
+ * @param wct An optional WindowContainerTransaction to apply before the transition finished.
+ * @param sct An optional Surface Transaction that is added to the end of the finish/cleanup
+ *            transaction. This is applied by shell.Transitions (before submitting the wct).
  * {@hide}
  */
 interface IRemoteTransitionFinishedCallback {
-    void onTransitionFinished(in WindowContainerTransaction wct);
+    void onTransitionFinished(in WindowContainerTransaction wct, in SurfaceControl.Transaction sct);
 }
diff --git a/core/java/android/window/ITaskFragmentOrganizer.aidl b/core/java/android/window/ITaskFragmentOrganizer.aidl
new file mode 100644
index 0000000..5eb432e
--- /dev/null
+++ b/core/java/android/window/ITaskFragmentOrganizer.aidl
@@ -0,0 +1,52 @@
+/**
+ * Copyright (c) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.window.TaskFragmentAppearedInfo;
+import android.window.TaskFragmentInfo;
+
+/** @hide */
+oneway interface ITaskFragmentOrganizer {
+    void onTaskFragmentAppeared(in TaskFragmentAppearedInfo taskFragmentAppearedInfo);
+    void onTaskFragmentInfoChanged(in TaskFragmentInfo taskFragmentInfo);
+    void onTaskFragmentVanished(in TaskFragmentInfo taskFragmentInfo);
+
+    /**
+     * Called when the parent leaf Task of organized TaskFragments is changed.
+     * When the leaf Task is changed, the organizer may want to update the TaskFragments in one
+     * transaction.
+     *
+     * For case like screen size change, it will trigger onTaskFragmentParentInfoChanged with new
+     * Task bounds, but may not trigger onTaskFragmentInfoChanged because there can be an override
+     * bounds.
+     */
+    void onTaskFragmentParentInfoChanged(in IBinder fragmentToken, in Configuration parentConfig);
+
+    /**
+     * Called when the {@link WindowContainerTransaction} created with
+     * {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)} failed on the server side.
+     *
+     * @param errorCallbackToken    Token set through {@link
+     *                              WindowContainerTransaction#setErrorCallbackToken(IBinder)}
+     * @param exceptionBundle   Bundle containing the exception. Should be created with
+     *                          {@link TaskFragmentOrganizer#putExceptionInBundle}.
+     */
+    void onTaskFragmentError(in IBinder errorCallbackToken, in Bundle exceptionBundle);
+}
diff --git a/core/java/android/window/ITaskFragmentOrganizerController.aidl b/core/java/android/window/ITaskFragmentOrganizerController.aidl
new file mode 100644
index 0000000..0ca8a86
--- /dev/null
+++ b/core/java/android/window/ITaskFragmentOrganizerController.aidl
@@ -0,0 +1,33 @@
+/**
+ * Copyright (c) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+import android.window.ITaskFragmentOrganizer;
+
+/** @hide */
+interface ITaskFragmentOrganizerController {
+
+    /**
+     * Registers a TaskFragmentOrganizer to manage TaskFragments.
+     */
+    void registerOrganizer(in ITaskFragmentOrganizer organizer);
+
+    /**
+     * Unregisters a previously registered TaskFragmentOrganizer.
+     */
+    void unregisterOrganizer(in ITaskFragmentOrganizer organizer);
+}
diff --git a/core/java/android/window/IWindowOrganizerController.aidl b/core/java/android/window/IWindowOrganizerController.aidl
index 1223d72..39cdf5a 100644
--- a/core/java/android/window/IWindowOrganizerController.aidl
+++ b/core/java/android/window/IWindowOrganizerController.aidl
@@ -20,6 +20,7 @@
 
 import android.os.IBinder;
 import android.window.IDisplayAreaOrganizerController;
+import android.window.ITaskFragmentOrganizerController;
 import android.window.ITaskOrganizerController;
 import android.window.ITransitionPlayer;
 import android.window.IWindowContainerTransactionCallback;
@@ -77,6 +78,9 @@
     /** @return An interface enabling the management of display area organizers. */
     IDisplayAreaOrganizerController getDisplayAreaOrganizerController();
 
+    /** @return An interface enabling the management of task fragment organizers. */
+    ITaskFragmentOrganizerController getTaskFragmentOrganizerController();
+
     /**
      * Registers a transition player with Core. There is only one of these at a time and calling
      * this will replace the existing one if set.
diff --git a/core/java/android/window/TaskFragmentAppearedInfo.aidl b/core/java/android/window/TaskFragmentAppearedInfo.aidl
new file mode 100644
index 0000000..3729c09
--- /dev/null
+++ b/core/java/android/window/TaskFragmentAppearedInfo.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+/**
+ * Data object for the TaskFragment info provided when a TaskFragment is presented to an organizer.
+ * @hide
+ */
+parcelable TaskFragmentAppearedInfo;
diff --git a/core/java/android/window/TaskFragmentAppearedInfo.java b/core/java/android/window/TaskFragmentAppearedInfo.java
new file mode 100644
index 0000000..234b30c
--- /dev/null
+++ b/core/java/android/window/TaskFragmentAppearedInfo.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.SurfaceControl;
+
+/**
+ * Data object for the TaskFragment info provided when a TaskFragment is presented to an organizer.
+ * @hide
+ */
+public final class TaskFragmentAppearedInfo implements Parcelable {
+
+    @NonNull
+    private final TaskFragmentInfo mTaskFragmentInfo;
+
+    @NonNull
+    private final SurfaceControl mLeash;
+
+    public TaskFragmentAppearedInfo(
+            @NonNull TaskFragmentInfo taskFragmentInfo, @NonNull SurfaceControl leash) {
+        mTaskFragmentInfo = taskFragmentInfo;
+        mLeash = leash;
+    }
+
+    public TaskFragmentInfo getTaskFragmentInfo() {
+        return mTaskFragmentInfo;
+    }
+
+    public SurfaceControl getLeash() {
+        return mLeash;
+    }
+
+    private TaskFragmentAppearedInfo(Parcel in) {
+        mTaskFragmentInfo = in.readTypedObject(TaskFragmentInfo.CREATOR);
+        mLeash = in.readTypedObject(SurfaceControl.CREATOR);
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeTypedObject(mTaskFragmentInfo, flags);
+        dest.writeTypedObject(mLeash, flags);
+    }
+
+    @NonNull
+    public static final Creator<TaskFragmentAppearedInfo> CREATOR =
+            new Creator<TaskFragmentAppearedInfo>() {
+                @Override
+                public TaskFragmentAppearedInfo createFromParcel(Parcel in) {
+                    return new TaskFragmentAppearedInfo(in);
+                }
+
+                @Override
+                public TaskFragmentAppearedInfo[] newArray(int size) {
+                    return new TaskFragmentAppearedInfo[size];
+                }
+            };
+
+    @Override
+    public String toString() {
+        return "TaskFragmentAppearedInfo{"
+                + " taskFragmentInfo=" + mTaskFragmentInfo
+                + "}";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/core/java/android/window/TaskFragmentCreationParams.aidl b/core/java/android/window/TaskFragmentCreationParams.aidl
new file mode 100644
index 0000000..fde5089
--- /dev/null
+++ b/core/java/android/window/TaskFragmentCreationParams.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+/**
+ * Data object for options to create TaskFragment with.
+ * @hide
+ */
+parcelable TaskFragmentCreationParams;
diff --git a/core/java/android/window/TaskFragmentCreationParams.java b/core/java/android/window/TaskFragmentCreationParams.java
new file mode 100644
index 0000000..e4d6a6c
--- /dev/null
+++ b/core/java/android/window/TaskFragmentCreationParams.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.app.WindowConfiguration.WindowingMode;
+
+import android.annotation.NonNull;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Data object for options to create TaskFragment with.
+ * @hide
+ */
+public final class TaskFragmentCreationParams implements Parcelable {
+
+    /** The organizer that will organize this TaskFragment. */
+    @NonNull
+    private final ITaskFragmentOrganizer mOrganizer;
+
+    /**
+     * Unique token assigned from the client organizer to identify the {@link TaskFragmentInfo} when
+     * a new TaskFragment is created with this option.
+     */
+    @NonNull
+    private final IBinder mFragmentToken;
+
+    /**
+     * Activity token used to identify the leaf Task to create the TaskFragment in. It has to belong
+     * to the same app as the root Activity of the target Task.
+     */
+    @NonNull
+    private final IBinder mOwnerToken;
+
+    /** The initial bounds of the TaskFragment. Fills parent if empty. */
+    @NonNull
+    private final Rect mInitialBounds = new Rect();
+
+    /** The initial windowing mode of the TaskFragment. Inherits from parent if not set. */
+    @WindowingMode
+    private int mWindowingMode = WINDOWING_MODE_UNDEFINED;
+
+    private TaskFragmentCreationParams(
+            @NonNull ITaskFragmentOrganizer organizer, @NonNull IBinder fragmentToken,
+            @NonNull IBinder ownerToken) {
+        mOrganizer = organizer;
+        mFragmentToken = fragmentToken;
+        mOwnerToken = ownerToken;
+    }
+
+    public ITaskFragmentOrganizer getOrganizer() {
+        return mOrganizer;
+    }
+
+    public IBinder getFragmentToken() {
+        return mFragmentToken;
+    }
+
+    public IBinder getOwnerToken() {
+        return mOwnerToken;
+    }
+
+    public Rect getInitialBounds() {
+        return mInitialBounds;
+    }
+
+    @WindowingMode
+    public int getWindowingMode() {
+        return mWindowingMode;
+    }
+
+    private TaskFragmentCreationParams(Parcel in) {
+        mOrganizer = ITaskFragmentOrganizer.Stub.asInterface(in.readStrongBinder());
+        mFragmentToken = in.readStrongBinder();
+        mOwnerToken = in.readStrongBinder();
+        mInitialBounds.readFromParcel(in);
+        mWindowingMode = in.readInt();
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeStrongInterface(mOrganizer);
+        dest.writeStrongBinder(mFragmentToken);
+        dest.writeStrongBinder(mOwnerToken);
+        mInitialBounds.writeToParcel(dest, flags);
+        dest.writeInt(mWindowingMode);
+    }
+
+    @NonNull
+    public static final Creator<TaskFragmentCreationParams> CREATOR =
+            new Creator<TaskFragmentCreationParams>() {
+                @Override
+                public TaskFragmentCreationParams createFromParcel(Parcel in) {
+                    return new TaskFragmentCreationParams(in);
+                }
+
+                @Override
+                public TaskFragmentCreationParams[] newArray(int size) {
+                    return new TaskFragmentCreationParams[size];
+                }
+            };
+
+    @Override
+    public String toString() {
+        return "TaskFragmentCreationParams{"
+                + " organizer=" + mOrganizer
+                + " fragmentToken=" + mFragmentToken
+                + " ownerToken=" + mOwnerToken
+                + " initialBounds=" + mInitialBounds
+                + " windowingMode=" + mWindowingMode
+                + "}";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /** Builder to construct the options to create TaskFragment with. */
+    public static class Builder {
+
+        @NonNull
+        private final ITaskFragmentOrganizer mOrganizer;
+
+        @NonNull
+        private final IBinder mFragmentToken;
+
+        @NonNull
+        private final IBinder mOwnerToken;
+
+        @NonNull
+        private final Rect mInitialBounds = new Rect();
+
+        @WindowingMode
+        private int mWindowingMode = WINDOWING_MODE_UNDEFINED;
+
+        public Builder(@NonNull ITaskFragmentOrganizer organizer, @NonNull IBinder fragmentToken,
+                @NonNull IBinder ownerToken) {
+            mOrganizer = organizer;
+            mFragmentToken = fragmentToken;
+            mOwnerToken = ownerToken;
+        }
+
+        /** Sets the initial bounds for the TaskFragment. */
+        public Builder setInitialBounds(@NonNull Rect bounds) {
+            mInitialBounds.set(bounds);
+            return this;
+        }
+
+        /** Sets the initial windowing mode for the TaskFragment. */
+        public Builder setWindowingMode(@WindowingMode int windowingMode) {
+            mWindowingMode = windowingMode;
+            return this;
+        }
+
+        /** Constructs the options to create TaskFragment with. */
+        public TaskFragmentCreationParams build() {
+            final TaskFragmentCreationParams result = new TaskFragmentCreationParams(
+                    mOrganizer, mFragmentToken, mOwnerToken);
+            result.mInitialBounds.set(mInitialBounds);
+            result.mWindowingMode = mWindowingMode;
+            return result;
+        }
+    }
+}
diff --git a/core/java/android/window/TaskFragmentInfo.aidl b/core/java/android/window/TaskFragmentInfo.aidl
new file mode 100644
index 0000000..461a736
--- /dev/null
+++ b/core/java/android/window/TaskFragmentInfo.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+/**
+ * Stores information about a particular TaskFragment.
+ * @hide
+ */
+parcelable TaskFragmentInfo;
diff --git a/core/java/android/window/TaskFragmentInfo.java b/core/java/android/window/TaskFragmentInfo.java
new file mode 100644
index 0000000..f864c91
--- /dev/null
+++ b/core/java/android/window/TaskFragmentInfo.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+import static android.app.WindowConfiguration.WindowingMode;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.Configuration;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Stores information about a particular TaskFragment.
+ * @hide
+ */
+public final class TaskFragmentInfo implements Parcelable {
+
+    /**
+     * Client assigned unique token in {@link TaskFragmentCreationParams#fragmentToken} to create
+     * this TaskFragment with.
+     */
+    @NonNull
+    private final IBinder mFragmentToken;
+
+    @NonNull
+    private final WindowContainerToken mToken;
+
+    @NonNull
+    private final Configuration mConfiguration = new Configuration();
+
+    /** Whether the TaskFragment contains any child Activity. */
+    private final boolean mIsEmpty;
+
+    /** Whether this TaskFragment is visible on the window hierarchy. */
+    private final boolean mIsVisible;
+
+    public TaskFragmentInfo(
+            @NonNull IBinder fragmentToken, @NonNull WindowContainerToken token,
+            @NonNull Configuration configuration, boolean isEmpty, boolean isVisible) {
+        if (fragmentToken == null) {
+            throw new IllegalArgumentException("Invalid TaskFragmentInfo.");
+        }
+        mFragmentToken = fragmentToken;
+        mToken = token;
+        mConfiguration.setTo(configuration);
+        mIsEmpty = isEmpty;
+        mIsVisible = isVisible;
+    }
+
+    public IBinder getFragmentToken() {
+        return mFragmentToken;
+    }
+
+    public WindowContainerToken getToken() {
+        return mToken;
+    }
+
+    public Configuration getConfiguration() {
+        return mConfiguration;
+    }
+
+    public boolean isEmpty() {
+        return mIsEmpty;
+    }
+
+    public boolean isVisible() {
+        return mIsVisible;
+    }
+
+    @WindowingMode
+    public int getWindowingMode() {
+        return mConfiguration.windowConfiguration.getWindowingMode();
+    }
+
+    /**
+     * Returns {@code true} if the parameters that are important for task fragment organizers are
+     * equal between this {@link TaskFragmentInfo} and {@param that}.
+     */
+    public boolean equalsForTaskFragmentOrganizer(@Nullable TaskFragmentInfo that) {
+        if (that == null) {
+            return false;
+        }
+
+        return mFragmentToken.equals(that.mFragmentToken)
+                && mToken.equals(that.mToken)
+                && mIsEmpty == that.mIsEmpty
+                && mIsVisible == that.mIsVisible
+                && getWindowingMode() == that.getWindowingMode();
+    }
+
+    private TaskFragmentInfo(Parcel in) {
+        mFragmentToken = in.readStrongBinder();
+        mToken = in.readTypedObject(WindowContainerToken.CREATOR);
+        mConfiguration.readFromParcel(in);
+        mIsEmpty = in.readBoolean();
+        mIsVisible = in.readBoolean();
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeStrongBinder(mFragmentToken);
+        dest.writeTypedObject(mToken, flags);
+        mConfiguration.writeToParcel(dest, flags);
+        dest.writeBoolean(mIsEmpty);
+        dest.writeBoolean(mIsVisible);
+    }
+
+    @NonNull
+    public static final Creator<TaskFragmentInfo> CREATOR =
+            new Creator<TaskFragmentInfo>() {
+                @Override
+                public TaskFragmentInfo createFromParcel(Parcel in) {
+                    return new TaskFragmentInfo(in);
+                }
+
+                @Override
+                public TaskFragmentInfo[] newArray(int size) {
+                    return new TaskFragmentInfo[size];
+                }
+            };
+
+    @Override
+    public String toString() {
+        return "TaskFragmentInfo{"
+                + " fragmentToken=" + mFragmentToken
+                + " token=" + mToken
+                + " isEmpty=" + mIsEmpty
+                + " isVisible=" + mIsVisible
+                + "}";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java
new file mode 100644
index 0000000..78c55a2
--- /dev/null
+++ b/core/java/android/window/TaskFragmentOrganizer.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+import android.annotation.CallSuper;
+import android.annotation.NonNull;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Interface for WindowManager to delegate control of {@link com.android.server.wm.TaskFragment}.
+ * @hide
+ */
+public class TaskFragmentOrganizer extends WindowOrganizer {
+
+    /**
+     * Key to the exception in {@link Bundle} in {@link ITaskFragmentOrganizer#onTaskFragmentError}.
+     */
+    private static final String KEY_ERROR_CALLBACK_EXCEPTION = "fragment_exception";
+
+    /**
+     * Creates a {@link Bundle} with an exception that can be passed to
+     * {@link ITaskFragmentOrganizer#onTaskFragmentError}.
+     */
+    public static Bundle putExceptionInBundle(@NonNull Throwable exception) {
+        final Bundle exceptionBundle = new Bundle();
+        exceptionBundle.putSerializable(KEY_ERROR_CALLBACK_EXCEPTION, exception);
+        return exceptionBundle;
+    }
+
+    /**
+     * Callbacks from WM Core are posted on this executor.
+     */
+    private final Executor mExecutor;
+
+    public TaskFragmentOrganizer(@NonNull Executor executor) {
+        mExecutor = executor;
+    }
+
+    /**
+     * Gets the executor to run callbacks on.
+     */
+    @NonNull
+    public Executor getExecutor() {
+        return mExecutor;
+    }
+
+    /**
+     * Registers a TaskFragmentOrganizer to manage TaskFragments.
+     */
+    @CallSuper
+    public void registerOrganizer() {
+        try {
+            getController().registerOrganizer(mInterface);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Unregisters a previously registered TaskFragmentOrganizer.
+     */
+    @CallSuper
+    public void unregisterOrganizer() {
+        try {
+            getController().unregisterOrganizer(mInterface);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** Called when a TaskFragment is created and organized by this organizer. */
+    public void onTaskFragmentAppeared(
+            @NonNull TaskFragmentAppearedInfo taskFragmentAppearedInfo) {}
+
+    /** Called when the status of an organized TaskFragment is changed. */
+    public void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo) {}
+
+    /** Called when an organized TaskFragment is removed. */
+    public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) {}
+
+    /**
+     * Called when the parent leaf Task of organized TaskFragments is changed.
+     * When the leaf Task is changed, the organizer may want to update the TaskFragments in one
+     * transaction.
+     *
+     * For case like screen size change, it will trigger onTaskFragmentParentInfoChanged with new
+     * Task bounds, but may not trigger onTaskFragmentInfoChanged because there can be an override
+     * bounds.
+     */
+    public void onTaskFragmentParentInfoChanged(
+            @NonNull IBinder fragmentToken, @NonNull Configuration parentConfig) {}
+
+    /**
+     * Called when the {@link WindowContainerTransaction} created with
+     * {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)} failed on the server side.
+     *
+     * @param errorCallbackToken    token set in
+     *                             {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)}
+     * @param exception             exception from the server side.
+     */
+    public void onTaskFragmentError(
+            @NonNull IBinder errorCallbackToken, @NonNull Throwable exception) {}
+
+    private final ITaskFragmentOrganizer mInterface = new ITaskFragmentOrganizer.Stub() {
+        @Override
+        public void onTaskFragmentAppeared(@NonNull TaskFragmentAppearedInfo taskFragmentInfo) {
+            mExecutor.execute(
+                    () -> TaskFragmentOrganizer.this.onTaskFragmentAppeared(taskFragmentInfo));
+        }
+
+        @Override
+        public void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo) {
+            mExecutor.execute(
+                    () -> TaskFragmentOrganizer.this.onTaskFragmentInfoChanged(taskFragmentInfo));
+        }
+
+        @Override
+        public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) {
+            mExecutor.execute(
+                    () -> TaskFragmentOrganizer.this.onTaskFragmentVanished(taskFragmentInfo));
+        }
+
+        @Override
+        public void onTaskFragmentParentInfoChanged(
+                @NonNull IBinder fragmentToken, @NonNull Configuration parentConfig) {
+            mExecutor.execute(
+                    () -> TaskFragmentOrganizer.this.onTaskFragmentParentInfoChanged(
+                            fragmentToken, parentConfig));
+        }
+
+        @Override
+        public void onTaskFragmentError(
+                @NonNull IBinder errorCallbackToken, @NonNull Bundle exceptionBundle) {
+            mExecutor.execute(() -> TaskFragmentOrganizer.this.onTaskFragmentError(
+                    errorCallbackToken,
+                    (Throwable) exceptionBundle.getSerializable(KEY_ERROR_CALLBACK_EXCEPTION)));
+        }
+    };
+
+    private ITaskFragmentOrganizerController getController() {
+        try {
+            return getWindowOrganizerController().getTaskFragmentOrganizerController();
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+}
diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java
index c7c91cd..8fa0110 100644
--- a/core/java/android/window/TaskOrganizer.java
+++ b/core/java/android/window/TaskOrganizer.java
@@ -222,6 +222,7 @@
         }
     }
 
+
     /**
      * Restarts the top activity in the given task by killing its process if it is visible.
      * @hide
diff --git a/core/java/android/window/TransitionFilter.java b/core/java/android/window/TransitionFilter.java
index 141f47b..6351b03 100644
--- a/core/java/android/window/TransitionFilter.java
+++ b/core/java/android/window/TransitionFilter.java
@@ -18,6 +18,7 @@
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.WindowConfiguration;
@@ -33,6 +34,18 @@
  */
 public final class TransitionFilter implements Parcelable {
 
+    /** The associated requirement doesn't care about the z-order. */
+    public static final int CONTAINER_ORDER_ANY = 0;
+    /** The associated requirement only matches the top-most (z-order) container. */
+    public static final int CONTAINER_ORDER_TOP = 1;
+
+    /** @hide */
+    @IntDef(prefix = { "CONTAINER_ORDER_" }, value = {
+            CONTAINER_ORDER_ANY,
+            CONTAINER_ORDER_TOP,
+    })
+    public @interface ContainerOrder {}
+
     /**
      * When non-null: this is a list of transition types that this filter applies to. This filter
      * will fail for transitions that aren't one of these types.
@@ -126,6 +139,7 @@
     public static final class Requirement implements Parcelable {
         public int mActivityType = ACTIVITY_TYPE_UNDEFINED;
         public int[] mModes = null;
+        public @ContainerOrder int mOrder = CONTAINER_ORDER_ANY;
 
         public Requirement() {
         }
@@ -133,6 +147,7 @@
         private Requirement(Parcel in) {
             mActivityType = in.readInt();
             mModes = in.createIntArray();
+            mOrder = in.readInt();
         }
 
         /** Go through changes and find if at-least one change matches this filter */
@@ -143,6 +158,9 @@
                     // Only look at independent animating windows.
                     continue;
                 }
+                if (mOrder == CONTAINER_ORDER_TOP && i > 0) {
+                    continue;
+                }
                 if (mActivityType != ACTIVITY_TYPE_UNDEFINED) {
                     if (change.getTaskInfo() == null
                             || change.getTaskInfo().getActivityType() != mActivityType) {
@@ -166,7 +184,7 @@
 
         /** Check if the request matches this filter. It may generate false positives */
         boolean matches(@NonNull TransitionRequestInfo request) {
-            // Can't check modes since the transition hasn't been built at this point.
+            // Can't check modes/order since the transition hasn't been built at this point.
             if (mActivityType == ACTIVITY_TYPE_UNDEFINED) return true;
             return request.getTriggerTask() != null
                     && request.getTriggerTask().getActivityType() == mActivityType;
@@ -177,6 +195,7 @@
         public void writeToParcel(@NonNull Parcel dest, int flags) {
             dest.writeInt(mActivityType);
             dest.writeIntArray(mModes);
+            dest.writeInt(mOrder);
         }
 
         @NonNull
@@ -209,7 +228,17 @@
                     out.append((i == 0 ? "" : ",") + TransitionInfo.modeToString(mModes[i]));
                 }
             }
-            return out.append("]}").toString();
+            out.append("]").toString();
+            out.append(" order=" + containerOrderToString(mOrder));
+            return out.toString();
         }
     }
+
+    private static String containerOrderToString(int order) {
+        switch (order) {
+            case CONTAINER_ORDER_ANY: return "ANY";
+            case CONTAINER_ORDER_TOP: return "TOP";
+        }
+        return "UNKNOWN(" + order + ")";
+    }
 }
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 23b8ee4..6430394 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -16,6 +16,12 @@
 
 package android.window;
 
+import static android.app.ActivityOptions.ANIM_CLIP_REVEAL;
+import static android.app.ActivityOptions.ANIM_CUSTOM;
+import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
+import static android.app.ActivityOptions.ANIM_SCALE_UP;
+import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN;
+import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP;
 import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
@@ -31,6 +37,7 @@
 import android.app.ActivityManager;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.view.Surface;
@@ -102,6 +109,8 @@
     private SurfaceControl mRootLeash;
     private final Point mRootOffset = new Point();
 
+    private AnimationOptions mOptions;
+
     /** @hide */
     public TransitionInfo(@WindowManager.TransitionOldType int type,
             @WindowManager.TransitionFlags int flags) {
@@ -116,6 +125,7 @@
         mRootLeash = new SurfaceControl();
         mRootLeash.readFromParcel(in);
         mRootOffset.readFromParcel(in);
+        mOptions = in.readTypedObject(AnimationOptions.CREATOR);
     }
 
     @Override
@@ -126,6 +136,7 @@
         dest.writeList(mChanges);
         mRootLeash.writeToParcel(dest, flags);
         mRootOffset.writeToParcel(dest, flags);
+        dest.writeTypedObject(mOptions, flags);
     }
 
     @NonNull
@@ -154,6 +165,10 @@
         mRootOffset.set(offsetLeft, offsetTop);
     }
 
+    public void setAnimationOptions(AnimationOptions options) {
+        mOptions = options;
+    }
+
     public int getType() {
         return mType;
     }
@@ -182,6 +197,10 @@
         return mRootOffset;
     }
 
+    public AnimationOptions getAnimationOptions() {
+        return mOptions;
+    }
+
     @NonNull
     public List<Change> getChanges() {
         return mChanges;
@@ -484,4 +503,146 @@
                     + mStartRotation + "->" + mEndRotation + "}";
         }
     }
+
+    /** Represents animation options during a transition */
+    public static final class AnimationOptions implements Parcelable {
+
+        private int mType;
+        private int mEnterResId;
+        private int mExitResId;
+        private boolean mOverrideTaskTransition;
+        private String mPackageName;
+        private final Rect mTransitionBounds = new Rect();
+        private HardwareBuffer mThumbnail;
+
+        private AnimationOptions(int type) {
+            mType = type;
+        }
+
+        public AnimationOptions(Parcel in) {
+            mType = in.readInt();
+            mEnterResId = in.readInt();
+            mExitResId = in.readInt();
+            mOverrideTaskTransition = in.readBoolean();
+            mPackageName = in.readString();
+            mTransitionBounds.readFromParcel(in);
+            mThumbnail = in.readTypedObject(HardwareBuffer.CREATOR);
+        }
+
+        public static AnimationOptions makeCustomAnimOptions(String packageName, int enterResId,
+                int exitResId, boolean overrideTaskTransition) {
+            AnimationOptions options = new AnimationOptions(ANIM_CUSTOM);
+            options.mPackageName = packageName;
+            options.mEnterResId = enterResId;
+            options.mExitResId = exitResId;
+            options.mOverrideTaskTransition = overrideTaskTransition;
+            return options;
+        }
+
+        public static AnimationOptions makeClipRevealAnimOptions(int startX, int startY, int width,
+                int height) {
+            AnimationOptions options = new AnimationOptions(ANIM_CLIP_REVEAL);
+            options.mTransitionBounds.set(startX, startY, startX + width, startY + height);
+            return options;
+        }
+
+        public static AnimationOptions makeScaleUpAnimOptions(int startX, int startY, int width,
+                int height) {
+            AnimationOptions options = new AnimationOptions(ANIM_SCALE_UP);
+            options.mTransitionBounds.set(startX, startY, startX + width, startY + height);
+            return options;
+        }
+
+        public static AnimationOptions makeThumnbnailAnimOptions(HardwareBuffer srcThumb,
+                int startX, int startY, boolean scaleUp) {
+            AnimationOptions options = new AnimationOptions(
+                    scaleUp ? ANIM_THUMBNAIL_SCALE_UP : ANIM_THUMBNAIL_SCALE_DOWN);
+            options.mTransitionBounds.set(startX, startY, startX, startY);
+            options.mThumbnail = srcThumb;
+            return options;
+        }
+
+        public static AnimationOptions makeCrossProfileAnimOptions() {
+            AnimationOptions options = new AnimationOptions(ANIM_OPEN_CROSS_PROFILE_APPS);
+            return options;
+        }
+
+        public int getType() {
+            return mType;
+        }
+
+        public int getEnterResId() {
+            return mEnterResId;
+        }
+
+        public int getExitResId() {
+            return mExitResId;
+        }
+
+        public boolean getOverrideTaskTransition() {
+            return mOverrideTaskTransition;
+        }
+
+        public String getPackageName() {
+            return mPackageName;
+        }
+
+        public Rect getTransitionBounds() {
+            return mTransitionBounds;
+        }
+
+        public HardwareBuffer getThumbnail() {
+            return mThumbnail;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(mType);
+            dest.writeInt(mEnterResId);
+            dest.writeInt(mExitResId);
+            dest.writeBoolean(mOverrideTaskTransition);
+            dest.writeString(mPackageName);
+            mTransitionBounds.writeToParcel(dest, flags);
+            dest.writeTypedObject(mThumbnail, flags);
+        }
+
+        @NonNull
+        public static final Creator<AnimationOptions> CREATOR =
+                new Creator<AnimationOptions>() {
+                    @Override
+                    public AnimationOptions createFromParcel(Parcel in) {
+                        return new AnimationOptions(in);
+                    }
+
+                    @Override
+                    public AnimationOptions[] newArray(int size) {
+                        return new AnimationOptions[size];
+                    }
+                };
+
+        /** @hide */
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @NonNull
+        private static String typeToString(int mode) {
+            switch(mode) {
+                case ANIM_CUSTOM: return "ANIM_CUSTOM";
+                case ANIM_CLIP_REVEAL: return "ANIM_CLIP_REVEAL";
+                case ANIM_SCALE_UP: return "ANIM_SCALE_UP";
+                case ANIM_THUMBNAIL_SCALE_UP: return "ANIM_THUMBNAIL_SCALE_UP";
+                case ANIM_THUMBNAIL_SCALE_DOWN: return "ANIM_THUMBNAIL_SCALE_DOWN";
+                case ANIM_OPEN_CROSS_PROFILE_APPS: return "ANIM_OPEN_CROSS_PROFILE_APPS";
+                default: return "<unknown:" + mode + ">";
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "{ AnimationOtions type= " + typeToString(mType) + " package=" + mPackageName
+                    + " override=" + mOverrideTaskTransition + " b=" + mTransitionBounds + "}";
+        }
+    }
 }
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index c0af572..9c512ad 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.annotation.TestApi;
 import android.app.WindowConfiguration;
+import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
 import android.graphics.Rect;
@@ -48,11 +49,15 @@
     // Flat list because re-order operations are order-dependent
     private final ArrayList<HierarchyOp> mHierarchyOps = new ArrayList<>();
 
+    @Nullable
+    private IBinder mErrorCallbackToken;
+
     public WindowContainerTransaction() {}
 
     private WindowContainerTransaction(Parcel in) {
         in.readMap(mChanges, null /* loader */);
         in.readList(mHierarchyOps, null /* loader */);
+        mErrorCallbackToken = in.readStrongBinder();
     }
 
     private Change getOrCreateChange(IBinder token) {
@@ -325,7 +330,8 @@
 
     /**
      * Sets to containers adjacent to each other. Containers below two visible adjacent roots will
-     * be made invisible. This currently only applies to Task containers created by organizer.
+     * be made invisible. This currently only applies to TaskFragment containers created by
+     * organizer.
      * @param root1 the first root.
      * @param root2 the second root.
      */
@@ -378,6 +384,119 @@
     }
 
     /**
+     * Creates a new TaskFragment with the given options.
+     * @param taskFragmentOptions the options used to create the TaskFragment.
+     * @hide
+     */
+    @NonNull
+    public WindowContainerTransaction createTaskFragment(
+            @NonNull TaskFragmentCreationParams taskFragmentOptions) {
+        final HierarchyOp hierarchyOp =
+                new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT)
+                        .setTaskFragmentCreationOptions(taskFragmentOptions)
+                        .build();
+        mHierarchyOps.add(hierarchyOp);
+        return this;
+    }
+
+    /**
+     * Deletes an existing TaskFragment. Any remaining activities below it will be destroyed.
+     * @param taskFragment  the TaskFragment to be removed.
+     * @hide
+     */
+    @NonNull
+    public WindowContainerTransaction deleteTaskFragment(
+            @NonNull WindowContainerToken taskFragment) {
+        final HierarchyOp hierarchyOp =
+                new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT)
+                        .setContainer(taskFragment.asBinder())
+                        .build();
+        mHierarchyOps.add(hierarchyOp);
+        return this;
+    }
+
+    /**
+     * Starts an activity in the TaskFragment.
+     * @param fragmentToken client assigned unique token to create TaskFragment with specified in
+     *                      {@link TaskFragmentCreationParams#fragmentToken}.
+     * @param activityIntent    intent to start the activity.
+     * @param activityOptions    ActivityOptions to start the activity with.
+     * @see android.content.Context#startActivity(Intent, Bundle).
+     * @hide
+     */
+    @NonNull
+    public WindowContainerTransaction startActivityInTaskFragment(
+            @NonNull IBinder fragmentToken, @NonNull Intent activityIntent,
+            @Nullable Bundle activityOptions) {
+        final HierarchyOp hierarchyOp =
+                new HierarchyOp.Builder(
+                        HierarchyOp.HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT)
+                        .setContainer(fragmentToken)
+                        .setActivityIntent(activityIntent)
+                        .setLaunchOptions(activityOptions)
+                        .build();
+        mHierarchyOps.add(hierarchyOp);
+        return this;
+    }
+
+    /**
+     * Moves an activity into the TaskFragment.
+     * @param fragmentToken client assigned unique token to create TaskFragment with specified in
+     *                      {@link TaskFragmentCreationParams#fragmentToken}.
+     * @param activityToken activity to be reparented.
+     * @hide
+     */
+    @NonNull
+    public WindowContainerTransaction reparentActivityToTaskFragment(
+            @NonNull IBinder fragmentToken, @NonNull IBinder activityToken) {
+        final HierarchyOp hierarchyOp =
+                new HierarchyOp.Builder(
+                        HierarchyOp.HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT)
+                        .setReparentContainer(fragmentToken)
+                        .setContainer(activityToken)
+                        .build();
+        mHierarchyOps.add(hierarchyOp);
+        return this;
+    }
+
+    /**
+     * Reparents all children of one TaskFragment to another.
+     * @param oldParent children of this TaskFragment will be reparented.
+     * @param newParent the new parent TaskFragment to move the children to. If {@code null}, the
+     *                  children will be moved to the leaf Task.
+     * @hide
+     */
+    @NonNull
+    public WindowContainerTransaction reparentChildren(
+            @NonNull WindowContainerToken oldParent,
+            @Nullable WindowContainerToken newParent) {
+        final HierarchyOp hierarchyOp =
+                new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_REPARENT_CHILDREN)
+                        .setContainer(oldParent.asBinder())
+                        .setReparentContainer(newParent.asBinder())
+                        .build();
+        mHierarchyOps.add(hierarchyOp);
+        return this;
+    }
+
+    /**
+     * When this {@link WindowContainerTransaction} failed to finish on the server side, it will
+     * trigger callback with this {@param errorCallbackToken}.
+     * @param errorCallbackToken    client provided token that will be passed back as parameter in
+     *                              the callback if there is an error on the server side.
+     * @see ITaskFragmentOrganizer#onTaskFragmentError
+     * @hide
+     */
+    @NonNull
+    public WindowContainerTransaction setErrorCallbackToken(@NonNull IBinder errorCallbackToken) {
+        if (mErrorCallbackToken != null) {
+            throw new IllegalStateException("Can't set multiple error token for one transaction.");
+        }
+        mErrorCallbackToken = errorCallbackToken;
+        return this;
+    }
+
+    /**
      * Merges another WCT into this one.
      * @param transfer When true, this will transfer everything from other potentially leaving
      *                 other in an unusable state. When false, other is left alone, but
@@ -398,6 +517,13 @@
             mHierarchyOps.add(transfer ? other.mHierarchyOps.get(i)
                     : new HierarchyOp(other.mHierarchyOps.get(i)));
         }
+        if (mErrorCallbackToken != null && other.mErrorCallbackToken != null && mErrorCallbackToken
+                != other.mErrorCallbackToken) {
+            throw new IllegalArgumentException("Can't merge two WCT with different error token");
+        }
+        mErrorCallbackToken = mErrorCallbackToken != null
+                ? mErrorCallbackToken
+                : other.mErrorCallbackToken;
     }
 
     /** @hide */
@@ -415,11 +541,17 @@
         return mHierarchyOps;
     }
 
+    /** @hide */
+    @Nullable
+    public IBinder getErrorCallbackToken() {
+        return mErrorCallbackToken;
+    }
+
     @Override
     @NonNull
     public String toString() {
         return "WindowContainerTransaction { changes = " + mChanges + " hops = " + mHierarchyOps
-                + " }";
+                + " errorCallbackToken=" + mErrorCallbackToken + " }";
     }
 
     @Override
@@ -427,6 +559,7 @@
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeMap(mChanges);
         dest.writeList(mHierarchyOps);
+        dest.writeStrongBinder(mErrorCallbackToken);
     }
 
     @Override
@@ -705,6 +838,11 @@
         public static final int HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS = 4;
         public static final int HIERARCHY_OP_TYPE_LAUNCH_TASK = 5;
         public static final int HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT = 6;
+        public static final int HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT = 7;
+        public static final int HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT = 8;
+        public static final int HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT = 9;
+        public static final int HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT = 10;
+        public static final int HIERARCHY_OP_TYPE_REPARENT_CHILDREN = 11;
 
         // The following key(s) are for use with mLaunchOptions:
         // When launching a task (eg. from recents), this is the taskId to be launched.
@@ -713,75 +851,98 @@
         private final int mType;
 
         // Container we are performing the operation on.
-        private final IBinder mContainer;
+        @Nullable
+        private IBinder mContainer;
 
         // If this is same as mContainer, then only change position, don't reparent.
-        private final IBinder mReparent;
+        @Nullable
+        private IBinder mReparent;
 
         // Moves/reparents to top of parent when {@code true}, otherwise moves/reparents to bottom.
-        private final boolean mToTop;
+        private boolean mToTop;
 
-        final private int[]  mWindowingModes;
-        final private int[] mActivityTypes;
+        @Nullable
+        private int[]  mWindowingModes;
 
-        private final Bundle mLaunchOptions;
+        @Nullable
+        private int[] mActivityTypes;
+
+        @Nullable
+        private Bundle mLaunchOptions;
+
+        @Nullable
+        private Intent mActivityIntent;
+
+        // Used as options for WindowContainerTransaction#createTaskFragment().
+        @Nullable
+        private TaskFragmentCreationParams mTaskFragmentCreationOptions;
 
         public static HierarchyOp createForReparent(
                 @NonNull IBinder container, @Nullable IBinder reparent, boolean toTop) {
-            return new HierarchyOp(HIERARCHY_OP_TYPE_REPARENT,
-                    container, reparent, null, null, toTop, null);
+            return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_REPARENT)
+                    .setContainer(container)
+                    .setReparentContainer(reparent)
+                    .setToTop(toTop)
+                    .build();
         }
 
         public static HierarchyOp createForReorder(@NonNull IBinder container, boolean toTop) {
-            return new HierarchyOp(HIERARCHY_OP_TYPE_REORDER,
-                    container, container, null, null, toTop, null);
+            return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_REORDER)
+                    .setContainer(container)
+                    .setReparentContainer(container)
+                    .setToTop(toTop)
+                    .build();
         }
 
         public static HierarchyOp createForChildrenTasksReparent(IBinder currentParent,
                 IBinder newParent, int[] windowingModes, int[] activityTypes, boolean onTop) {
-            return new HierarchyOp(HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT,
-                    currentParent, newParent, windowingModes, activityTypes, onTop, null);
+            return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT)
+                    .setContainer(currentParent)
+                    .setReparentContainer(newParent)
+                    .setWindowingModes(windowingModes)
+                    .setActivityTypes(activityTypes)
+                    .setToTop(onTop)
+                    .build();
         }
 
         public static HierarchyOp createForSetLaunchRoot(IBinder container,
                 int[] windowingModes, int[] activityTypes) {
-            return new HierarchyOp(HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT,
-                    container, null, windowingModes, activityTypes, false, null);
+            return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT)
+                    .setContainer(container)
+                    .setWindowingModes(windowingModes)
+                    .setActivityTypes(activityTypes)
+                    .build();
         }
 
         public static HierarchyOp createForAdjacentRoots(IBinder root1, IBinder root2) {
-            return new HierarchyOp(HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS,
-                    root1, root2, null, null, false, null);
+            return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS)
+                    .setContainer(root1)
+                    .setReparentContainer(root2)
+                    .build();
         }
 
         /** Create a hierarchy op for launching a task. */
         public static HierarchyOp createForTaskLaunch(int taskId, @Nullable Bundle options) {
             final Bundle fullOptions = options == null ? new Bundle() : options;
             fullOptions.putInt(LAUNCH_KEY_TASK_ID, taskId);
-            return new HierarchyOp(HIERARCHY_OP_TYPE_LAUNCH_TASK, null, null, null, null, true,
-                    fullOptions);
+            return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_LAUNCH_TASK)
+                    .setToTop(true)
+                    .setLaunchOptions(fullOptions)
+                    .build();
         }
 
         /** Create a hierarchy op for setting launch adjacent flag root. */
         public static HierarchyOp createForSetLaunchAdjacentFlagRoot(IBinder container,
                 boolean clearRoot) {
-            return new HierarchyOp(HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT, container, null,
-                    null, null, clearRoot, null);
+            return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT)
+                    .setContainer(container)
+                    .setToTop(clearRoot)
+                    .build();
         }
 
-
-        private HierarchyOp(int type, @Nullable IBinder container, @Nullable IBinder reparent,
-                int[] windowingModes, int[] activityTypes, boolean toTop,
-                @Nullable Bundle launchOptions) {
+        /** Only creates through {@link Builder}. */
+        private HierarchyOp(int type) {
             mType = type;
-            mContainer = container;
-            mReparent = reparent;
-            mWindowingModes = windowingModes != null ?
-                    Arrays.copyOf(windowingModes, windowingModes.length) : null;
-            mActivityTypes = activityTypes != null ?
-                    Arrays.copyOf(activityTypes, activityTypes.length) : null;
-            mToTop = toTop;
-            mLaunchOptions = launchOptions;
         }
 
         public HierarchyOp(@NonNull HierarchyOp copy) {
@@ -792,6 +953,8 @@
             mWindowingModes = copy.mWindowingModes;
             mActivityTypes = copy.mActivityTypes;
             mLaunchOptions = copy.mLaunchOptions;
+            mActivityIntent = copy.mActivityIntent;
+            mTaskFragmentCreationOptions = copy.mTaskFragmentCreationOptions;
         }
 
         protected HierarchyOp(Parcel in) {
@@ -802,6 +965,8 @@
             mWindowingModes = in.createIntArray();
             mActivityTypes = in.createIntArray();
             mLaunchOptions = in.readBundle();
+            mActivityIntent = in.readTypedObject(Intent.CREATOR);
+            mTaskFragmentCreationOptions = in.readTypedObject(TaskFragmentCreationParams.CREATOR);
         }
 
         public int getType() {
@@ -844,6 +1009,16 @@
             return mLaunchOptions;
         }
 
+        @Nullable
+        public Intent getActivityIntent() {
+            return mActivityIntent;
+        }
+
+        @Nullable
+        public TaskFragmentCreationParams getTaskFragmentCreationOptions() {
+            return mTaskFragmentCreationOptions;
+        }
+
         @Override
         public String toString() {
             switch (mType) {
@@ -868,6 +1043,19 @@
                 case HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT:
                     return "{SetAdjacentFlagRoot: container=" + mContainer + " clearRoot=" + mToTop
                             + "}";
+                case HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT:
+                    return "{CreateTaskFragment: options=" + mTaskFragmentCreationOptions + "}";
+                case HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT:
+                    return "{DeleteTaskFragment: taskFragment=" + mContainer + "}";
+                case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT:
+                    return "{StartActivityInTaskFragment: fragmentToken=" + mContainer + " intent="
+                            + mActivityIntent + " options=" + mLaunchOptions + "}";
+                case HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT:
+                    return "{ReparentActivityToTaskFragment: fragmentToken=" + mReparent
+                            + " activity=" + mContainer + "}";
+                case HIERARCHY_OP_TYPE_REPARENT_CHILDREN:
+                    return "{ReparentChildren: oldParent=" + mContainer + " newParent=" + mReparent
+                            + "}";
                 default:
                     return "{mType=" + mType + " container=" + mContainer + " reparent=" + mReparent
                             + " mToTop=" + mToTop + " mWindowingMode=" + mWindowingModes
@@ -884,6 +1072,8 @@
             dest.writeIntArray(mWindowingModes);
             dest.writeIntArray(mActivityTypes);
             dest.writeBundle(mLaunchOptions);
+            dest.writeTypedObject(mActivityIntent, flags);
+            dest.writeTypedObject(mTaskFragmentCreationOptions, flags);
         }
 
         @Override
@@ -902,5 +1092,96 @@
                 return new HierarchyOp[size];
             }
         };
+
+        private static class Builder {
+
+            private final int mType;
+
+            @Nullable
+            private IBinder mContainer;
+
+            @Nullable
+            private IBinder mReparent;
+
+            private boolean mToTop;
+
+            @Nullable
+            private int[]  mWindowingModes;
+
+            @Nullable
+            private int[] mActivityTypes;
+
+            @Nullable
+            private Bundle mLaunchOptions;
+
+            @Nullable
+            private Intent mActivityIntent;
+
+            @Nullable
+            private TaskFragmentCreationParams mTaskFragmentCreationOptions;
+
+            Builder(int type) {
+                mType = type;
+            }
+
+            Builder setContainer(@Nullable IBinder container) {
+                mContainer = container;
+                return this;
+            }
+
+            Builder setReparentContainer(@Nullable IBinder reparentContainer) {
+                mReparent = reparentContainer;
+                return this;
+            }
+
+            Builder setToTop(boolean toTop) {
+                mToTop = toTop;
+                return this;
+            }
+
+            Builder setWindowingModes(@Nullable int[] windowingModes) {
+                mWindowingModes = windowingModes;
+                return this;
+            }
+
+            Builder setActivityTypes(@Nullable int[] activityTypes) {
+                mActivityTypes = activityTypes;
+                return this;
+            }
+
+            Builder setLaunchOptions(@Nullable Bundle launchOptions) {
+                mLaunchOptions = launchOptions;
+                return this;
+            }
+
+            Builder setActivityIntent(@Nullable Intent activityIntent) {
+                mActivityIntent = activityIntent;
+                return this;
+            }
+
+            Builder setTaskFragmentCreationOptions(
+                    @Nullable TaskFragmentCreationParams taskFragmentCreationOptions) {
+                mTaskFragmentCreationOptions = taskFragmentCreationOptions;
+                return this;
+            }
+
+            HierarchyOp build() {
+                final HierarchyOp hierarchyOp = new HierarchyOp(mType);
+                hierarchyOp.mContainer = mContainer;
+                hierarchyOp.mReparent = mReparent;
+                hierarchyOp.mWindowingModes = mWindowingModes != null
+                        ? Arrays.copyOf(mWindowingModes, mWindowingModes.length)
+                        : null;
+                hierarchyOp.mActivityTypes = mActivityTypes != null
+                        ? Arrays.copyOf(mActivityTypes, mActivityTypes.length)
+                        : null;
+                hierarchyOp.mToTop = mToTop;
+                hierarchyOp.mLaunchOptions = mLaunchOptions;
+                hierarchyOp.mActivityIntent = mActivityIntent;
+                hierarchyOp.mTaskFragmentCreationOptions = mTaskFragmentCreationOptions;
+
+                return hierarchyOp;
+            }
+        }
     }
 }
diff --git a/core/java/android/window/WindowContext.java b/core/java/android/window/WindowContext.java
index 901625b..5d40085 100644
--- a/core/java/android/window/WindowContext.java
+++ b/core/java/android/window/WindowContext.java
@@ -26,7 +26,6 @@
 import android.content.ContextWrapper;
 import android.content.res.Configuration;
 import android.os.Bundle;
-import android.os.IBinder;
 import android.view.WindowManager;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -57,8 +56,14 @@
      *
      * @param base Base {@link Context} for this new instance.
      * @param type Window type to be used with this context.
-     * @param options A bundle used to pass window-related options.
-     *
+     * @param options A bundle used to pass window-related options. For example, on device with
+     *                multiple DisplayAreaGroups, one may specify the RootDisplayArea for the window
+     *                using {@link DisplayAreaOrganizer#KEY_ROOT_DISPLAY_AREA_ID} in the options.
+     *                Example usage:
+     *                Bundle options = new Bundle();
+     *                options.put(KEY_ROOT_DISPLAY_AREA_ID, displayAreaInfo.rootDisplayAreaId);
+     *                Context windowContext = context.createWindowContext(display, type, options);
+     * @see DisplayAreaInfo#rootDisplayAreaId
      * @hide
      */
     public WindowContext(@NonNull Context base, int type, @Nullable Bundle options) {
@@ -67,7 +72,7 @@
         mType = type;
         mOptions = options;
         mWindowManager = createWindowContextWindowManager(this);
-        IBinder token = getWindowContextToken();
+        WindowTokenClient token = (WindowTokenClient) getWindowContextToken();
         mController = new WindowContextController(token);
 
         Reference.reachabilityFence(this);
diff --git a/core/java/android/window/WindowContextController.java b/core/java/android/window/WindowContextController.java
index d84f571..5aa6233 100644
--- a/core/java/android/window/WindowContextController.java
+++ b/core/java/android/window/WindowContextController.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -46,7 +47,7 @@
     @VisibleForTesting
     public boolean mAttachedToDisplayArea;
     @NonNull
-    private final IBinder mToken;
+    private final WindowTokenClient mToken;
 
     /**
      * Window Context Controller constructor
@@ -54,14 +55,13 @@
      * @param token The token used to attach to a window manager node. It is usually from
      *              {@link Context#getWindowContextToken()}.
      */
-    public WindowContextController(@NonNull IBinder token) {
-        mToken = token;
-        mWms = WindowManagerGlobal.getWindowManagerService();
+    public WindowContextController(@NonNull WindowTokenClient token) {
+        this(token, WindowManagerGlobal.getWindowManagerService());
     }
 
     /** Used for test only. DO NOT USE it in production code. */
     @VisibleForTesting
-    public WindowContextController(@NonNull IBinder token, IWindowManager mockWms) {
+    public WindowContextController(@NonNull WindowTokenClient token, IWindowManager mockWms) {
         mToken = token;
         mWms = mockWms;
     }
@@ -81,8 +81,15 @@
                     + "a DisplayArea once.");
         }
         try {
-            mAttachedToDisplayArea = mWms.attachWindowContextToDisplayArea(mToken, type, displayId,
-                    options);
+            final Configuration configuration = mWms.attachWindowContextToDisplayArea(mToken, type,
+                    displayId, options);
+            if (configuration != null) {
+                mAttachedToDisplayArea = true;
+                // Send the DisplayArea's configuration to WindowContext directly instead of
+                // waiting for dispatching from WMS.
+                mToken.onConfigurationChanged(configuration, displayId,
+                        false /* shouldReportConfigChange */);
+            }
         }  catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/window/WindowProviderService.java b/core/java/android/window/WindowProviderService.java
index b8619fb..5171adf 100644
--- a/core/java/android/window/WindowProviderService.java
+++ b/core/java/android/window/WindowProviderService.java
@@ -36,7 +36,6 @@
 import android.view.WindowManager.LayoutParams.WindowType;
 import android.view.WindowManagerImpl;
 
-// TODO(b/159767464): handle #onConfigurationChanged(Configuration)
 /**
  * A {@link Service} responsible for showing a non-activity window, such as software keyboards or
  * accessibility overlay windows. This {@link Service} has similar behavior to
@@ -54,6 +53,7 @@
     private final WindowTokenClient mWindowToken = new WindowTokenClient();
     private final WindowContextController mController = new WindowContextController(mWindowToken);
     private WindowManager mWindowManager;
+    private boolean mInitialized;
 
     /**
      * Returns the type of this {@link WindowProviderService}.
@@ -90,6 +90,20 @@
     }
 
     /**
+     * Returns the display ID to launch this {@link WindowProviderService}.
+     *
+     * @hide
+     */
+    @TestApi
+    @SuppressLint({"OnNameExpected"})
+    // Suppress the lint because it is not a callback and users may override this API to provide
+    // display.
+    @NonNull
+    public int getInitialDisplayId() {
+        return DEFAULT_DISPLAY;
+    }
+
+    /**
      * Attaches this WindowProviderService to the {@code windowToken}.
      *
      * @hide
@@ -104,19 +118,22 @@
     public final Context createServiceBaseContext(ActivityThread mainThread,
             LoadedApk packageInfo) {
         final Context context = super.createServiceBaseContext(mainThread, packageInfo);
-        // Always associate with the default display at initialization.
-        final Display defaultDisplay = context.getSystemService(DisplayManager.class)
-                .getDisplay(DEFAULT_DISPLAY);
-        return context.createTokenContext(mWindowToken, defaultDisplay);
+        final Display display = context.getSystemService(DisplayManager.class)
+                .getDisplay(getInitialDisplayId());
+        return context.createTokenContext(mWindowToken, display);
     }
 
-    @CallSuper
+    /** @hide */
     @Override
-    public void onCreate() {
-        super.onCreate();
-        mWindowToken.attachContext(this);
-        mController.attachToDisplayArea(getWindowType(), getDisplayId(), getWindowContextOptions());
-        mWindowManager = WindowManagerImpl.createWindowContextWindowManager(this);
+    protected void attachBaseContext(Context newBase) {
+        super.attachBaseContext(newBase);
+        if (!mInitialized) {
+            mWindowToken.attachContext(this);
+            mController.attachToDisplayArea(getWindowType(), getDisplayId(),
+                    getWindowContextOptions());
+            mWindowManager = WindowManagerImpl.createWindowContextWindowManager(this);
+            mInitialized = true;
+        }
     }
 
     @SuppressLint("OnNameExpected")
diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java
index 6abf557..284b4b9 100644
--- a/core/java/android/window/WindowTokenClient.java
+++ b/core/java/android/window/WindowTokenClient.java
@@ -15,14 +15,25 @@
  */
 package android.window;
 
+import static android.window.ConfigurationHelper.diffPublicWithSizeBuckets;
+import static android.window.ConfigurationHelper.freeTextLayoutCachesIfNeeded;
+import static android.window.ConfigurationHelper.isDifferentDisplay;
+import static android.window.ConfigurationHelper.shouldUpdateResources;
+
 import android.annotation.NonNull;
 import android.app.ActivityThread;
 import android.app.IWindowToken;
 import android.app.ResourcesManager;
 import android.content.Context;
 import android.content.res.Configuration;
+import android.inputmethodservice.AbstractInputMethodService;
+import android.os.Build;
 import android.os.Bundle;
+import android.os.Debug;
 import android.os.IBinder;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.lang.ref.WeakReference;
 
@@ -33,11 +44,13 @@
  * {@link Context#getWindowContextToken() the token of non-Activity UI Contexts}.
  *
  * @see WindowContext
- * @see android.view.IWindowManager#registerWindowContextListener(IBinder, int, int, Bundle)
+ * @see android.view.IWindowManager#attachWindowContextToDisplayArea(IBinder, int, int, Bundle)
  *
  * @hide
  */
 public class WindowTokenClient extends IWindowToken.Stub {
+    private static final String TAG = WindowTokenClient.class.getSimpleName();
+
     /**
      * Attached {@link Context} for this window token to update configuration and resources.
      * Initialized by {@link #attachContext(Context)}.
@@ -46,12 +59,16 @@
 
     private final ResourcesManager mResourcesManager = ResourcesManager.getInstance();
 
+    private final Configuration mConfiguration = new Configuration();
+
+    private boolean mShouldDumpConfigForIme;
+
     /**
      * Attaches {@code context} to this {@link WindowTokenClient}. Each {@link WindowTokenClient}
      * can only attach one {@link Context}.
      * <p>This method must be called before invoking
-     * {@link android.view.IWindowManager#registerWindowContextListener(IBinder, int, int,
-     * Bundle, boolean)}.<p/>
+     * {@link android.view.IWindowManager#attachWindowContextToDisplayArea(IBinder, int, int,
+     * Bundle)}.<p/>
      *
      * @param context context to be attached
      * @throws IllegalStateException if attached context has already existed.
@@ -61,25 +78,85 @@
             throw new IllegalStateException("Context is already attached.");
         }
         mContextRef = new WeakReference<>(context);
+        mConfiguration.setTo(context.getResources().getConfiguration());
+        mShouldDumpConfigForIme = Build.IS_DEBUGGABLE
+                && context instanceof AbstractInputMethodService;
     }
 
+    /**
+     * Called when {@link Configuration} updates from the server side receive.
+     *
+     * @param newConfig the updated {@link Configuration}
+     * @param newDisplayId the updated {@link android.view.Display} ID
+     */
     @Override
     public void onConfigurationChanged(Configuration newConfig, int newDisplayId) {
+        onConfigurationChanged(newConfig, newDisplayId, true /* shouldReportConfigChange */);
+    }
+
+    /**
+     * Called when {@link Configuration} updates from the server side receive.
+     *
+     * Similar to {@link #onConfigurationChanged(Configuration, int)}, but adds a flag to control
+     * whether to dispatch configuration update or not.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public void onConfigurationChanged(Configuration newConfig, int newDisplayId,
+            boolean shouldReportConfigChange) {
         final Context context = mContextRef.get();
         if (context == null) {
             return;
         }
-        final int currentDisplayId = context.getDisplayId();
-        final boolean displayChanged = newDisplayId != currentDisplayId;
-        final Configuration config = context.getResources().getConfiguration();
-        final boolean configChanged = config.diff(newConfig) != 0;
-        if (displayChanged || configChanged) {
+        final boolean displayChanged = isDifferentDisplay(context.getDisplayId(), newDisplayId);
+        final boolean shouldUpdateResources = shouldUpdateResources(this, mConfiguration,
+                newConfig, newConfig /* overrideConfig */, displayChanged,
+                null /* configChanged */);
+
+        if (!shouldUpdateResources && mShouldDumpConfigForIme) {
+            Log.d(TAG, "Configuration not dispatch to IME because configuration is up"
+                    + " to date. Current config=" + context.getResources().getConfiguration()
+                    + ", reported config=" + mConfiguration
+                    + ", updated config=" + newConfig);
+        }
+
+        if (shouldUpdateResources) {
             // TODO(ag/9789103): update resource manager logic to track non-activity tokens
             mResourcesManager.updateResourcesForActivity(this, newConfig, newDisplayId);
-            if (context instanceof WindowContext) {
+
+            if (shouldReportConfigChange && context instanceof WindowContext) {
+                final WindowContext windowContext = (WindowContext) context;
                 ActivityThread.currentActivityThread().getHandler().post(
-                        () -> ((WindowContext) context).dispatchConfigurationChanged(newConfig));
+                        () -> windowContext.dispatchConfigurationChanged(newConfig));
             }
+
+            // Dispatch onConfigurationChanged only if there's a significant public change to
+            // make it compatible with the original behavior.
+            final Configuration[] sizeConfigurations = context.getResources()
+                    .getSizeConfigurations();
+            final SizeConfigurationBuckets buckets = sizeConfigurations != null
+                    ? new SizeConfigurationBuckets(sizeConfigurations) : null;
+            final int diff = diffPublicWithSizeBuckets(mConfiguration, newConfig, buckets);
+
+            if (shouldReportConfigChange && diff != 0
+                    && context instanceof WindowProviderService) {
+                final WindowProviderService windowProviderService = (WindowProviderService) context;
+                ActivityThread.currentActivityThread().getHandler().post(
+                        () -> windowProviderService.onConfigurationChanged(newConfig));
+            }
+            freeTextLayoutCachesIfNeeded(diff);
+            if (mShouldDumpConfigForIme) {
+                if (!shouldReportConfigChange) {
+                    Log.d(TAG, "Only apply configuration update to Resources because "
+                            + "shouldReportConfigChange is false.\n" + Debug.getCallers(5));
+                } else if (diff == 0) {
+                    Log.d(TAG, "Configuration not dispatch to IME because configuration has no "
+                            + " public difference with updated config. "
+                            + " Current config=" + context.getResources().getConfiguration()
+                            + ", reported config=" + mConfiguration
+                            + ", updated config=" + newConfig);
+                }
+            }
+            mConfiguration.setTo(newConfig);
         }
         if (displayChanged) {
             context.updateDisplay(newDisplayId);
diff --git a/core/java/com/android/internal/policy/TransitionAnimation.java b/core/java/com/android/internal/policy/TransitionAnimation.java
index 60a8d80..d3224b1 100644
--- a/core/java/com/android/internal/policy/TransitionAnimation.java
+++ b/core/java/com/android/internal/policy/TransitionAnimation.java
@@ -16,16 +16,20 @@
 
 package com.android.internal.policy;
 
+import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE;
 import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
 import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
+import static android.view.WindowManager.TRANSIT_OLD_NONE;
 import static android.view.WindowManager.TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE;
 import static android.view.WindowManager.TRANSIT_OLD_TRANSLUCENT_ACTIVITY_OPEN;
 import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_CLOSE;
 import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_OPEN;
+import static android.view.WindowManager.TRANSIT_OPEN;
 
+import android.annotation.DrawableRes;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
@@ -34,12 +38,18 @@
 import android.content.res.ResourceId;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Picture;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
 import android.hardware.HardwareBuffer;
 import android.os.SystemProperties;
 import android.util.Slog;
 import android.view.WindowManager.LayoutParams;
 import android.view.WindowManager.TransitionOldType;
+import android.view.WindowManager.TransitionType;
 import android.view.animation.AlphaAnimation;
 import android.view.animation.Animation;
 import android.view.animation.AnimationSet;
@@ -56,11 +66,17 @@
 
 /** @hide */
 public class TransitionAnimation {
+    public static final int WALLPAPER_TRANSITION_NONE = 0;
+    public static final int WALLPAPER_TRANSITION_OPEN = 1;
+    public static final int WALLPAPER_TRANSITION_CLOSE = 2;
+    public static final int WALLPAPER_TRANSITION_INTRA_OPEN = 3;
+    public static final int WALLPAPER_TRANSITION_INTRA_CLOSE = 4;
+
     // These are the possible states for the enter/exit activities during a thumbnail transition
-    public static final int THUMBNAIL_TRANSITION_ENTER_SCALE_UP = 0;
-    public static final int THUMBNAIL_TRANSITION_EXIT_SCALE_UP = 1;
-    public static final int THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN = 2;
-    public static final int THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN = 3;
+    private static final int THUMBNAIL_TRANSITION_ENTER_SCALE_UP = 0;
+    private static final int THUMBNAIL_TRANSITION_EXIT_SCALE_UP = 1;
+    private static final int THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN = 2;
+    private static final int THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN = 3;
 
     /**
      * Maximum duration for the clip reveal animation. This is used when there is a lot of movement
@@ -72,9 +88,15 @@
 
     public static final int DEFAULT_APP_TRANSITION_DURATION = 336;
 
+    /** Fraction of animation at which the recents thumbnail stays completely transparent */
+    private static final float RECENTS_THUMBNAIL_FADEIN_FRACTION = 0.5f;
     /** Fraction of animation at which the recents thumbnail becomes completely transparent */
     private static final float RECENTS_THUMBNAIL_FADEOUT_FRACTION = 0.5f;
 
+    /** Interpolator to be used for animations that respond directly to a touch */
+    static final Interpolator TOUCH_RESPONSE_INTERPOLATOR =
+            new PathInterpolator(0.3f, 0f, 0.1f, 1f);
+
     private static final String DEFAULT_PACKAGE = "android";
 
     private final Context mContext;
@@ -86,7 +108,9 @@
             new PathInterpolator(0.3f, 0f, 0.1f, 1f);
     private final Interpolator mClipHorizontalInterpolator = new PathInterpolator(0, 0, 0.4f, 1f);
     private final Interpolator mDecelerateInterpolator;
+    private final Interpolator mFastOutLinearInInterpolator;
     private final Interpolator mLinearOutSlowInInterpolator;
+    private final Interpolator mThumbnailFadeInInterpolator;
     private final Interpolator mThumbnailFadeOutInterpolator;
     private final Rect mTmpFromClipRect = new Rect();
     private final Rect mTmpToClipRect = new Rect();
@@ -107,8 +131,19 @@
 
         mDecelerateInterpolator = AnimationUtils.loadInterpolator(context,
                 com.android.internal.R.interpolator.decelerate_cubic);
+        mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context,
+                com.android.internal.R.interpolator.fast_out_linear_in);
         mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
                 com.android.internal.R.interpolator.linear_out_slow_in);
+        mThumbnailFadeInInterpolator = input -> {
+            // Linear response for first fraction, then complete after that.
+            if (input < RECENTS_THUMBNAIL_FADEIN_FRACTION) {
+                return 0f;
+            }
+            float t = (input - RECENTS_THUMBNAIL_FADEIN_FRACTION)
+                    / (1f - RECENTS_THUMBNAIL_FADEIN_FRACTION);
+            return mFastOutLinearInInterpolator.getInterpolation(t);
+        };
         mThumbnailFadeOutInterpolator = input -> {
             // Linear response for first fraction, then complete after that.
             if (input < RECENTS_THUMBNAIL_FADEOUT_FRACTION) {
@@ -181,6 +216,13 @@
                 DEFAULT_PACKAGE, com.android.internal.R.anim.cross_profile_apps_thumbnail_enter);
     }
 
+    @Nullable
+    public Animation createCrossProfileAppsThumbnailAnimationLocked(Rect appRect) {
+        final Animation animation = loadCrossProfileAppThumbnailEnterAnimation();
+        return prepareThumbnailAnimationWithDuration(animation, appRect.width(),
+                appRect.height(), 0, null);
+    }
+
     /** Load animation by resource Id from specific package. */
     @Nullable
     public Animation loadAnimationRes(String packageName, int resId) {
@@ -347,8 +389,15 @@
         }
     }
 
-    public Animation createClipRevealAnimationLocked(int transit, boolean enter, Rect appFrame,
-            Rect displayFrame, Rect startRect) {
+    public Animation createClipRevealAnimationLocked(@TransitionType int transit,
+            int wallpaperTransit, boolean enter, Rect appFrame, Rect displayFrame, Rect startRect) {
+        return createClipRevealAnimationLockedCompat(
+                getTransitCompatType(transit, wallpaperTransit), enter, appFrame, displayFrame,
+                startRect);
+    }
+
+    public Animation createClipRevealAnimationLockedCompat(@TransitionOldType int transit,
+            boolean enter, Rect appFrame, Rect displayFrame, Rect startRect) {
         final Animation anim;
         if (enter) {
             final int appWidth = appFrame.width();
@@ -458,8 +507,14 @@
         return anim;
     }
 
-    public Animation createScaleUpAnimationLocked(int transit, boolean enter,
-            Rect containingFrame, Rect startRect) {
+    public Animation createScaleUpAnimationLocked(@TransitionType int transit, int wallpaperTransit,
+            boolean enter, Rect containingFrame, Rect startRect) {
+        return createScaleUpAnimationLockedCompat(getTransitCompatType(transit, wallpaperTransit),
+                enter, containingFrame, startRect);
+    }
+
+    public Animation createScaleUpAnimationLockedCompat(@TransitionOldType int transit,
+            boolean enter, Rect containingFrame, Rect startRect) {
         Animation a;
         setupDefaultNextAppTransitionStartRect(startRect, mTmpRect);
         final int appWidth = containingFrame.width();
@@ -514,12 +569,19 @@
         return a;
     }
 
+    public Animation createThumbnailEnterExitAnimationLocked(boolean enter, boolean scaleUp,
+            Rect containingFrame, @TransitionType int transit, int wallpaperTransit,
+            HardwareBuffer thumbnailHeader, Rect startRect) {
+        return createThumbnailEnterExitAnimationLockedCompat(enter, scaleUp, containingFrame,
+                getTransitCompatType(transit, wallpaperTransit), thumbnailHeader, startRect);
+    }
+
     /**
      * This animation is created when we are doing a thumbnail transition, for the activity that is
      * leaving, and the activity that is entering.
      */
-    public Animation createThumbnailEnterExitAnimationLocked(int thumbTransitState,
-            Rect containingFrame, int transit, HardwareBuffer thumbnailHeader,
+    public Animation createThumbnailEnterExitAnimationLockedCompat(boolean enter, boolean scaleUp,
+            Rect containingFrame, @TransitionOldType int transit, HardwareBuffer thumbnailHeader,
             Rect startRect) {
         final int appWidth = containingFrame.width();
         final int appHeight = containingFrame.height();
@@ -529,6 +591,7 @@
         final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
         final int thumbHeightI = thumbnailHeader != null ? thumbnailHeader.getHeight() : appHeight;
         final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1;
+        final int thumbTransitState = getThumbnailTransitionState(enter, scaleUp);
 
         switch (thumbTransitState) {
             case THUMBNAIL_TRANSITION_ENTER_SCALE_UP: {
@@ -587,8 +650,8 @@
      * This alternate animation is created when we are doing a thumbnail transition, for the
      * activity that is leaving, and the activity that is entering.
      */
-    public Animation createAspectScaledThumbnailEnterExitAnimationLocked(int thumbTransitState,
-            int orientation, int transit, Rect containingFrame, Rect contentInsets,
+    public Animation createAspectScaledThumbnailEnterExitAnimationLocked(boolean enter,
+            boolean scaleUp, int orientation, int transit, Rect containingFrame, Rect contentInsets,
             @Nullable Rect surfaceInsets, @Nullable Rect stableInsets, boolean freeform,
             Rect startRect, Rect defaultStartRect) {
         Animation a;
@@ -601,11 +664,11 @@
         final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1;
         final int thumbStartX = mTmpRect.left - containingFrame.left - contentInsets.left;
         final int thumbStartY = mTmpRect.top - containingFrame.top;
+        final int thumbTransitState = getThumbnailTransitionState(enter, scaleUp);
 
         switch (thumbTransitState) {
             case THUMBNAIL_TRANSITION_ENTER_SCALE_UP:
             case THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN: {
-                final boolean scaleUp = thumbTransitState == THUMBNAIL_TRANSITION_ENTER_SCALE_UP;
                 if (freeform && scaleUp) {
                     a = createAspectScaledThumbnailEnterFreeformAnimationLocked(
                             containingFrame, surfaceInsets, startRect, defaultStartRect);
@@ -720,10 +783,151 @@
     }
 
     /**
+     * This animation runs for the thumbnail that gets cross faded with the enter/exit activity
+     * when a thumbnail is specified with the pending animation override.
+     */
+    public Animation createThumbnailAspectScaleAnimationLocked(Rect appRect,
+            @Nullable Rect contentInsets, HardwareBuffer thumbnailHeader, int orientation,
+            Rect startRect, Rect defaultStartRect, boolean scaleUp) {
+        Animation a;
+        final int thumbWidthI = thumbnailHeader.getWidth();
+        final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
+        final int thumbHeightI = thumbnailHeader.getHeight();
+        final int appWidth = appRect.width();
+
+        float scaleW = appWidth / thumbWidth;
+        getNextAppTransitionStartRect(startRect, defaultStartRect, mTmpRect);
+        final float fromX;
+        float fromY;
+        final float toX;
+        float toY;
+        final float pivotX;
+        final float pivotY;
+        if (shouldScaleDownThumbnailTransition(orientation)) {
+            fromX = mTmpRect.left;
+            fromY = mTmpRect.top;
+
+            // For the curved translate animation to work, the pivot points needs to be at the
+            // same absolute position as the one from the real surface.
+            toX = mTmpRect.width() / 2 * (scaleW - 1f) + appRect.left;
+            toY = appRect.height() / 2 * (1 - 1 / scaleW) + appRect.top;
+            pivotX = mTmpRect.width() / 2;
+            pivotY = appRect.height() / 2 / scaleW;
+            if (mGridLayoutRecentsEnabled) {
+                // In the grid layout, the header is displayed above the thumbnail instead of
+                // overlapping it.
+                fromY -= thumbHeightI;
+                toY -= thumbHeightI * scaleW;
+            }
+        } else {
+            pivotX = 0;
+            pivotY = 0;
+            fromX = mTmpRect.left;
+            fromY = mTmpRect.top;
+            toX = appRect.left;
+            toY = appRect.top;
+        }
+        if (scaleUp) {
+            // Animation up from the thumbnail to the full screen
+            Animation scale = new ScaleAnimation(1f, scaleW, 1f, scaleW, pivotX, pivotY);
+            scale.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
+            scale.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
+            Animation alpha = new AlphaAnimation(1f, 0f);
+            alpha.setInterpolator(mThumbnailFadeOutInterpolator);
+            alpha.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
+            Animation translate = createCurvedMotion(fromX, toX, fromY, toY);
+            translate.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
+            translate.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
+
+            mTmpFromClipRect.set(0, 0, thumbWidthI, thumbHeightI);
+            mTmpToClipRect.set(appRect);
+
+            // Containing frame is in screen space, but we need the clip rect in the
+            // app space.
+            mTmpToClipRect.offsetTo(0, 0);
+            mTmpToClipRect.right = (int) (mTmpToClipRect.right / scaleW);
+            mTmpToClipRect.bottom = (int) (mTmpToClipRect.bottom / scaleW);
+
+            if (contentInsets != null) {
+                mTmpToClipRect.inset((int) (-contentInsets.left * scaleW),
+                        (int) (-contentInsets.top * scaleW),
+                        (int) (-contentInsets.right * scaleW),
+                        (int) (-contentInsets.bottom * scaleW));
+            }
+
+            Animation clipAnim = new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect);
+            clipAnim.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
+            clipAnim.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
+
+            // This AnimationSet uses the Interpolators assigned above.
+            AnimationSet set = new AnimationSet(false);
+            set.addAnimation(scale);
+            if (!mGridLayoutRecentsEnabled) {
+                // In the grid layout, the header should be shown for the whole animation.
+                set.addAnimation(alpha);
+            }
+            set.addAnimation(translate);
+            set.addAnimation(clipAnim);
+            a = set;
+        } else {
+            // Animation down from the full screen to the thumbnail
+            Animation scale = new ScaleAnimation(scaleW, 1f, scaleW, 1f, pivotX, pivotY);
+            scale.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
+            scale.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
+            Animation alpha = new AlphaAnimation(0f, 1f);
+            alpha.setInterpolator(mThumbnailFadeInInterpolator);
+            alpha.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
+            Animation translate = createCurvedMotion(toX, fromX, toY, fromY);
+            translate.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
+            translate.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
+
+            // This AnimationSet uses the Interpolators assigned above.
+            AnimationSet set = new AnimationSet(false);
+            set.addAnimation(scale);
+            if (!mGridLayoutRecentsEnabled) {
+                // In the grid layout, the header should be shown for the whole animation.
+                set.addAnimation(alpha);
+            }
+            set.addAnimation(translate);
+            a = set;
+
+        }
+        return prepareThumbnailAnimationWithDuration(a, appWidth, appRect.height(), 0,
+                null);
+    }
+
+    /**
+     * Creates an overlay with a background color and a thumbnail for the cross profile apps
+     * animation.
+     */
+    public HardwareBuffer createCrossProfileAppsThumbnail(
+            @DrawableRes int thumbnailDrawableRes, Rect frame) {
+        final int width = frame.width();
+        final int height = frame.height();
+
+        final Picture picture = new Picture();
+        final Canvas canvas = picture.beginRecording(width, height);
+        canvas.drawColor(Color.argb(0.6f, 0, 0, 0));
+        final int thumbnailSize = mContext.getResources().getDimensionPixelSize(
+                com.android.internal.R.dimen.cross_profile_apps_thumbnail_size);
+        final Drawable drawable = mContext.getDrawable(thumbnailDrawableRes);
+        drawable.setBounds(
+                (width - thumbnailSize) / 2,
+                (height - thumbnailSize) / 2,
+                (width + thumbnailSize) / 2,
+                (height + thumbnailSize) / 2);
+        drawable.setTint(mContext.getColor(android.R.color.white));
+        drawable.draw(canvas);
+        picture.endRecording();
+
+        return Bitmap.createBitmap(picture).getHardwareBuffer();
+    }
+
+    /**
      * Prepares the specified animation with a standard duration, interpolator, etc.
      */
     private Animation prepareThumbnailAnimation(Animation a, int appWidth, int appHeight,
-            int transit) {
+            @TransitionOldType int transit) {
         // Pick the desired duration.  If this is an inter-activity transition,
         // it  is the standard duration for that.  Otherwise we use the longer
         // task transition duration.
@@ -820,6 +1024,22 @@
         return anim;
     }
 
+    private static @TransitionOldType int getTransitCompatType(@TransitionType int transit,
+            int wallpaperTransit) {
+        if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_OPEN) {
+            return TRANSIT_OLD_WALLPAPER_INTRA_OPEN;
+        } else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_CLOSE) {
+            return TRANSIT_OLD_WALLPAPER_INTRA_CLOSE;
+        } else if (transit == TRANSIT_OPEN) {
+            return TRANSIT_OLD_ACTIVITY_OPEN;
+        } else if (transit == TRANSIT_CLOSE) {
+            return TRANSIT_OLD_ACTIVITY_CLOSE;
+        }
+
+        // We only do some special handle for above type, so use type NONE for default behavior.
+        return TRANSIT_OLD_NONE;
+    }
+
     /**
      * Calculates the duration for the clip reveal animation. If the clip is "cut off", meaning that
      * the start rect is outside of the target rect, and there is a lot of movement going on.
@@ -843,10 +1063,33 @@
     }
 
     /**
+     * Return the current thumbnail transition state.
+     */
+    private int getThumbnailTransitionState(boolean enter, boolean scaleUp) {
+        if (enter) {
+            if (scaleUp) {
+                return THUMBNAIL_TRANSITION_ENTER_SCALE_UP;
+            } else {
+                return THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN;
+            }
+        } else {
+            if (scaleUp) {
+                return THUMBNAIL_TRANSITION_EXIT_SCALE_UP;
+            } else {
+                return THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN;
+            }
+        }
+    }
+
+    /**
      * Prepares the specified animation with a standard duration, interpolator, etc.
      */
-    private static Animation prepareThumbnailAnimationWithDuration(Animation a, int appWidth,
+    public static Animation prepareThumbnailAnimationWithDuration(Animation a, int appWidth,
             int appHeight, long duration, Interpolator interpolator) {
+        if (a == null) {
+            return null;
+        }
+
         if (duration > 0) {
             a.setDuration(duration);
         }
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 10f14b4..4f940db 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -25,6 +25,7 @@
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.service.notification.StatusBarNotification;
+import android.view.InsetsState;
 
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.internal.view.AppearanceRegion;
@@ -182,7 +183,7 @@
     /**
      * Notifies System UI side of system bar attribute change on the specified display.
      *
-     * @param displayId the ID of the display to notify
+     * @param displayId the ID of the display to notify.
      * @param appearance the appearance of the focused window. The light top bar appearance is not
      *                   controlled here, but primaryAppearance and secondaryAppearance.
      * @param appearanceRegions a set of appearances which will be only applied in their own bounds.
@@ -191,11 +192,12 @@
      *                         stacks.
      * @param navbarColorManagedByIme {@code true} if navigation bar color is managed by IME.
      * @param behavior the behavior of the focused window.
-     * @param isFullscreen whether any of status or navigation bar is requested invisible.
+     * @param requestedState the collection of the requested visibilities of system insets.
+     * @param packageName the package name of the focused app.
      */
     void onSystemBarAttributesChanged(int displayId, int appearance,
             in AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-            int behavior, boolean isFullscreen);
+            int behavior, in InsetsState requestedVisibilities, String packageName);
 
     /**
      * Notifies System UI to show transient bars. The transient bars are system bars, e.g., status
diff --git a/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java b/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java
index 8fb2f9c..2fd1691 100644
--- a/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java
+++ b/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java
@@ -21,6 +21,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.ArrayMap;
+import android.view.InsetsState;
 
 import com.android.internal.view.AppearanceRegion;
 
@@ -39,14 +40,15 @@
     public final IBinder mImeToken;
     public final boolean mNavbarColorManagedByIme;
     public final int mBehavior;
-    public final boolean mAppFullscreen;
+    public final InsetsState mRequestedState;
+    public final String mPackageName;
     public final int[] mTransientBarTypes;
 
     public RegisterStatusBarResult(ArrayMap<String, StatusBarIcon> icons, int disabledFlags1,
             int appearance, AppearanceRegion[] appearanceRegions, int imeWindowVis,
             int imeBackDisposition, boolean showImeSwitcher, int disabledFlags2, IBinder imeToken,
-            boolean navbarColorManagedByIme, int behavior, boolean appFullscreen,
-            @NonNull int[] transientBarTypes) {
+            boolean navbarColorManagedByIme, int behavior, InsetsState requestedState,
+            String packageName, @NonNull int[] transientBarTypes) {
         mIcons = new ArrayMap<>(icons);
         mDisabledFlags1 = disabledFlags1;
         mAppearance = appearance;
@@ -58,7 +60,8 @@
         mImeToken = imeToken;
         mNavbarColorManagedByIme = navbarColorManagedByIme;
         mBehavior = behavior;
-        mAppFullscreen = appFullscreen;
+        mRequestedState = requestedState;
+        mPackageName = packageName;
         mTransientBarTypes = transientBarTypes;
     }
 
@@ -80,7 +83,8 @@
         dest.writeStrongBinder(mImeToken);
         dest.writeBoolean(mNavbarColorManagedByIme);
         dest.writeInt(mBehavior);
-        dest.writeBoolean(mAppFullscreen);
+        dest.writeTypedObject(mRequestedState, 0);
+        dest.writeString(mPackageName);
         dest.writeIntArray(mTransientBarTypes);
     }
 
@@ -104,12 +108,13 @@
                     final IBinder imeToken = source.readStrongBinder();
                     final boolean navbarColorManagedByIme = source.readBoolean();
                     final int behavior = source.readInt();
-                    final boolean appFullscreen = source.readBoolean();
+                    final InsetsState requestedState = source.readTypedObject(InsetsState.CREATOR);
+                    final String packageName = source.readString();
                     final int[] transientBarTypes = source.createIntArray();
                     return new RegisterStatusBarResult(icons, disabledFlags1, appearance,
                             appearanceRegions, imeWindowVis, imeBackDisposition, showImeSwitcher,
                             disabledFlags2, imeToken, navbarColorManagedByIme, behavior,
-                            appFullscreen, transientBarTypes);
+                            requestedState, packageName, transientBarTypes);
                 }
 
                 @Override
diff --git a/core/java/com/android/internal/view/IInputMethod.aidl b/core/java/com/android/internal/view/IInputMethod.aidl
index 8d82e33..5354afb 100644
--- a/core/java/com/android/internal/view/IInputMethod.aidl
+++ b/core/java/com/android/internal/view/IInputMethod.aidl
@@ -35,7 +35,7 @@
  * {@hide}
  */
 oneway interface IInputMethod {
-    void initializeInternal(IBinder token, int displayId, IInputMethodPrivilegedOperations privOps,
+    void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privOps,
              int configChanges);
 
     void onCreateInlineSuggestionsRequest(in InlineSuggestionsRequestInfo requestInfo,
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 125182c..958205f 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -230,6 +230,7 @@
                 "libbinderthreadstateutils",
                 "libdmabufinfo",
                 "libgif",
+                "libgui_window_info_static",
                 "libseccomp_policy",
                 "libgrallocusage",
                 "libscrypt_static",
@@ -370,6 +371,7 @@
                 "libinput",
                 "libbinderthreadstateutils",
                 "libsqlite",
+                "libgui_window_info_static",
             ],
             shared_libs: [
                 // libbinder needs to be shared since it has global state
diff --git a/core/jni/android_hardware_input_InputApplicationHandle.h b/core/jni/android_hardware_input_InputApplicationHandle.h
index ec99d6d..7eb7ac4 100644
--- a/core/jni/android_hardware_input_InputApplicationHandle.h
+++ b/core/jni/android_hardware_input_InputApplicationHandle.h
@@ -19,7 +19,7 @@
 
 #include <string>
 
-#include <input/InputApplication.h>
+#include <gui/InputApplication.h>
 
 #include <nativehelper/JNIHelp.h>
 #include "jni.h"
diff --git a/core/jni/android_hardware_input_InputWindowHandle.cpp b/core/jni/android_hardware_input_InputWindowHandle.cpp
index 463d909..afc44ff 100644
--- a/core/jni/android_hardware_input_InputWindowHandle.cpp
+++ b/core/jni/android_hardware_input_InputWindowHandle.cpp
@@ -26,14 +26,17 @@
 #include <ui/Region.h>
 #include <utils/threads.h>
 
+#include <gui/WindowInfo.h>
 #include "android_hardware_input_InputApplicationHandle.h"
 #include "android_util_Binder.h"
 #include "core_jni_helpers.h"
-#include "input/InputWindow.h"
 #include "jni.h"
 
 namespace android {
 
+using gui::TouchOcclusionMode;
+using gui::WindowInfo;
+
 struct WeakRefHandleField {
     jfieldID ctrl;
     jmethodID get;
@@ -115,9 +118,9 @@
 
     mInfo.name = getStringField(env, obj, gInputWindowHandleClassInfo.name, "<null>");
 
-    mInfo.flags = Flags<InputWindowInfo::Flag>(
+    mInfo.flags = Flags<WindowInfo::Flag>(
             env->GetIntField(obj, gInputWindowHandleClassInfo.layoutParamsFlags));
-    mInfo.type = static_cast<InputWindowInfo::Type>(
+    mInfo.type = static_cast<WindowInfo::Type>(
             env->GetIntField(obj, gInputWindowHandleClassInfo.layoutParamsType));
     mInfo.dispatchingTimeout = std::chrono::milliseconds(
             env->GetLongField(obj, gInputWindowHandleClassInfo.dispatchingTimeoutMillis));
@@ -159,7 +162,7 @@
     mInfo.ownerUid = env->GetIntField(obj,
             gInputWindowHandleClassInfo.ownerUid);
     mInfo.packageName = getStringField(env, obj, gInputWindowHandleClassInfo.packageName, "<null>");
-    mInfo.inputFeatures = static_cast<InputWindowInfo::Feature>(
+    mInfo.inputFeatures = static_cast<WindowInfo::Feature>(
             env->GetIntField(obj, gInputWindowHandleClassInfo.inputFeatures));
     mInfo.displayId = env->GetIntField(obj,
             gInputWindowHandleClassInfo.displayId);
diff --git a/core/jni/android_hardware_input_InputWindowHandle.h b/core/jni/android_hardware_input_InputWindowHandle.h
index de5bd6e..635480fc 100644
--- a/core/jni/android_hardware_input_InputWindowHandle.h
+++ b/core/jni/android_hardware_input_InputWindowHandle.h
@@ -17,14 +17,14 @@
 #ifndef _ANDROID_VIEW_INPUT_WINDOW_HANDLE_H
 #define _ANDROID_VIEW_INPUT_WINDOW_HANDLE_H
 
-#include <input/InputWindow.h>
+#include <gui/WindowInfo.h>
 
 #include <nativehelper/JNIHelp.h>
 #include "jni.h"
 
 namespace android {
 
-class NativeInputWindowHandle : public InputWindowHandle {
+class NativeInputWindowHandle : public gui::WindowInfoHandle {
 public:
     NativeInputWindowHandle(jweak objWeak);
     virtual ~NativeInputWindowHandle();
@@ -37,7 +37,6 @@
     jweak mObjWeak;
 };
 
-
 extern sp<NativeInputWindowHandle> android_view_InputWindowHandle_getHandle(
         JNIEnv* env, jobject inputWindowHandleObj);
 
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index 6971301..21db198 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -22,6 +22,7 @@
 #include <android_runtime/AndroidRuntime.h>
 #include <android_runtime/Log.h>
 #include <attestation/HmacKeyManager.h>
+#include <gui/constants.h>
 #include <input/Input.h>
 #include <nativehelper/ScopedUtfChars.h>
 #include <utils/Log.h>
@@ -56,6 +57,8 @@
     jfieldID toolMajor;
     jfieldID toolMinor;
     jfieldID orientation;
+    jfieldID relativeX;
+    jfieldID relativeY;
 } gPointerCoordsClassInfo;
 
 static struct {
@@ -212,6 +215,12 @@
             env->GetFloatField(pointerCoordsObj, gPointerCoordsClassInfo.toolMinor));
     outRawPointerCoords->setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION,
             env->GetFloatField(pointerCoordsObj, gPointerCoordsClassInfo.orientation));
+    outRawPointerCoords->setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X,
+                                      env->GetFloatField(pointerCoordsObj,
+                                                         gPointerCoordsClassInfo.relativeX));
+    outRawPointerCoords->setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y,
+                                      env->GetFloatField(pointerCoordsObj,
+                                                         gPointerCoordsClassInfo.relativeY));
 
     BitSet64 bits =
             BitSet64(env->GetLongField(pointerCoordsObj, gPointerCoordsClassInfo.mPackedAxisBits));
@@ -261,6 +270,12 @@
     float rawY = rawPointerCoords->getAxisValue(AMOTION_EVENT_AXIS_Y);
     vec2 transformed = transform.transform(rawX, rawY);
 
+    float rawRelX = rawPointerCoords->getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
+    float rawRelY = rawPointerCoords->getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
+    // Apply only rotation and scale, not translation.
+    const vec2 transformedOrigin = transform.transform(0, 0);
+    const vec2 transformedRel = transform.transform(rawRelX, rawRelY) - transformedOrigin;
+
     env->SetFloatField(outPointerCoordsObj, gPointerCoordsClassInfo.x, transformed.x);
     env->SetFloatField(outPointerCoordsObj, gPointerCoordsClassInfo.y, transformed.y);
     env->SetFloatField(outPointerCoordsObj, gPointerCoordsClassInfo.pressure,
@@ -277,6 +292,8 @@
             rawPointerCoords->getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR));
     env->SetFloatField(outPointerCoordsObj, gPointerCoordsClassInfo.orientation,
             rawPointerCoords->getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION));
+    env->SetFloatField(outPointerCoordsObj, gPointerCoordsClassInfo.relativeX, transformedRel.x);
+    env->SetFloatField(outPointerCoordsObj, gPointerCoordsClassInfo.relativeY, transformedRel.y);
 
     uint64_t outBits = 0;
     BitSet64 bits = BitSet64(rawPointerCoords->bits);
@@ -289,6 +306,8 @@
     bits.clearBit(AMOTION_EVENT_AXIS_TOOL_MAJOR);
     bits.clearBit(AMOTION_EVENT_AXIS_TOOL_MINOR);
     bits.clearBit(AMOTION_EVENT_AXIS_ORIENTATION);
+    bits.clearBit(AMOTION_EVENT_AXIS_RELATIVE_X);
+    bits.clearBit(AMOTION_EVENT_AXIS_RELATIVE_Y);
     if (!bits.isEmpty()) {
         uint32_t packedAxesCount = bits.count();
         jfloatArray outValuesArray = obtainPackedAxisValuesArray(env, packedAxesCount,
@@ -378,9 +397,9 @@
                       flags, edgeFlags, metaState, buttonState,
                       static_cast<MotionClassification>(classification), transform, xPrecision,
                       yPrecision, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                      AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_DISPLAY_SIZE,
-                      AMOTION_EVENT_INVALID_DISPLAY_SIZE, downTimeNanos, eventTimeNanos,
-                      pointerCount, pointerProperties, rawPointerCoords);
+                      AMOTION_EVENT_INVALID_CURSOR_POSITION, INVALID_DISPLAY_SIZE,
+                      INVALID_DISPLAY_SIZE, downTimeNanos, eventTimeNanos, pointerCount,
+                      pointerProperties, rawPointerCoords);
 
     return reinterpret_cast<jlong>(event.release());
 }
@@ -872,6 +891,8 @@
     gPointerCoordsClassInfo.toolMajor = GetFieldIDOrDie(env, clazz, "toolMajor", "F");
     gPointerCoordsClassInfo.toolMinor = GetFieldIDOrDie(env, clazz, "toolMinor", "F");
     gPointerCoordsClassInfo.orientation = GetFieldIDOrDie(env, clazz, "orientation", "F");
+    gPointerCoordsClassInfo.relativeX = GetFieldIDOrDie(env, clazz, "relativeX", "F");
+    gPointerCoordsClassInfo.relativeY = GetFieldIDOrDie(env, clazz, "relativeY", "F");
 
     clazz = FindClassOrDie(env, "android/view/MotionEvent$PointerProperties");
 
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 1695e1a..dd80c73 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -62,6 +62,8 @@
 
 namespace android {
 
+using gui::FocusRequest;
+
 static void doThrowNPE(JNIEnv* env) {
     jniThrowNullPointerException(env, NULL);
 }
@@ -1006,6 +1008,17 @@
     }
 }
 
+static void nativeSetDisplayFlags(JNIEnv* env, jclass clazz, jlong transactionObj, jobject tokenObj,
+                                  jint flags) {
+    sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
+    if (token == NULL) return;
+
+    {
+        auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+        transaction->setDisplayFlags(token, flags);
+    }
+}
+
 static void nativeSetDisplayProjection(JNIEnv* env, jclass clazz,
         jlong transactionObj,
         jobject tokenObj, jint orientation,
@@ -1881,6 +1894,8 @@
             (void*)nativeSetDisplaySurface },
     {"nativeSetDisplayLayerStack", "(JLandroid/os/IBinder;I)V",
             (void*)nativeSetDisplayLayerStack },
+    {"nativeSetDisplayFlags", "(JLandroid/os/IBinder;I)V",
+            (void*)nativeSetDisplayFlags },
     {"nativeSetDisplayProjection", "(JLandroid/os/IBinder;IIIIIIIII)V",
             (void*)nativeSetDisplayProjection },
     {"nativeSetDisplaySize", "(JLandroid/os/IBinder;II)V",
diff --git a/core/proto/android/server/accessibilitytrace.proto b/core/proto/android/server/accessibilitytrace.proto
index 1fc4a01..41fecfd 100644
--- a/core/proto/android/server/accessibilitytrace.proto
+++ b/core/proto/android/server/accessibilitytrace.proto
@@ -46,17 +46,17 @@
     /* required: elapsed realtime in nanos since boot of when this entry was logged */
     optional fixed64 elapsed_realtime_nanos = 1;
     optional string calendar_time = 2;
-
-    optional string process_name = 3;
-    optional string thread_id_name = 4;
+    repeated string logging_type = 3;
+    optional string process_name = 4;
+    optional string thread_id_name = 5;
 
     /* where the trace originated */
-    optional string where = 5;
+    optional string where = 6;
 
-    optional string calling_pkg = 6;
-    optional string calling_params = 7;
-    optional string calling_stacks = 8;
+    optional string calling_pkg = 7;
+    optional string calling_params = 8;
+    optional string calling_stacks = 9;
 
-    optional AccessibilityDumpProto accessibility_service = 9;
-    optional com.android.server.wm.WindowManagerServiceDumpProto window_manager_service = 10;
+    optional AccessibilityDumpProto accessibility_service = 10;
+    optional com.android.server.wm.WindowManagerServiceDumpProto window_manager_service = 11;
 }
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index fa1e9d4..0121bff 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -278,7 +278,7 @@
 message TaskProto {
     option (.android.msg_privacy).dest = DEST_AUTOMATIC;
 
-    optional WindowContainerProto window_container = 1;
+    optional WindowContainerProto window_container = 1 [deprecated=true];
     optional int32 id = 2;
     reserved 3; // activity
     optional bool fills_parent = 4;
@@ -295,12 +295,12 @@
     optional string real_activity = 13;
     optional string orig_activity = 14;
 
-    optional int32 display_id = 15;
+    optional int32 display_id = 15 [deprecated=true];
     optional int32 root_task_id = 16;
-    optional int32 activity_type = 17 [(.android.typedef) = "android.app.WindowConfiguration.ActivityType"];
+    optional int32 activity_type = 17 [(.android.typedef) = "android.app.WindowConfiguration.ActivityType", deprecated=true] ;
     optional int32 resize_mode = 18 [(.android.typedef) = "android.appwidget.AppWidgetProviderInfo.ResizeModeFlags"];
-    optional int32 min_width = 19;
-    optional int32 min_height = 20;
+    optional int32 min_width = 19 [deprecated=true];
+    optional int32 min_height = 20 [deprecated=true];
 
     optional .android.graphics.RectProto adjusted_bounds = 21;
     optional .android.graphics.RectProto last_non_fullscreen_bounds = 22;
@@ -312,6 +312,18 @@
     optional bool created_by_organizer = 28;
     optional string affinity = 29;
     optional bool has_child_pip_activity = 30;
+    optional TaskFragmentProto task_fragment = 31;
+}
+
+/* represents TaskFragment */
+message TaskFragmentProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    optional WindowContainerProto window_container = 1;
+    optional int32 display_id = 2;
+    optional int32 activity_type = 3 [(.android.typedef) = "android.app.WindowConfiguration.ActivityType"];
+    optional int32 min_width = 4;
+    optional int32 min_height = 5;
 }
 
 /* represents ActivityRecordProto */
@@ -493,6 +505,8 @@
     optional WindowTokenProto window_token = 7;
     /* represents a WindowState child */
     optional WindowStateProto window = 8;
+    /* represents a WindowState child */
+    optional TaskFragmentProto task_fragment = 9;
 }
 
 /* represents ConfigurationContainer */
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 029ed5b..2141477 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3607,8 +3607,8 @@
     -->
     <integer name="config_largeScreenSmallestScreenWidthDp">600</integer>
 
-    <!-- True if the device is using leagacy split. -->
-    <bool name="config_useLegacySplit">true</bool>
+    <!-- True if the device is using legacy split. -->
+    <bool name="config_useLegacySplit">false</bool>
 
     <!-- True if the device supports running activities on secondary displays. -->
     <bool name="config_supportsMultiDisplay">true</bool>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 2403a60..eb4919b 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3201,10 +3201,84 @@
     <public type="string" name="config_defaultRingtoneVibrationSound" id="0x0104003b" />
 
   <!-- ===============================================================
+    Resources added in version S-V2 of the platform
+
+    NOTE: add <public> elements within a <staging-public-group> like so:
+
+    <staging-public-group type="attr" first-id="0x01ff0000">
+        <public name="exampleAttr1" />
+        <public name="exampleAttr2" />
+    </staging-public-group>
+
+    To add a new <staging-public-group> block, find the id value for the
+    last <staging-public-group> block defined for thie API level, and
+    subtract 0x00010000 from it to get to the id of the new block.
+
+    For example, if the block closest to the end of this file has an id of
+    0x01ee0000, the id of the new block should be 0x01ed0000
+    (0x01ee0000 - 0x00010000 = 0x01ed0000).
+    =============================================================== -->
+  <eat-comment />
+
+  <staging-public-group type="attr" first-id="0x01ff0000">
+  </staging-public-group>
+
+  <staging-public-group type="id" first-id="0x01fe0000">
+  </staging-public-group>
+
+  <staging-public-group type="style" first-id="0x01fd0000">
+  </staging-public-group>
+
+  <staging-public-group type="string" first-id="0x01fc0000">
+  </staging-public-group>
+
+  <staging-public-group type="dimen" first-id="0x01fb0000">
+  </staging-public-group>
+
+  <staging-public-group type="color" first-id="0x01fa0000">
+  </staging-public-group>
+
+  <staging-public-group type="array" first-id="0x01f90000">
+  </staging-public-group>
+
+  <staging-public-group type="drawable" first-id="0x01f80000">
+  </staging-public-group>
+
+  <staging-public-group type="layout" first-id="0x01f70000">
+  </staging-public-group>
+
+  <staging-public-group type="anim" first-id="0x01f60000">
+  </staging-public-group>
+
+  <staging-public-group type="animator" first-id="0x01f50000">
+  </staging-public-group>
+
+  <staging-public-group type="interpolator" first-id="0x01f40000">
+  </staging-public-group>
+
+  <staging-public-group type="mipmap" first-id="0x01f30000">
+  </staging-public-group>
+
+  <staging-public-group type="integer" first-id="0x01f20000">
+  </staging-public-group>
+
+  <staging-public-group type="transition" first-id="0x01f10000">
+  </staging-public-group>
+
+  <staging-public-group type="raw" first-id="0x01f00000">
+  </staging-public-group>
+
+  <staging-public-group type="bool" first-id="0x01ef0000">
+  </staging-public-group>
+
+  <staging-public-group type="fraction" first-id="0x01ee0000">
+  </staging-public-group>
+
+  <!-- ===============================================================
        DO NOT ADD UN-GROUPED ITEMS HERE
 
        Any new items (attrs, styles, ids, etc.) *must* be added in a
-       public-group block, as the preceding comment explains.
+       staging-public-group block, as the preceding comment explains.
        Items added outside of a group may have their value recalculated
        every time something new is added to this file.
        =============================================================== -->
diff --git a/core/tests/coretests/src/android/accessibilityservice/AccessibilityServiceTest.java b/core/tests/coretests/src/android/accessibilityservice/AccessibilityServiceTest.java
index c65ef9a..53ba140 100644
--- a/core/tests/coretests/src/android/accessibilityservice/AccessibilityServiceTest.java
+++ b/core/tests/coretests/src/android/accessibilityservice/AccessibilityServiceTest.java
@@ -16,18 +16,40 @@
 
 package android.accessibilityservice;
 
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.verify;
 
+import android.content.Context;
 import android.content.Intent;
+import android.graphics.PixelFormat;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.media.ImageReader;
+import android.os.Binder;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.RemoteException;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
 import android.view.accessibility.AccessibilityEvent;
+import android.window.WindowTokenClient;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.core.app.ApplicationProvider;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -42,6 +64,8 @@
 public class AccessibilityServiceTest {
     private static final String TAG = "AccessibilityServiceTest";
     private static final int CONNECTION_ID = 1;
+    private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams(
+            TYPE_ACCESSIBILITY_OVERLAY);
 
     private static class AccessibilityServiceTestClass extends AccessibilityService {
         private IAccessibilityServiceClient mCallback;
@@ -49,7 +73,11 @@
 
         AccessibilityServiceTestClass() {
             super();
-            attachBaseContext(InstrumentationRegistry.getContext());
+            Context context = ApplicationProvider.getApplicationContext();
+            final Display display = context.getSystemService(DisplayManager.class)
+                    .getDisplay(DEFAULT_DISPLAY);
+
+            attachBaseContext(context.createTokenContext(new WindowTokenClient(), display));
             mLooper = InstrumentationRegistry.getContext().getMainLooper();
         }
 
@@ -78,14 +106,33 @@
     private @Mock IBinder mMockIBinder;
     private IAccessibilityServiceClient mServiceInterface;
     private AccessibilityServiceTestClass mService;
+    private final SparseArray<IBinder> mWindowTokens = new SparseArray<>();
 
     @Before
-    public void setUp() throws RemoteException {
+    public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mService = new AccessibilityServiceTestClass();
+        mService.onCreate();
         mService.setupCallback(mMockClientForCallback);
         mServiceInterface = (IAccessibilityServiceClient) mService.onBind(new Intent());
         mServiceInterface.init(mMockConnection, CONNECTION_ID, mMockIBinder);
+        doAnswer(invocation -> {
+            Object[] args = invocation.getArguments();
+            final int displayId = (int) args[0];
+            final IBinder token = new Binder();
+            WindowManagerGlobal.getWindowManagerService().addWindowToken(token,
+                    TYPE_ACCESSIBILITY_OVERLAY, displayId, null /* options */);
+            mWindowTokens.put(displayId, token);
+            return token;
+        }).when(mMockConnection).getOverlayWindowToken(anyInt());
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        for (int i = mWindowTokens.size() - 1; i >= 0; --i) {
+            WindowManagerGlobal.getWindowManagerService().removeWindowToken(
+                    mWindowTokens.valueAt(i), mWindowTokens.keyAt(i));
+        }
     }
 
     @Test
@@ -101,4 +148,79 @@
 
         verify(mMockConnection).getSystemActions();
     }
+
+    @Test
+    public void testAddViewWithA11yServiceDerivedDisplayContext() throws Exception {
+        try (VirtualDisplaySession session = new VirtualDisplaySession()) {
+            final Context context = mService.createDisplayContext(session.getDisplay());
+            InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                    () -> context.getSystemService(WindowManager.class)
+                            .addView(new View(context), mParams)
+            );
+        }
+    }
+
+    @Test
+    public void testAddViewWithA11yServiceDerivedWindowContext() throws Exception {
+        try (VirtualDisplaySession session = new VirtualDisplaySession()) {
+            final Context context = mService.createDisplayContext(session.getDisplay())
+                    .createWindowContext(TYPE_ACCESSIBILITY_OVERLAY, null /* options */);
+            InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                    () -> context.getSystemService(WindowManager.class)
+                            .addView(new View(context), mParams)
+            );
+        }
+    }
+
+    @Test
+    public void testAddViewWithA11yServiceDerivedWindowContextWithDisplay() throws Exception {
+        try (VirtualDisplaySession session = new VirtualDisplaySession()) {
+            final Context context = mService.createWindowContext(session.getDisplay(),
+                    TYPE_ACCESSIBILITY_OVERLAY, null /* options */);
+            InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                    () -> context.getSystemService(WindowManager.class)
+                            .addView(new View(context), mParams)
+            );
+        }
+    }
+
+    @Test(expected = WindowManager.BadTokenException.class)
+    public void testAddViewWithA11yServiceDerivedWindowContextWithDifferentType()
+            throws Exception {
+        try (VirtualDisplaySession session = new VirtualDisplaySession()) {
+            final Context context = mService.createWindowContext(session.getDisplay(),
+                    TYPE_APPLICATION_OVERLAY, null /* options */);
+            InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                    () -> context.getSystemService(WindowManager.class)
+                            .addView(new View(context), mParams)
+            );
+        }
+    }
+
+
+    private static class VirtualDisplaySession implements AutoCloseable {
+        private final VirtualDisplay mVirtualDisplay;
+
+        VirtualDisplaySession() {
+            final DisplayManager displayManager = ApplicationProvider.getApplicationContext()
+                    .getSystemService(DisplayManager.class);
+            final int width = 800;
+            final int height = 480;
+            final int density = 160;
+            ImageReader reader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888,
+                    2 /* maxImages */);
+            mVirtualDisplay = displayManager.createVirtualDisplay(
+                    TAG, width, height, density, reader.getSurface(),
+                    VIRTUAL_DISPLAY_FLAG_PUBLIC | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
+        }
+
+        private Display getDisplay() {
+            return mVirtualDisplay.getDisplay();
+        }
+
+        @Override
+        public void close() throws Exception {
+            mVirtualDisplay.release();
+        }
+    }
 }
diff --git a/core/tests/coretests/src/android/content/ContextTest.java b/core/tests/coretests/src/android/content/ContextTest.java
index d1776fb..3d7d807 100644
--- a/core/tests/coretests/src/android/content/ContextTest.java
+++ b/core/tests/coretests/src/android/content/ContextTest.java
@@ -32,7 +32,6 @@
 import android.graphics.PixelFormat;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.VirtualDisplay;
-import android.inputmethodservice.InputMethodService;
 import android.media.ImageReader;
 import android.os.UserHandle;
 import android.view.Display;
@@ -140,13 +139,6 @@
     }
 
     @Test
-    public void testIsUiContext_InputMethodService_returnsTrue() {
-        final InputMethodService ims = new InputMethodService();
-
-        assertTrue(ims.isUiContext());
-    }
-
-    @Test
     public void testGetDisplayFromDisplayContextDerivedContextOnPrimaryDisplay() {
         verifyGetDisplayFromDisplayContextDerivedContext(false /* onSecondaryDisplay */);
     }
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
index 8643a37..b71d814 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
@@ -166,4 +166,7 @@
 
     public void logTrace(long timestamp, String where, String callingParams, int processId,
             long threadId, int callingUid, Bundle callingStack) {}
+
+    public void logTrace(long timestamp, String where, long loggingTypes, String callingParams,
+            int processId, long threadId, int callingUid, Bundle serializedCallingStackInBundle) {}
 }
diff --git a/core/tests/coretests/src/android/window/WindowContextControllerTest.java b/core/tests/coretests/src/android/window/WindowContextControllerTest.java
index 020f4a0..a6e351d 100644
--- a/core/tests/coretests/src/android/window/WindowContextControllerTest.java
+++ b/core/tests/coretests/src/android/window/WindowContextControllerTest.java
@@ -22,12 +22,15 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
+import android.content.res.Configuration;
 import android.os.Binder;
 import android.platform.test.annotations.Presubmit;
 import android.view.IWindowManager;
@@ -38,6 +41,8 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 /**
  * Tests for {@link WindowContextController}
@@ -53,15 +58,18 @@
 @Presubmit
 public class WindowContextControllerTest {
     private WindowContextController mController;
+    @Mock
     private IWindowManager mMockWms;
+    @Mock
+    private WindowTokenClient mMockToken;
 
     @Before
     public void setUp() throws Exception {
-        mMockWms = mock(IWindowManager.class);
-        mController = new WindowContextController(new Binder(), mMockWms);
-
-        doReturn(true).when(mMockWms).attachWindowContextToDisplayArea(any(), anyInt(),
-                anyInt(), any());
+        MockitoAnnotations.initMocks(this);
+        mController = new WindowContextController(mMockToken, mMockWms);
+        doNothing().when(mMockToken).onConfigurationChanged(any(), anyInt(), anyBoolean());
+        doReturn(new Configuration()).when(mMockWms).attachWindowContextToDisplayArea(any(),
+                anyInt(), anyInt(), any());
     }
 
     @Test(expected = IllegalStateException.class)
@@ -85,6 +93,8 @@
                 null /* options */);
 
         assertThat(mController.mAttachedToDisplayArea).isTrue();
+        verify(mMockToken).onConfigurationChanged(any(), eq(DEFAULT_DISPLAY),
+                eq(false) /* shouldReportConfigChange */);
 
         mController.detachIfNeeded();
 
diff --git a/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java b/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java
index 272f228..7d4412c 100644
--- a/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java
+++ b/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java
@@ -24,6 +24,7 @@
 import android.os.Parcel;
 import android.os.UserHandle;
 import android.util.ArrayMap;
+import android.view.InsetsState;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
@@ -59,7 +60,8 @@
                 new Binder() /* imeToken */,
                 true /* navbarColorManagedByIme */,
                 BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE,
-                true /* appFullscreen */,
+                new InsetsState() /* requestedState */,
+                "test" /* packageName */,
                 new int[0] /* transientBarTypes */);
 
         final RegisterStatusBarResult copy = clone(original);
@@ -79,7 +81,8 @@
         assertThat(copy.mImeToken).isSameInstanceAs(original.mImeToken);
         assertThat(copy.mNavbarColorManagedByIme).isEqualTo(original.mNavbarColorManagedByIme);
         assertThat(copy.mBehavior).isEqualTo(original.mBehavior);
-        assertThat(copy.mAppFullscreen).isEqualTo(original.mAppFullscreen);
+        assertThat(copy.mRequestedState).isEqualTo(original.mRequestedState);
+        assertThat(copy.mPackageName).isEqualTo(original.mPackageName);
         assertThat(copy.mTransientBarTypes).isEqualTo(original.mTransientBarTypes);
     }
 
diff --git a/core/tests/mockingcoretests/src/android/window/ConfigurationHelperTest.java b/core/tests/mockingcoretests/src/android/window/ConfigurationHelperTest.java
new file mode 100644
index 0000000..996d7b4
--- /dev/null
+++ b/core/tests/mockingcoretests/src/android/window/ConfigurationHelperTest.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+
+import android.app.ResourcesManager;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+/**
+ * Tests for {@link ConfigurationHelper}
+ *
+ * <p>Build/Install/Run:
+ *  atest FrameworksMockingCoreTests:ConfigurationHelperTest
+ *
+ * <p>This test class is a part of Window Manager Service tests and specified in
+ * {@link com.android.server.wm.test.filters.FrameworksTestsFilter}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class ConfigurationHelperTest {
+    MockitoSession mMockitoSession;
+    ResourcesManager mResourcesManager;
+
+    @Before
+    public void setUp() {
+        mMockitoSession = mockitoSession()
+                .strictness(Strictness.LENIENT)
+                .spyStatic(ResourcesManager.class)
+                .startMocking();
+        doReturn(mock(ResourcesManager.class)).when(ResourcesManager::getInstance);
+        mResourcesManager = ResourcesManager.getInstance();
+    }
+
+    @After
+    public void tearDown() {
+        mMockitoSession.finishMocking();
+    }
+
+    @Test
+    public void testShouldUpdateResources_NullConfig_ReturnsTrue() {
+        assertThat(ConfigurationHelper.shouldUpdateResources(new Binder(), null /* config */,
+                new Configuration(), new Configuration(), false /* displayChanged */,
+                null /* configChanged */)).isTrue();
+    }
+
+    @Test
+    public void testShouldUpdateResources_DisplayChanged_ReturnsTrue() {
+        assertThat(ConfigurationHelper.shouldUpdateResources(new Binder(), new Configuration(),
+                new Configuration(), new Configuration(), true /* displayChanged */,
+                null /* configChanged */)).isTrue();
+    }
+
+    @Test
+    public void testShouldUpdateResources_DifferentResources_ReturnsTrue() {
+        doReturn(false).when(mResourcesManager).isSameResourcesOverrideConfig(any(), any());
+
+        assertThat(ConfigurationHelper.shouldUpdateResources(new Binder(), new Configuration(),
+                new Configuration(), new Configuration(), false /* displayChanged */,
+                null /* configChanged */)).isTrue();
+    }
+
+    @Test
+    public void testShouldUpdateResources_DifferentBounds_ReturnsTrue() {
+        doReturn(true).when(mResourcesManager).isSameResourcesOverrideConfig(any(), any());
+
+        final Configuration config = new Configuration();
+        config.windowConfiguration.setBounds(new Rect(0, 0, 10, 10));
+        config.windowConfiguration.setMaxBounds(new Rect(0, 0, 20, 20));
+
+        final Configuration newConfig = new Configuration();
+        newConfig.windowConfiguration.setBounds(new Rect(0, 0, 20, 20));
+        newConfig.windowConfiguration.setMaxBounds(new Rect(0, 0, 20, 20));
+
+        assertThat(ConfigurationHelper.shouldUpdateResources(new Binder(), config, newConfig,
+                new Configuration(), false /* displayChanged */, null /* configChanged */))
+                .isTrue();
+    }
+
+    @Test
+    public void testShouldUpdateResources_SameConfig_ReturnsFalse() {
+        doReturn(true).when(mResourcesManager).isSameResourcesOverrideConfig(any(), any());
+
+        final Configuration config = new Configuration();
+        final Configuration newConfig = new Configuration();
+
+        assertThat(ConfigurationHelper.shouldUpdateResources(new Binder(), config, newConfig,
+                new Configuration(), false /* displayChanged */, null /* configChanged */))
+                .isFalse();
+    }
+
+    @Test
+    public void testShouldUpdateResources_DifferentConfig_ReturnsTrue() {
+        doReturn(true).when(mResourcesManager).isSameResourcesOverrideConfig(any(), any());
+
+        final Configuration config = new Configuration();
+        final Configuration newConfig = new Configuration();
+        newConfig.setToDefaults();
+
+        assertThat(ConfigurationHelper.shouldUpdateResources(new Binder(), config, newConfig,
+                new Configuration(), false /* displayChanged */, null /* configChanged */))
+                .isTrue();
+    }
+
+    @Test
+    public void testShouldUpdateResources_DifferentNonPublicConfig_ReturnsTrue() {
+        doReturn(true).when(mResourcesManager).isSameResourcesOverrideConfig(any(), any());
+
+        final Configuration config = new Configuration();
+        final Configuration newConfig = new Configuration();
+        newConfig.windowConfiguration.setAppBounds(new Rect(0, 0, 10, 10));
+
+        assertThat(ConfigurationHelper.shouldUpdateResources(new Binder(), config, newConfig,
+                new Configuration(), false /* displayChanged */, null /* configChanged */))
+                .isTrue();
+    }
+
+    @Test
+    public void testShouldUpdateResources_OverrideConfigChanged_ReturnsFalse() {
+        doReturn(true).when(mResourcesManager).isSameResourcesOverrideConfig(any(), any());
+
+        final Configuration config = new Configuration();
+        final Configuration newConfig = new Configuration();
+        final boolean configChanged = true;
+
+        assertThat(ConfigurationHelper.shouldUpdateResources(new Binder(), config, newConfig,
+                new Configuration(), false /* displayChanged */, configChanged))
+                .isEqualTo(configChanged);
+    }
+}
diff --git a/data/etc/car/Android.bp b/data/etc/car/Android.bp
index 084e1db..d1e4322 100644
--- a/data/etc/car/Android.bp
+++ b/data/etc/car/Android.bp
@@ -130,6 +130,13 @@
 }
 
 prebuilt_etc {
+    name: "allowed_privapp_com.google.android.car.adaslocation",
+    sub_dir: "permissions",
+    src: "com.google.android.car.adaslocation.xml",
+    filename_from_src: true,
+}
+
+prebuilt_etc {
     name: "allowed_privapp_com.google.android.car.kitchensink",
     sub_dir: "permissions",
     src: "com.google.android.car.kitchensink.xml",
diff --git a/data/etc/car/com.google.android.car.adaslocation.xml b/data/etc/car/com.google.android.car.adaslocation.xml
new file mode 100644
index 0000000..cc1ef3c
--- /dev/null
+++ b/data/etc/car/com.google.android.car.adaslocation.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<permissions>
+    <privapp-permissions package="com.google.android.car.adaslocation">
+        <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
+    </privapp-permissions>
+</permissions>
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index b67988e..50e6b90 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -79,12 +79,6 @@
       "group": "WM_DEBUG_WINDOW_TRANSITIONS",
       "at": "com\/android\/server\/wm\/Transition.java"
     },
-    "-2029985709": {
-      "message": "setFocusedTask: taskId=%d",
-      "level": "DEBUG",
-      "group": "WM_DEBUG_FOCUS",
-      "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
-    },
     "-2024464438": {
       "message": "app-onAnimationFinished(): mOuter=%s",
       "level": "DEBUG",
@@ -103,6 +97,12 @@
       "group": "WM_DEBUG_REMOTE_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
     },
+    "-2010331310": {
+      "message": "resumeTopActivity: Top activity resumed (dontWaitForPause) %s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_STATES",
+      "at": "com\/android\/server\/wm\/TaskFragment.java"
+    },
     "-2006946193": {
       "message": "setClientVisible: %s clientVisible=%b Callers=%s",
       "level": "VERBOSE",
@@ -211,6 +211,12 @@
       "group": "WM_DEBUG_WINDOW_ORGANIZER",
       "at": "com\/android\/server\/wm\/TaskOrganizerController.java"
     },
+    "-1886145147": {
+      "message": "resumeTopActivity: Going to sleep and all paused",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_STATES",
+      "at": "com\/android\/server\/wm\/TaskFragment.java"
+    },
     "-1884933373": {
       "message": "enableScreenAfterBoot: mDisplayEnabled=%b mForceDisplayEnabled=%b mShowingBootMessages=%b mSystemBooted=%b. %s",
       "level": "INFO",
@@ -247,12 +253,6 @@
       "group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
       "at": "com\/android\/server\/wm\/AppTransition.java"
     },
-    "-1861864501": {
-      "message": "resumeTopActivityLocked: Going to sleep and all paused",
-      "level": "DEBUG",
-      "group": "WM_DEBUG_STATES",
-      "at": "com\/android\/server\/wm\/Task.java"
-    },
     "-1844540996": {
       "message": "  Initial targets: %s",
       "level": "VERBOSE",
@@ -325,12 +325,6 @@
       "group": "WM_DEBUG_RECENTS_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RecentsAnimationController.java"
     },
-    "-1768090656": {
-      "message": "Re-launching after pause: %s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_STATES",
-      "at": "com\/android\/server\/wm\/Task.java"
-    },
     "-1750384749": {
       "message": "Launch on display check: allow launch on public display",
       "level": "DEBUG",
@@ -415,12 +409,6 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
-    "-1655805455": {
-      "message": "Enqueue pending stop if needed: %s wasStopping=%b visibleRequested=%b",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_STATES",
-      "at": "com\/android\/server\/wm\/Task.java"
-    },
     "-1647332198": {
       "message": "remove RecentTask %s when finishing user %d",
       "level": "INFO",
@@ -433,6 +421,12 @@
       "group": "WM_DEBUG_ADD_REMOVE",
       "at": "com\/android\/server\/wm\/ResetTargetTaskHelper.java"
     },
+    "-1633115609": {
+      "message": "Key dispatch not paused for screen off",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STATES",
+      "at": "com\/android\/server\/wm\/TaskFragment.java"
+    },
     "-1632122349": {
       "message": "Changing surface while display frozen: %s",
       "level": "VERBOSE",
@@ -487,6 +481,12 @@
       "group": "WM_DEBUG_WINDOW_TRANSITIONS",
       "at": "com\/android\/server\/wm\/Transition.java"
     },
+    "-1564228464": {
+      "message": "App died while pausing: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STATES",
+      "at": "com\/android\/server\/wm\/TaskFragment.java"
+    },
     "-1559645910": {
       "message": "Looking for task of type=%s, taskAffinity=%s, intent=%s, info=%s, preferredTDA=%s",
       "level": "DEBUG",
@@ -565,12 +565,6 @@
       "group": "WM_DEBUG_CONFIGURATION",
       "at": "com\/android\/server\/wm\/ActivityStarter.java"
     },
-    "-1492696222": {
-      "message": "App died during pause, not stopping: %s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_STATES",
-      "at": "com\/android\/server\/wm\/Task.java"
-    },
     "-1480772131": {
       "message": "No app or window is requesting an orientation, return %d for display id=%d",
       "level": "VERBOSE",
@@ -637,12 +631,24 @@
       "group": "WM_DEBUG_ADD_REMOVE",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-1421296808": {
+      "message": "Moving to RESUMED: %s (in existing)",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STATES",
+      "at": "com\/android\/server\/wm\/TaskFragment.java"
+    },
     "-1419762046": {
       "message": "moveRootTaskToDisplay: moving taskId=%d to displayId=%d",
       "level": "DEBUG",
       "group": "WM_DEBUG_TASKS",
       "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
     },
+    "-1419461256": {
+      "message": "resumeTopActivity: Resumed %s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_STATES",
+      "at": "com\/android\/server\/wm\/TaskFragment.java"
+    },
     "-1413901262": {
       "message": "startRecentsActivity(): intent=%s",
       "level": "DEBUG",
@@ -709,6 +715,12 @@
       "group": "WM_DEBUG_IME",
       "at": "com\/android\/server\/wm\/WindowState.java"
     },
+    "-1311436264": {
+      "message": "Unregister task fragment organizer=%s uid=%d pid=%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_ORGANIZER",
+      "at": "com\/android\/server\/wm\/TaskFragmentOrganizerController.java"
+    },
     "-1305966693": {
       "message": "Sending position change to %s, onTop: %b",
       "level": "VERBOSE",
@@ -805,6 +817,12 @@
       "group": "WM_DEBUG_STATES",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "-1187377055": {
+      "message": "Enqueue pending stop if needed: %s wasStopping=%b visibleRequested=%b",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STATES",
+      "at": "com\/android\/server\/wm\/TaskFragment.java"
+    },
     "-1176488860": {
       "message": "SURFACE isSecure=%b: %s",
       "level": "INFO",
@@ -919,12 +937,6 @@
       "group": "WM_DEBUG_RECENTS_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RecentsAnimation.java"
     },
-    "-1066383762": {
-      "message": "Sleep still waiting to pause %s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_STATES",
-      "at": "com\/android\/server\/wm\/Task.java"
-    },
     "-1060365734": {
       "message": "Attempted to add QS dialog window with bad token %s.  Aborting.",
       "level": "WARN",
@@ -985,6 +997,12 @@
       "group": "WM_DEBUG_STARTING_WINDOW",
       "at": "com\/android\/server\/wm\/WindowState.java"
     },
+    "-957060823": {
+      "message": "Moving to PAUSING: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STATES",
+      "at": "com\/android\/server\/wm\/TaskFragment.java"
+    },
     "-951939129": {
       "message": "Unregister task organizer=%s uid=%d",
       "level": "VERBOSE",
@@ -1201,6 +1219,12 @@
       "group": "WM_DEBUG_STATES",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "-706481945": {
+      "message": "TaskFragment parent info changed name=%s parentTaskId=%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_ORGANIZER",
+      "at": "com\/android\/server\/wm\/TaskFragmentOrganizerController.java"
+    },
     "-705939410": {
       "message": "Waiting for pause to complete...",
       "level": "VERBOSE",
@@ -1237,12 +1261,6 @@
       "group": "WM_DEBUG_WINDOW_TRANSITIONS",
       "at": "com\/android\/server\/wm\/Transition.java"
     },
-    "-672228342": {
-      "message": "resumeTopActivityLocked: Top activity resumed %s",
-      "level": "DEBUG",
-      "group": "WM_DEBUG_STATES",
-      "at": "com\/android\/server\/wm\/Task.java"
-    },
     "-668956537": {
       "message": "  THUMBNAIL %s: CREATE",
       "level": "INFO",
@@ -1267,11 +1285,11 @@
       "group": "WM_DEBUG_REMOTE_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
     },
-    "-650261962": {
-      "message": "Sleep needs to pause %s",
+    "-648891906": {
+      "message": "Activity not running or entered PiP, resuming next.",
       "level": "VERBOSE",
       "group": "WM_DEBUG_STATES",
-      "at": "com\/android\/server\/wm\/Task.java"
+      "at": "com\/android\/server\/wm\/TaskFragment.java"
     },
     "-641258376": {
       "message": "realStartActivityLocked: Skipping start of r=%s some activities pausing...",
@@ -1309,12 +1327,6 @@
       "group": "WM_DEBUG_BOOT",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
-    "-606328116": {
-      "message": "resumeTopActivityLocked: Top activity resumed (dontWaitForPause) %s",
-      "level": "DEBUG",
-      "group": "WM_DEBUG_STATES",
-      "at": "com\/android\/server\/wm\/Task.java"
-    },
     "-597091183": {
       "message": "Delete TaskDisplayArea uid=%d",
       "level": "VERBOSE",
@@ -1375,11 +1387,11 @@
       "group": "WM_SHOW_TRANSACTIONS",
       "at": "com\/android\/server\/wm\/WindowAnimator.java"
     },
-    "-533690126": {
-      "message": "resumeTopActivityLocked: Resumed %s",
-      "level": "DEBUG",
-      "group": "WM_DEBUG_STATES",
-      "at": "com\/android\/server\/wm\/Task.java"
+    "-542756093": {
+      "message": "TaskFragment vanished name=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_ORGANIZER",
+      "at": "com\/android\/server\/wm\/TaskFragmentOrganizerController.java"
     },
     "-532081937": {
       "message": "  Commit activity becoming invisible: %s",
@@ -1387,11 +1399,11 @@
       "group": "WM_DEBUG_WINDOW_TRANSITIONS",
       "at": "com\/android\/server\/wm\/Transition.java"
     },
-    "-527683022": {
-      "message": "resumeTopActivityLocked: Skip resume: some activity pausing.",
+    "-521613870": {
+      "message": "App died during pause, not stopping: %s",
       "level": "VERBOSE",
       "group": "WM_DEBUG_STATES",
-      "at": "com\/android\/server\/wm\/Task.java"
+      "at": "com\/android\/server\/wm\/TaskFragment.java"
     },
     "-519504830": {
       "message": "applyAnimation: anim=%s nextAppTransition=ANIM_CUSTOM transit=%s isEntrance=%b Callers=%s",
@@ -1477,18 +1489,6 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/DisplayRotation.java"
     },
-    "-427457280": {
-      "message": "App died while pausing: %s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_STATES",
-      "at": "com\/android\/server\/wm\/Task.java"
-    },
-    "-417514857": {
-      "message": "Key dispatch not paused for screen off",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_STATES",
-      "at": "com\/android\/server\/wm\/Task.java"
-    },
     "-415865166": {
       "message": "findFocusedWindow: Found new focus @ %s",
       "level": "VERBOSE",
@@ -1597,11 +1597,17 @@
       "group": "WM_DEBUG_LOCKTASK",
       "at": "com\/android\/server\/wm\/LockTaskController.java"
     },
-    "-303497363": {
-      "message": "reparent: moving activity=%s to task=%d at %d",
+    "-312353598": {
+      "message": "Executing finish of activity: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STATES",
+      "at": "com\/android\/server\/wm\/TaskFragment.java"
+    },
+    "-310337305": {
+      "message": "Activity config changed during resume: %s, new next: %s",
       "level": "INFO",
-      "group": "WM_DEBUG_ADD_REMOVE",
-      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+      "group": "WM_DEBUG_STATES",
+      "at": "com\/android\/server\/wm\/TaskFragment.java"
     },
     "-302468788": {
       "message": "Expected target rootTask=%s to be top most but found rootTask=%s",
@@ -1621,12 +1627,6 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
-    "-279436615": {
-      "message": "Moving to PAUSING: %s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_STATES",
-      "at": "com\/android\/server\/wm\/Task.java"
-    },
     "-262984451": {
       "message": "Relaunch failed %s",
       "level": "INFO",
@@ -1639,6 +1639,12 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "-248761393": {
+      "message": "startPausing: taskFrag =%s mResumedActivity=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_STATES",
+      "at": "com\/android\/server\/wm\/TaskFragment.java"
+    },
     "-240296576": {
       "message": "handleAppTransitionReady: displayId=%d appTransition={%s} openingApps=[%s] closingApps=[%s] transit=%s",
       "level": "VERBOSE",
@@ -1651,12 +1657,6 @@
       "group": "WM_DEBUG_CONFIGURATION",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
-    "-234244777": {
-      "message": "Activity config changed during resume: %s, new next: %s",
-      "level": "INFO",
-      "group": "WM_DEBUG_STATES",
-      "at": "com\/android\/server\/wm\/Task.java"
-    },
     "-230587670": {
       "message": "SyncGroup %d:  Unfinished container: %s",
       "level": "VERBOSE",
@@ -1723,12 +1723,6 @@
       "group": "WM_DEBUG_STARTING_WINDOW",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
-    "-118786523": {
-      "message": "Resume failed; resetting state to %s: %s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_STATES",
-      "at": "com\/android\/server\/wm\/Task.java"
-    },
     "-116086365": {
       "message": "******************** ENABLING SCREEN!",
       "level": "INFO",
@@ -1777,6 +1771,18 @@
       "group": "WM_SHOW_TRANSACTIONS",
       "at": "com\/android\/server\/wm\/Session.java"
     },
+    "-80004683": {
+      "message": "Resume failed; resetting state to %s: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STATES",
+      "at": "com\/android\/server\/wm\/TaskFragment.java"
+    },
+    "-55185509": {
+      "message": "setFocusedTask: taskId=%d touchedActivity=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_FOCUS",
+      "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
+    },
     "-50336993": {
       "message": "moveFocusableActivityToTop: activity=%s",
       "level": "DEBUG",
@@ -1909,12 +1915,6 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowContextListenerController.java"
     },
-    "94402792": {
-      "message": "Moving to RESUMED: %s (in existing)",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_STATES",
-      "at": "com\/android\/server\/wm\/Task.java"
-    },
     "95216706": {
       "message": "hideIme target: %s ",
       "level": "DEBUG",
@@ -1933,6 +1933,12 @@
       "group": "WM_DEBUG_APP_TRANSITIONS",
       "at": "com\/android\/server\/wm\/AppTransitionController.java"
     },
+    "102618780": {
+      "message": "resumeTopActivity: Pausing %s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_STATES",
+      "at": "com\/android\/server\/wm\/TaskFragment.java"
+    },
     "108170907": {
       "message": "Add starting %s: startingData=%s",
       "level": "VERBOSE",
@@ -2173,6 +2179,18 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "327461496": {
+      "message": "Complete pause: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STATES",
+      "at": "com\/android\/server\/wm\/TaskFragment.java"
+    },
+    "341055768": {
+      "message": "resumeTopActivity: Skip resume: need to start pausing",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STATES",
+      "at": "com\/android\/server\/wm\/TaskFragment.java"
+    },
     "342460966": {
       "message": "DRAG %s: pos=(%d,%d)",
       "level": "INFO",
@@ -2227,11 +2245,11 @@
       "group": "WM_DEBUG_BOOT",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
-    "391189028": {
-      "message": "pauseBackTasks: task=%s mResumedActivity=%s",
-      "level": "DEBUG",
+    "378825104": {
+      "message": "Enqueueing pending pause: %s",
+      "level": "VERBOSE",
       "group": "WM_DEBUG_STATES",
-      "at": "com\/android\/server\/wm\/TaskDisplayArea.java"
+      "at": "com\/android\/server\/wm\/TaskFragment.java"
     },
     "397105698": {
       "message": "grantEmbeddedWindowFocus remove request for win=%s dropped since no candidate was found",
@@ -2365,6 +2383,12 @@
       "group": "WM_SHOW_TRANSACTIONS",
       "at": "com\/android\/server\/wm\/WindowSurfaceController.java"
     },
+    "573582981": {
+      "message": "reparent: moving activity=%s to new task fragment in task=%d at %d",
+      "level": "INFO",
+      "group": "WM_DEBUG_ADD_REMOVE",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "579298675": {
       "message": "Moving to DESTROYED: %s (removed from history)",
       "level": "VERBOSE",
@@ -2467,6 +2491,12 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "660908897": {
+      "message": "Auto-PIP allowed, entering PIP mode directly: %s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_STATES",
+      "at": "com\/android\/server\/wm\/TaskFragment.java"
+    },
     "662572728": {
       "message": "Attempted to add a toast window with bad token %s.  Aborting.",
       "level": "WARN",
@@ -2485,12 +2515,24 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "669361121": {
+      "message": "Sleep still need to stop %d activities",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STATES",
+      "at": "com\/android\/server\/wm\/TaskFragment.java"
+    },
     "674932310": {
       "message": "Setting Intent of %s to target %s",
       "level": "VERBOSE",
       "group": "WM_DEBUG_TASKS",
       "at": "com\/android\/server\/wm\/Task.java"
     },
+    "675705156": {
+      "message": "resumeTopActivity: Top activity resumed %s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_STATES",
+      "at": "com\/android\/server\/wm\/TaskFragment.java"
+    },
     "685047360": {
       "message": "Resizing window %s",
       "level": "VERBOSE",
@@ -2521,12 +2563,6 @@
       "group": "WM_DEBUG_STATES",
       "at": "com\/android\/server\/wm\/ActivityTaskSupervisor.java"
     },
-    "709500946": {
-      "message": "resumeTopActivityLocked: Skip resume: need to start pausing",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_STATES",
-      "at": "com\/android\/server\/wm\/Task.java"
-    },
     "715749922": {
       "message": "Allowlisting %d:%s",
       "level": "WARN",
@@ -2629,12 +2665,6 @@
       "group": "WM_DEBUG_STATES",
       "at": "com\/android\/server\/wm\/RootWindowContainer.java"
     },
-    "897964776": {
-      "message": "Complete pause: %s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_STATES",
-      "at": "com\/android\/server\/wm\/Task.java"
-    },
     "898863925": {
       "message": "Attempted to add QS dialog window with unknown token %s.  Aborting.",
       "level": "WARN",
@@ -2659,6 +2689,12 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/WindowStateAnimator.java"
     },
+    "935418348": {
+      "message": "resumeTopActivity: Skip resume: some activity pausing.",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STATES",
+      "at": "com\/android\/server\/wm\/TaskFragment.java"
+    },
     "950074526": {
       "message": "setLockTaskMode: Can't lock due to auth",
       "level": "WARN",
@@ -2707,11 +2743,11 @@
       "group": "WM_DEBUG_TASKS",
       "at": "com\/android\/server\/wm\/ActivityTaskSupervisor.java"
     },
-    "988389910": {
-      "message": "resumeTopActivityLocked: Pausing %s",
-      "level": "DEBUG",
+    "987903142": {
+      "message": "Sleep needs to pause %s",
+      "level": "VERBOSE",
       "group": "WM_DEBUG_STATES",
-      "at": "com\/android\/server\/wm\/Task.java"
+      "at": "com\/android\/server\/wm\/TaskFragment.java"
     },
     "996960396": {
       "message": "Starting Transition %d",
@@ -2719,18 +2755,24 @@
       "group": "WM_DEBUG_WINDOW_TRANSITIONS",
       "at": "com\/android\/server\/wm\/Transition.java"
     },
-    "1001509841": {
-      "message": "Auto-PIP allowed, entering PIP mode directly: %s",
-      "level": "DEBUG",
-      "group": "WM_DEBUG_STATES",
-      "at": "com\/android\/server\/wm\/Task.java"
-    },
     "1001904964": {
       "message": "***** BOOT TIMEOUT: forcing display enabled",
       "level": "WARN",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "1011462000": {
+      "message": "Re-launching after pause: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STATES",
+      "at": "com\/android\/server\/wm\/TaskFragment.java"
+    },
+    "1022095595": {
+      "message": "TaskFragment info changed name=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_ORGANIZER",
+      "at": "com\/android\/server\/wm\/TaskFragmentOrganizerController.java"
+    },
     "1023413388": {
       "message": "Finish waiting for pause of: %s",
       "level": "VERBOSE",
@@ -2917,6 +2959,12 @@
       "group": "WM_DEBUG_STATES",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "1284122013": {
+      "message": "TaskFragment appeared name=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_ORGANIZER",
+      "at": "com\/android\/server\/wm\/TaskFragmentOrganizerController.java"
+    },
     "1288731814": {
       "message": "WindowState.hideLw: setting mFocusMayChange true",
       "level": "INFO",
@@ -3163,12 +3211,6 @@
       "group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
       "at": "com\/android\/server\/wm\/WindowContainer.java"
     },
-    "1585450696": {
-      "message": "resumeTopActivityLocked: Restarting %s",
-      "level": "DEBUG",
-      "group": "WM_DEBUG_STATES",
-      "at": "com\/android\/server\/wm\/Task.java"
-    },
     "1589610525": {
       "message": "applyAnimation NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS: anim=%s transit=%s isEntrance=true Callers=%s",
       "level": "VERBOSE",
@@ -3217,6 +3259,12 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/DisplayContent.java"
     },
+    "1653025361": {
+      "message": "Register task fragment organizer=%s uid=%d pid=%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_ORGANIZER",
+      "at": "com\/android\/server\/wm\/TaskFragmentOrganizerController.java"
+    },
     "1653210583": {
       "message": "Removing app %s delayed=%b animation=%s animating=%b",
       "level": "VERBOSE",
@@ -3385,18 +3433,6 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
-    "1837992242": {
-      "message": "Executing finish of activity: %s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_STATES",
-      "at": "com\/android\/server\/wm\/Task.java"
-    },
-    "1847414670": {
-      "message": "Activity not running or entered PiP, resuming next.",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_STATES",
-      "at": "com\/android\/server\/wm\/Task.java"
-    },
     "1853793312": {
       "message": "Notify removed startingWindow %s",
       "level": "VERBOSE",
@@ -3409,6 +3445,12 @@
       "group": "WM_DEBUG_FOCUS",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "1856783490": {
+      "message": "resumeTopActivity: Restarting %s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_STATES",
+      "at": "com\/android\/server\/wm\/TaskFragment.java"
+    },
     "1865125884": {
       "message": "finishScreenTurningOn: mAwake=%b, mScreenOnEarly=%b, mScreenOnFully=%b, mKeyguardDrawComplete=%b, mWindowManagerDrawComplete=%b",
       "level": "DEBUG",
@@ -3421,30 +3463,24 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
-    "1884961873": {
-      "message": "Sleep still need to stop %d activities",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_STATES",
-      "at": "com\/android\/server\/wm\/Task.java"
-    },
     "1891501279": {
       "message": "cancelAnimation(): reason=%s",
       "level": "DEBUG",
       "group": "WM_DEBUG_REMOTE_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
     },
-    "1894239744": {
-      "message": "Enqueueing pending pause: %s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_STATES",
-      "at": "com\/android\/server\/wm\/Task.java"
-    },
     "1903353011": {
       "message": "notifyAppStopped: %s",
       "level": "VERBOSE",
       "group": "WM_DEBUG_ADD_REMOVE",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "1912291550": {
+      "message": "Sleep still waiting to pause %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STATES",
+      "at": "com\/android\/server\/wm\/TaskFragment.java"
+    },
     "1918448345": {
       "message": "Task appeared taskId=%d",
       "level": "VERBOSE",
diff --git a/libs/WindowManager/Shell/res/layout/split_outline.xml b/libs/WindowManager/Shell/res/layout/split_outline.xml
new file mode 100644
index 0000000..4e2a77f
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/split_outline.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<com.android.wm.shell.splitscreen.OutlineRoot
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <com.android.wm.shell.splitscreen.OutlineView
+        android:id="@+id/split_outline"
+        android:layout_height="match_parent"
+        android:layout_width="match_parent" />
+
+</com.android.wm.shell.splitscreen.OutlineRoot>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
index 34c66a4..bf074b0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
@@ -97,6 +97,14 @@
         b.setParent(sc);
     }
 
+    public void setPosition(@NonNull SurfaceControl.Transaction tx, int displayId, int x, int y) {
+        final SurfaceControl sc = mLeashes.get(displayId);
+        if (sc == null) {
+            throw new IllegalArgumentException("can't find display" + displayId);
+        }
+        tx.setPosition(sc, x, y);
+    }
+
     @Override
     public void onDisplayAreaAppeared(@NonNull DisplayAreaInfo displayAreaInfo,
             @NonNull SurfaceControl leash) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
index 1861e48..2f3214d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
@@ -40,6 +40,8 @@
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
+import com.android.wm.shell.common.SyncTransactionQueue;
+
 import java.io.PrintWriter;
 import java.util.concurrent.Executor;
 
@@ -74,6 +76,7 @@
 
     private final ShellTaskOrganizer mTaskOrganizer;
     private final Executor mShellExecutor;
+    private final SyncTransactionQueue mSyncQueue;
 
     private ActivityManager.RunningTaskInfo mTaskInfo;
     private WindowContainerToken mTaskToken;
@@ -89,11 +92,12 @@
     private final Rect mTmpRootRect = new Rect();
     private final int[] mTmpLocation = new int[2];
 
-    public TaskView(Context context, ShellTaskOrganizer organizer) {
+    public TaskView(Context context, ShellTaskOrganizer organizer, SyncTransactionQueue syncQueue) {
         super(context, null, 0, 0, true /* disableBackgroundLayer */);
 
         mTaskOrganizer = organizer;
         mShellExecutor = organizer.getExecutor();
+        mSyncQueue = syncQueue;
         setUseAlpha();
         getHolder().addCallback(this);
         mGuard.open("release");
@@ -189,8 +193,7 @@
 
         WindowContainerTransaction wct = new WindowContainerTransaction();
         wct.setBounds(mTaskToken, mTmpRect);
-        // TODO(b/151449487): Enable synchronization
-        mTaskOrganizer.applyTransaction(wct);
+        mSyncQueue.queue(wct);
     }
 
     /**
@@ -236,14 +239,16 @@
     private void updateTaskVisibility() {
         WindowContainerTransaction wct = new WindowContainerTransaction();
         wct.setHidden(mTaskToken, !mSurfaceCreated /* hidden */);
-        mTaskOrganizer.applyTransaction(wct);
-        // TODO(b/151449487): Only call callback once we enable synchronization
-        if (mListener != null) {
-            final int taskId = mTaskInfo.taskId;
+        mSyncQueue.queue(wct);
+        if (mListener == null) {
+            return;
+        }
+        int taskId = mTaskInfo.taskId;
+        mSyncQueue.runInSync((t) -> {
             mListenerExecutor.execute(() -> {
                 mListener.onTaskVisibilityChanged(taskId, mSurfaceCreated);
             });
-        }
+        });
     }
 
     @Override
@@ -264,10 +269,12 @@
             updateTaskVisibility();
         }
         mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, true);
-        // TODO: Synchronize show with the resize
         onLocationChanged();
         if (taskInfo.taskDescription != null) {
-            setResizeBackgroundColor(taskInfo.taskDescription.getBackgroundColor());
+            int backgroundColor = taskInfo.taskDescription.getBackgroundColor();
+            mSyncQueue.runInSync((t) -> {
+                setResizeBackgroundColor(t, backgroundColor);
+            });
         }
 
         if (mListener != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java
index 58ca1fb..8286d10 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java
@@ -20,8 +20,8 @@
 import android.content.Context;
 
 import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.annotations.ExternalThread;
-import com.android.wm.shell.common.annotations.ShellMainThread;
 
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
@@ -30,12 +30,14 @@
 public class TaskViewFactoryController {
     private final ShellTaskOrganizer mTaskOrganizer;
     private final ShellExecutor mShellExecutor;
+    private final SyncTransactionQueue mSyncQueue;
     private final TaskViewFactory mImpl = new TaskViewFactoryImpl();
 
     public TaskViewFactoryController(ShellTaskOrganizer taskOrganizer,
-            ShellExecutor shellExecutor) {
+            ShellExecutor shellExecutor, SyncTransactionQueue syncQueue) {
         mTaskOrganizer = taskOrganizer;
         mShellExecutor = shellExecutor;
+        mSyncQueue = syncQueue;
     }
 
     public TaskViewFactory asTaskViewFactory() {
@@ -44,7 +46,7 @@
 
     /** Creates an {@link TaskView} */
     public void create(@UiContext Context context, Executor executor, Consumer<TaskView> onCreate) {
-        TaskView taskView = new TaskView(context, mTaskOrganizer);
+        TaskView taskView = new TaskView(context, mTaskOrganizer, mSyncQueue);
         executor.execute(() -> {
             onCreate.accept(taskView);
         });
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 09fcb86e5..f3f66dc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -85,6 +85,7 @@
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TaskStackListenerCallback;
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.pip.PinnedStackListenerForwarder;
@@ -137,6 +138,7 @@
     private final TaskStackListenerImpl mTaskStackListener;
     private final ShellTaskOrganizer mTaskOrganizer;
     private final DisplayController mDisplayController;
+    private final SyncTransactionQueue mSyncQueue;
 
     // Used to post to main UI thread
     private final ShellExecutor mMainExecutor;
@@ -209,7 +211,8 @@
             ShellTaskOrganizer organizer,
             DisplayController displayController,
             ShellExecutor mainExecutor,
-            Handler mainHandler) {
+            Handler mainHandler,
+            SyncTransactionQueue syncQueue) {
         BubbleLogger logger = new BubbleLogger(uiEventLogger);
         BubblePositioner positioner = new BubblePositioner(context, windowManager);
         BubbleData data = new BubbleData(context, logger, positioner, mainExecutor);
@@ -217,7 +220,7 @@
                 new BubbleDataRepository(context, launcherApps, mainExecutor),
                 statusBarService, windowManager, windowManagerShellWrapper, launcherApps,
                 logger, taskStackListener, organizer, positioner, displayController, mainExecutor,
-                mainHandler);
+                mainHandler, syncQueue);
     }
 
     /**
@@ -239,7 +242,8 @@
             BubblePositioner positioner,
             DisplayController displayController,
             ShellExecutor mainExecutor,
-            Handler mainHandler) {
+            Handler mainHandler,
+            SyncTransactionQueue syncQueue) {
         mContext = context;
         mLauncherApps = launcherApps;
         mBarService = statusBarService == null
@@ -262,6 +266,7 @@
         mSavedBubbleKeysPerUser = new SparseSetArray<>();
         mBubbleIconFactory = new BubbleIconFactory(context);
         mDisplayController = displayController;
+        mSyncQueue = syncQueue;
     }
 
     public void initialize() {
@@ -561,6 +566,10 @@
         return mTaskOrganizer;
     }
 
+    SyncTransactionQueue getSyncTransactionQueue() {
+        return mSyncQueue;
+    }
+
     /** Contains information to help position things on the screen.  */
     BubblePositioner getPositioner() {
         return mBubblePositioner;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index 9687ec6..a02fa9b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -25,6 +25,7 @@
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_EXPANDED_VIEW;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.wm.shell.bubbles.BubblePositioner.MAX_HEIGHT;
 
 import android.annotation.NonNull;
 import android.annotation.SuppressLint;
@@ -60,7 +61,6 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.policy.ScreenDecorationsUtils;
-import com.android.launcher3.icons.IconNormalizer;
 import com.android.wm.shell.R;
 import com.android.wm.shell.TaskView;
 import com.android.wm.shell.common.AlphaOptimizedButton;
@@ -77,7 +77,6 @@
 
     // The triangle pointing to the expanded view
     private View mPointerView;
-    private int mPointerMargin;
     @Nullable private int[] mExpandedViewContainerLocation;
 
     private AlphaOptimizedButton mManageButton;
@@ -102,9 +101,6 @@
      */
     private boolean mIsAlphaAnimating = false;
 
-    private int mMinHeight;
-    private int mOverflowHeight;
-    private int mManageButtonHeight;
     private int mPointerWidth;
     private int mPointerHeight;
     private float mPointerRadius;
@@ -338,7 +334,8 @@
             bringChildToFront(mOverflowView);
             mManageButton.setVisibility(GONE);
         } else {
-            mTaskView = new TaskView(mContext, mController.getTaskOrganizer());
+            mTaskView = new TaskView(mContext, mController.getTaskOrganizer(),
+                    mController.getSyncTransactionQueue());
             mTaskView.setListener(mController.getMainExecutor(), mTaskViewListener);
             mExpandedViewContainer.addView(mTaskView);
             bringChildToFront(mTaskView);
@@ -347,12 +344,8 @@
 
     void updateDimensions() {
         Resources res = getResources();
-        mMinHeight = res.getDimensionPixelSize(R.dimen.bubble_expanded_default_height);
-        mOverflowHeight = res.getDimensionPixelSize(R.dimen.bubble_overflow_height);
-
         updateFontSize();
 
-        mPointerMargin = res.getDimensionPixelSize(R.dimen.bubble_pointer_margin);
         mPointerWidth = res.getDimensionPixelSize(R.dimen.bubble_pointer_width);
         mPointerHeight = res.getDimensionPixelSize(R.dimen.bubble_pointer_height);
         mPointerRadius = getResources().getDimensionPixelSize(R.dimen.bubble_pointer_radius);
@@ -368,7 +361,6 @@
             updatePointerView();
         }
 
-        mManageButtonHeight = res.getDimensionPixelSize(R.dimen.bubble_manage_button_height);
         if (mManageButton != null) {
             int visibility = mManageButton.getVisibility();
             removeView(mManageButton);
@@ -632,12 +624,11 @@
         }
 
         if ((mBubble != null && mTaskView != null) || mIsOverflow) {
-            float desiredHeight = mIsOverflow
-                    ? mPositioner.isLargeScreen() ? getMaxExpandedHeight() : mOverflowHeight
-                    : mBubble.getDesiredHeight(mContext);
-            desiredHeight = Math.max(desiredHeight, mMinHeight);
-            float height = Math.min(desiredHeight, getMaxExpandedHeight());
-            height = Math.max(height, mMinHeight);
+            float desiredHeight = mPositioner.getExpandedViewHeight(mBubble);
+            int maxHeight = mPositioner.getMaxExpandedViewHeight(mIsOverflow);
+            float height = desiredHeight == MAX_HEIGHT
+                    ? maxHeight
+                    : Math.min(desiredHeight, maxHeight);
             FrameLayout.LayoutParams lp = mIsOverflow
                     ? (FrameLayout.LayoutParams) mOverflowView.getLayoutParams()
                     : (FrameLayout.LayoutParams) mTaskView.getLayoutParams();
@@ -661,23 +652,6 @@
         }
     }
 
-    private int getMaxExpandedHeight() {
-        int expandedContainerY = mExpandedViewContainerLocation != null
-                // Remove top insets back here because availableRect.height would account for that
-                ? mExpandedViewContainerLocation[1] - mPositioner.getInsets().top
-                : 0;
-        int settingsHeight = mIsOverflow ? 0 : mManageButtonHeight;
-        int pointerHeight = mPositioner.showBubblesVertically()
-                ? mPointerWidth
-                : (int) (mPointerHeight - mPointerOverlap + mPointerMargin);
-        return mPositioner.getAvailableRect().height()
-                - expandedContainerY
-                - getPaddingTop()
-                - getPaddingBottom()
-                - settingsHeight
-                - pointerHeight;
-    }
-
     /**
      * Update appearance of the expanded view being displayed.
      *
@@ -727,14 +701,11 @@
                 : mPointerHeight - mPointerOverlap;
         setPadding((int) paddingLeft, (int) paddingTop, (int) paddingRight, 0);
 
-        final float expandedViewY = mPositioner.getExpandedViewY();
-        // TODO: I don't understand why it works but it does - why normalized in portrait
-        //  & not in landscape? Am I missing ~2dp in the portrait expandedViewY calculation?
-        final float normalizedSize = IconNormalizer.getNormalizedCircleSize(
-                mPositioner.getBubbleSize());
-        final float bubbleCenter = showVertically
-                ? bubblePosition + (mPositioner.getBubbleSize() / 2f) - expandedViewY
-                : bubblePosition + (normalizedSize / 2f) - mPointerWidth;
+        // Subtract the expandedViewY here because the pointer is placed within the expandedView.
+        float pointerPosition = mPositioner.getPointerPosition(bubblePosition);
+        final float bubbleCenter = mPositioner.showBubblesVertically()
+                ? pointerPosition - mPositioner.getExpandedViewY(mBubble, bubblePosition)
+                : pointerPosition;
         // Post because we need the width of the view
         post(() -> {
             float pointerY;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index c600f56..0a856a8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -34,6 +34,7 @@
 
 import androidx.annotation.VisibleForTesting;
 
+import com.android.launcher3.icons.IconNormalizer;
 import com.android.wm.shell.R;
 
 import java.lang.annotation.Retention;
@@ -58,6 +59,8 @@
 
     /** When the bubbles are collapsed in a stack only some of them are shown, this is how many. **/
     public static final int NUM_VISIBLE_WHEN_RESTING = 2;
+    /** Indicates a bubble's height should be the maximum available space. **/
+    public static final int MAX_HEIGHT = -1;
 
     private Context mContext;
     private WindowManager mWindowManager;
@@ -68,13 +71,16 @@
     private int mMaxBubbles;
 
     private int mBubbleSize;
-    private int mBubbleBadgeSize;
     private int mSpacingBetweenBubbles;
     private int mExpandedViewLargeScreenWidth;
     private int mExpandedViewPadding;
     private int mPointerMargin;
-    private float mPointerWidth;
-    private float mPointerHeight;
+    private int mPointerWidth;
+    private int mPointerHeight;
+    private int mPointerOverlap;
+    private int mManageButtonHeight;
+    private int mExpandedViewMinHeight;
+    private int mOverflowHeight;
 
     private PointF mPinLocation;
     private PointF mRestingStackPosition;
@@ -151,7 +157,6 @@
 
         Resources res = mContext.getResources();
         mBubbleSize = res.getDimensionPixelSize(R.dimen.bubble_size);
-        mBubbleBadgeSize = res.getDimensionPixelSize(R.dimen.bubble_badge_size);
         mSpacingBetweenBubbles = res.getDimensionPixelSize(R.dimen.bubble_spacing);
         mDefaultMaxBubbles = res.getInteger(R.integer.bubbles_max_rendered);
 
@@ -161,6 +166,10 @@
         mPointerWidth = res.getDimensionPixelSize(R.dimen.bubble_pointer_width);
         mPointerHeight = res.getDimensionPixelSize(R.dimen.bubble_pointer_height);
         mPointerMargin = res.getDimensionPixelSize(R.dimen.bubble_pointer_margin);
+        mPointerOverlap = res.getDimensionPixelSize(R.dimen.bubble_pointer_overlap);
+        mManageButtonHeight = res.getDimensionPixelSize(R.dimen.bubble_manage_button_height);
+        mExpandedViewMinHeight = res.getDimensionPixelSize(R.dimen.bubble_expanded_default_height);
+        mOverflowHeight = res.getDimensionPixelSize(R.dimen.bubble_overflow_height);
 
         mMaxBubbles = calculateMaxBubbles();
 
@@ -296,8 +305,8 @@
         return mPaddings;
     }
 
-    /** Calculates the y position of the expanded view when it is expanded. */
-    public float getExpandedViewY() {
+    /** Gets the y position of the expanded view if it was top-aligned. */
+    private float getExpandedViewYTopAligned() {
         final int top = getAvailableRect().top;
         if (showBubblesVertically()) {
             return top - mPointerWidth;
@@ -306,6 +315,116 @@
         }
     }
 
+    /** The maximum height the expanded view can be. */
+    public int getMaxExpandedViewHeight(boolean isOverflow) {
+        int paddingTop = showBubblesVertically()
+                ? 0
+                : mPointerHeight;
+        int settingsHeight = isOverflow ? 0 : mManageButtonHeight;
+        // Subtract pointer size because it's laid out in LinearLayout with the expanded view.
+        int pointerSize = showBubblesVertically()
+                ? mPointerWidth
+                : (mPointerHeight + mPointerMargin);
+        // Subtract top insets because availableRect.height would account for that
+        int expandedContainerY = (int) getExpandedViewYTopAligned() - getInsets().top;
+        return getAvailableRect().height()
+                - expandedContainerY
+                - paddingTop
+                - settingsHeight
+                - pointerSize;
+    }
+
+    /**
+     * Determines the height for the bubble, ensuring a minimum height. If the height should be as
+     * big as available, returns {@link #MAX_HEIGHT}.
+     */
+    public float getExpandedViewHeight(BubbleViewProvider bubble) {
+        boolean isOverflow = bubble == null || BubbleOverflow.KEY.equals(bubble.getKey());
+        float desiredHeight = isOverflow
+                ? mOverflowHeight
+                : ((Bubble) bubble).getDesiredHeight(mContext);
+        int manageButtonHeight = isOverflow ? 0 : mManageButtonHeight;
+        desiredHeight = Math.max(manageButtonHeight + desiredHeight, mExpandedViewMinHeight);
+        if (desiredHeight > getMaxExpandedViewHeight(isOverflow)) {
+            return MAX_HEIGHT;
+        }
+        return desiredHeight;
+    }
+
+    /**
+     * Gets the y position for the expanded view. This is the position on screen of the top
+     * horizontal line of the expanded view.
+     *
+     * @param bubble the bubble being positioned.
+     * @param bubblePosition the x position of the bubble if showing on top, the y position of the
+     *                       bubble if showing vertically.
+     * @return the y position for the expanded view.
+     */
+    public float getExpandedViewY(BubbleViewProvider bubble, float bubblePosition) {
+        boolean isOverflow = bubble == null || BubbleOverflow.KEY.equals(bubble.getKey());
+        float expandedViewHeight = getExpandedViewHeight(bubble);
+        float topAlignment = getExpandedViewYTopAligned();
+        if (!showBubblesVertically() || expandedViewHeight == MAX_HEIGHT) {
+            // Top-align when bubbles are shown at the top or are max size.
+            return topAlignment;
+        }
+        // If we're here, we're showing vertically & developer has made height less than maximum.
+        int manageButtonHeight = isOverflow ? 0 : mManageButtonHeight;
+        float pointerPosition = getPointerPosition(bubblePosition);
+        float bottomIfCentered = pointerPosition + (expandedViewHeight / 2) + manageButtonHeight;
+        float topIfCentered = pointerPosition - (expandedViewHeight / 2);
+        if (topIfCentered > mPositionRect.top && mPositionRect.bottom > bottomIfCentered) {
+            // Center it
+            return pointerPosition - mPointerWidth - (expandedViewHeight / 2f);
+        } else if (topIfCentered <= mPositionRect.top) {
+            // Top align
+            return topAlignment;
+        } else {
+            // Bottom align
+            return mPositionRect.bottom - manageButtonHeight - expandedViewHeight - mPointerWidth;
+        }
+    }
+
+    /**
+     * The position the pointer points to, the center of the bubble.
+     *
+     * @param bubblePosition the x position of the bubble if showing on top, the y position of the
+     *                       bubble if showing vertically.
+     * @return the position the tip of the pointer points to. The x position if showing on top, the
+     * y position if showing vertically.
+     */
+    public float getPointerPosition(float bubblePosition) {
+        // TODO: I don't understand why it works but it does - why normalized in portrait
+        //  & not in landscape? Am I missing ~2dp in the portrait expandedViewY calculation?
+        final float normalizedSize = IconNormalizer.getNormalizedCircleSize(
+                getBubbleSize());
+        return showBubblesVertically()
+                ? bubblePosition + (getBubbleSize() / 2f)
+                : bubblePosition + (normalizedSize / 2f) - mPointerWidth;
+    }
+
+    /**
+     * When bubbles are expanded in portrait, they display at the top of the screen in a horizontal
+     * row. When in landscape or on a large screen, they show at the left or right side in a
+     * vertical row. This method accounts for screen orientation and will return an x or y value
+     * for the position of the bubble in the row.
+     *
+     * @param index bubble index in the row.
+     * @param numberOfBubbles the number of bubbles (including the overflow) in the row.
+     * @return the y position of the bubble if showing vertically and the x position if showing
+     * horizontally.
+     */
+    public float getBubbleXOrYForOrientation(int index, int numberOfBubbles) {
+        final float positionInBar = index * (mBubbleSize + mSpacingBetweenBubbles);
+        final float expandedStackSize = (numberOfBubbles * mBubbleSize)
+                + ((numberOfBubbles - 1) * mSpacingBetweenBubbles);
+        final float centerPosition = showBubblesVertically()
+                ? mPositionRect.centerY()
+                : mPositionRect.centerX();
+        final float rowStart = centerPosition - (expandedStackSize / 2f);
+        return rowStart + positionInBar;
+    }
+
     /**
      * Sets the stack's most recent position along the edge of the screen. This is saved when the
      * last bubble is removed, so that the stack can be restored in its previous position.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 92e455c..2c6136b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -874,8 +874,10 @@
                         mExpandedAnimationController.expandFromStack(() -> {
                             afterExpandedViewAnimation();
                         } /* after */);
+                        final float translationY = mPositioner.getExpandedViewY(mExpandedBubble,
+                                getBubbleIndex(mExpandedBubble));
                         mExpandedViewContainer.setTranslationX(0f);
-                        mExpandedViewContainer.setTranslationY(mPositioner.getExpandedViewY());
+                        mExpandedViewContainer.setTranslationY(translationY);
                         mExpandedViewContainer.setAlpha(1f);
                     }
                     removeOnLayoutChangeListener(mOrientationChangedListener);
@@ -1524,6 +1526,7 @@
                     bubble.cleanupViews();
                 }
                 updatePointerPosition();
+                updateExpandedView();
                 logBubbleEvent(bubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED);
                 return;
             }
@@ -1815,9 +1818,10 @@
             mTaskbarScrim.setVisibility(VISIBLE);
             mTaskbarScrim.animate().alpha(1f).start();
         }
-
+        final float translationY = mPositioner.getExpandedViewY(mExpandedBubble,
+                getBubbleIndex(mExpandedBubble));
         mExpandedViewContainer.setTranslationX(0f);
-        mExpandedViewContainer.setTranslationY(mPositioner.getExpandedViewY());
+        mExpandedViewContainer.setTranslationY(translationY);
         mExpandedViewContainer.setAlpha(1f);
 
         int index;
@@ -1866,7 +1870,7 @@
                     1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
                     1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
                     bubbleWillBeAt + mBubbleSize / 2f,
-                    mPositioner.getExpandedViewY());
+                    translationY);
         }
         mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
 
@@ -1970,7 +1974,7 @@
             mExpandedViewContainerMatrix.setScale(
                     1f, 1f,
                     expandingFromBubbleAt + mBubbleSize / 2f,
-                    mPositioner.getExpandedViewY());
+                    mPositioner.getExpandedViewY(mExpandedBubble, index));
         }
 
         mExpandedViewAlphaAnimator.reverse();
@@ -2077,7 +2081,7 @@
                     1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
                     1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
                     expandingFromBubbleDestination + mBubbleSize / 2f,
-                    mPositioner.getExpandedViewY());
+                    mPositioner.getExpandedViewY(mExpandedBubble, expandingFromBubbleDestination));
         }
 
         mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
@@ -2698,7 +2702,9 @@
             mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE);
         }
         if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
-            mExpandedViewContainer.setTranslationY(mPositioner.getExpandedViewY());
+            mExpandedViewContainer.setTranslationY(mPositioner.getExpandedViewY(mExpandedBubble,
+                    mExpandedAnimationController.getBubbleXOrYForOrientation(
+                            getBubbleIndex(mExpandedBubble))));
             mExpandedViewContainer.setTranslationX(0f);
             mExpandedBubble.getExpandedView().updateView(
                     mExpandedViewContainer.getLocationOnScreen());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
index df2b440..efe07fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
@@ -83,12 +83,6 @@
     private float mBubblePaddingTop;
     /** Size of each bubble. */
     private float mBubbleSizePx;
-    /** Max number of bubbles shown in row above expanded view. */
-    private int mBubblesMaxRendered;
-    /** Max amount of space to have between bubbles when expanded. */
-    private int mBubblesMaxSpace;
-    /** Amount of space between the bubbles when expanded. */
-    private float mSpaceBetweenBubbles;
     /** Whether the expand / collapse animation is running. */
     private boolean mAnimatingExpand = false;
 
@@ -211,8 +205,6 @@
         mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
         mStackOffsetPx = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
         mBubbleSizePx = mPositioner.getBubbleSize();
-        mBubblesMaxRendered = mPositioner.getMaxBubbles();
-        mSpaceBetweenBubbles = res.getDimensionPixelSize(R.dimen.bubble_spacing);
     }
 
     /**
@@ -628,14 +620,13 @@
         }
     }
 
-    // TODO - could move to method on bubblePositioner if mSpaceBetweenBubbles gets moved
     /**
      * When bubbles are expanded in portrait, they display at the top of the screen in a horizontal
      * row. When in landscape or on a large screen, they show at the left or right side in a
      * vertical row. This method accounts for screen orientation and will return an x or y value
      * for the position of the bubble in the row.
      *
-     * @param index Bubble index in row.
+     * @param index bubble index in the row.
      * @return the y position of the bubble if showing vertically and the x position if showing
      * horizontally.
      */
@@ -643,15 +634,6 @@
         if (mLayout == null) {
             return 0;
         }
-        final float positionInBar = index * (mBubbleSizePx + mSpaceBetweenBubbles);
-        Rect availableRect = mPositioner.getAvailableRect();
-        final boolean isLandscape = mPositioner.showBubblesVertically();
-        final float expandedStackSize = (mLayout.getChildCount() * mBubbleSizePx)
-                + ((mLayout.getChildCount() - 1) * mSpaceBetweenBubbles);
-        final float centerPosition = isLandscape
-                ? availableRect.centerY()
-                : availableRect.centerX();
-        final float rowStart = centerPosition - (expandedStackSize / 2f);
-        return rowStart + positionInBar;
+        return mPositioner.getBubbleXOrYForOrientation(index, mLayout.getChildCount());
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index a7996f05..6f63369 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -279,9 +279,9 @@
                     if (!mImeShowing) {
                         removeImeSurface();
                     }
-                }
-                if (mImeSourceControl != null) {
-                    mImeSourceControl.release(SurfaceControl::release);
+                    if (mImeSourceControl != null) {
+                        mImeSourceControl.release(SurfaceControl::release);
+                    }
                 }
                 mImeSourceControl = imeSourceControl;
             }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 5b158d2..8adfac0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -92,12 +92,16 @@
     private DividerSnapAlgorithm mDividerSnapAlgorithm;
     private int mDividePosition;
     private boolean mInitialized = false;
+    private int mOrientation;
+    private int mRotation;
 
     public SplitLayout(String windowName, Context context, Configuration configuration,
             SplitLayoutHandler splitLayoutHandler,
             SplitWindowManager.ParentContainerCallbacks parentContainerCallbacks,
             DisplayImeController displayImeController, ShellTaskOrganizer taskOrganizer) {
         mContext = context.createConfigurationContext(configuration);
+        mOrientation = configuration.orientation;
+        mRotation = configuration.windowConfiguration.getRotation();
         mSplitLayoutHandler = splitLayoutHandler;
         mDisplayImeController = displayImeController;
         mSplitWindowManager = new SplitWindowManager(
@@ -144,25 +148,36 @@
 
     /** Applies new configuration, returns {@code false} if there's no effect to the layout. */
     public boolean updateConfiguration(Configuration configuration) {
+        boolean affectsLayout = false;
+
+        // Make sure to render the divider bar with proper resources that matching the screen
+        // orientation.
+        final int orientation = configuration.orientation;
+        if (orientation != mOrientation) {
+            mOrientation = orientation;
+            mContext = mContext.createConfigurationContext(configuration);
+            mSplitWindowManager.setConfiguration(configuration);
+            affectsLayout = true;
+        }
+
+        // Update the split bounds when necessary. Besides root bounds changed, split bounds need to
+        // be updated when the rotation changed to cover the case that users rotated the screen 180
+        // degrees.
+        final int rotation = configuration.windowConfiguration.getRotation();
         final Rect rootBounds = configuration.windowConfiguration.getBounds();
-        if (mRootBounds.equals(rootBounds)) {
-            return false;
+        if (rotation != mRotation || !mRootBounds.equals(rootBounds)) {
+            mRootBounds.set(rootBounds);
+            mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds);
+            resetDividerPosition();
+            affectsLayout = true;
         }
 
-        mContext = mContext.createConfigurationContext(configuration);
-        mSplitWindowManager.setConfiguration(configuration);
-        mRootBounds.set(rootBounds);
-        mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds);
-        resetDividerPosition();
-
-        // Don't inflate divider bar if it is not initialized.
-        if (!mInitialized) {
-            return false;
+        if (mInitialized) {
+            release();
+            init();
         }
 
-        release();
-        init();
-        return true;
+        return affectsLayout;
     }
 
     /** Updates recording bounds of divider window and both of the splits. */
@@ -271,7 +286,11 @@
     }
 
     private void flingDividePosition(int from, int to) {
-        if (from == to) return;
+        if (from == to) {
+            // No animation run, it should stop resizing here.
+            mSplitWindowManager.setResizingSplits(false);
+            return;
+        }
         ValueAnimator animator = ValueAnimator
                 .ofInt(from, to)
                 .setDuration(250);
@@ -296,9 +315,8 @@
         return context.getSystemService(WindowManager.class)
                 .getMaximumWindowMetrics()
                 .getWindowInsets()
-                .getInsets(WindowInsets.Type.navigationBars()
-                        | WindowInsets.Type.statusBars()
-                        | WindowInsets.Type.displayCutout()).toRect();
+                .getInsets(WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout())
+                .toRect();
     }
 
     private static boolean isLandscape(Rect bounds) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerView.java
index 362b40f..9bed40d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerView.java
@@ -460,6 +460,7 @@
     private void stopDragging() {
         mHandle.setTouching(false, true /* animate */);
         mWindowManager.setSlippery(true);
+        mWindowManagerProxy.setResizing(false);
         releaseBackground();
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTransitions.java
index d9409ec..b1fa2ac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTransitions.java
@@ -204,7 +204,8 @@
 
     @Override
     public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
-            @NonNull SurfaceControl.Transaction t,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         if (transition != mPendingDismiss && transition != mPendingEnter) {
             // If we're not in split-mode, just abort
@@ -239,12 +240,12 @@
                 if (change.getParent() != null) {
                     // This is probably reparented, so we want the parent to be immediately visible
                     final TransitionInfo.Change parentChange = info.getChange(change.getParent());
-                    t.show(parentChange.getLeash());
-                    t.setAlpha(parentChange.getLeash(), 1.f);
+                    startTransaction.show(parentChange.getLeash());
+                    startTransaction.setAlpha(parentChange.getLeash(), 1.f);
                     // and then animate this layer outside the parent (since, for example, this is
                     // the home task animating from fullscreen to part-screen).
-                    t.reparent(leash, info.getRootLeash());
-                    t.setLayer(leash, info.getChanges().size() - i);
+                    startTransaction.reparent(leash, info.getRootLeash());
+                    startTransaction.setLayer(leash, info.getChanges().size() - i);
                     // build the finish reparent/reposition
                     mFinishTransaction.reparent(leash, parentChange.getLeash());
                     mFinishTransaction.setPosition(leash,
@@ -271,12 +272,12 @@
             if (transition == mPendingEnter
                     && mListener.mPrimary.token.equals(change.getContainer())
                     || mListener.mSecondary.token.equals(change.getContainer())) {
-                t.setWindowCrop(leash, change.getStartAbsBounds().width(),
+                startTransaction.setWindowCrop(leash, change.getStartAbsBounds().width(),
                         change.getStartAbsBounds().height());
                 if (mListener.mPrimary.token.equals(change.getContainer())) {
                     // Move layer to top since we want it above the oversized home task during
                     // animation even though home task is on top in hierarchy.
-                    t.setLayer(leash, info.getChanges().size() + 1);
+                    startTransaction.setLayer(leash, info.getChanges().size() + 1);
                 }
             }
             boolean isOpening = Transitions.isOpeningType(info.getType());
@@ -289,7 +290,7 @@
                     // Dismissing via snap-to-top/bottom means that the dismissed task is already
                     // not-visible (usually cropped to oblivion) so immediately set its alpha to 0
                     // and don't animate it so it doesn't pop-in when reparented.
-                    t.setAlpha(leash, 0.f);
+                    startTransaction.setAlpha(leash, 0.f);
                 } else {
                     startExampleAnimation(leash, false /* show */);
                 }
@@ -311,7 +312,7 @@
             }
             mSplitScreen.finishEnterSplitTransition(homeIsVisible);
         }
-        t.apply();
+        startTransaction.apply();
         onFinish();
         return true;
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index f367cd6..efff3e3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -107,38 +107,6 @@
      */
     private static final int ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS = 1000;
 
-    // Not a complete set of states but serves what we want right now.
-    private enum State {
-        UNDEFINED(0),
-        TASK_APPEARED(1),
-        ENTRY_SCHEDULED(2),
-        ENTERING_PIP(3),
-        ENTERED_PIP(4),
-        EXITING_PIP(5);
-
-        private final int mStateValue;
-
-        State(int value) {
-            mStateValue = value;
-        }
-
-        private boolean isInPip() {
-            return mStateValue >= TASK_APPEARED.mStateValue
-                    && mStateValue != EXITING_PIP.mStateValue;
-        }
-
-        /**
-         * Resize request can be initiated in other component, ignore if we are no longer in PIP,
-         * still waiting for animation or we're exiting from it.
-         *
-         * @return {@code true} if the resize request should be blocked/ignored.
-         */
-        private boolean shouldBlockResizeRequest() {
-            return mStateValue < ENTERING_PIP.mStateValue
-                    || mStateValue == EXITING_PIP.mStateValue;
-        }
-    }
-
     private final Context mContext;
     private final SyncTransactionQueue mSyncTransactionQueue;
     private final PipBoundsState mPipBoundsState;
@@ -190,7 +158,8 @@
             }
             final boolean isExitPipDirection = isOutPipDirection(direction)
                     || isRemovePipDirection(direction);
-            if (mState != State.EXITING_PIP || isExitPipDirection) {
+            if (mPipTransitionState.getTransitionState() != PipTransitionState.EXITING_PIP
+                    || isExitPipDirection) {
                 // Finish resize as long as we're not exiting PIP, or, if we are, only if this is
                 // the end of an exit PIP animation.
                 // This is necessary in case there was a resize animation ongoing when exit PIP
@@ -233,7 +202,7 @@
     private ActivityManager.RunningTaskInfo mDeferredTaskInfo;
     private WindowContainerToken mToken;
     private SurfaceControl mLeash;
-    private State mState = State.UNDEFINED;
+    private PipTransitionState mPipTransitionState;
     private @PipAnimationController.AnimationType int mOneShotAnimationType = ANIM_TYPE_BOUNDS;
     private long mLastOneShotAlphaAnimationTime;
     private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
@@ -278,6 +247,7 @@
 
     public PipTaskOrganizer(Context context,
             @NonNull SyncTransactionQueue syncTransactionQueue,
+            @NonNull PipTransitionState pipTransitionState,
             @NonNull PipBoundsState pipBoundsState,
             @NonNull PipBoundsAlgorithm boundsHandler,
             @NonNull PipMenuController pipMenuController,
@@ -291,6 +261,7 @@
             @ShellMainThread ShellExecutor mainExecutor) {
         mContext = context;
         mSyncTransactionQueue = syncTransactionQueue;
+        mPipTransitionState = pipTransitionState;
         mPipBoundsState = pipBoundsState;
         mPipBoundsAlgorithm = boundsHandler;
         mPipMenuController = pipMenuController;
@@ -326,14 +297,14 @@
     }
 
     public boolean isInPip() {
-        return mState.isInPip();
+        return mPipTransitionState.isInPip();
     }
 
     /**
      * Returns whether the entry animation is waiting to be started.
      */
     public boolean isEntryScheduled() {
-        return mState == State.ENTRY_SCHEDULED;
+        return mPipTransitionState.getTransitionState() == PipTransitionState.ENTRY_SCHEDULED;
     }
 
     /**
@@ -401,9 +372,11 @@
      * @param animationDurationMs duration in millisecond for the exiting PiP transition
      */
     public void exitPip(int animationDurationMs) {
-        if (!mState.isInPip() || mState == State.EXITING_PIP || mToken == null) {
+        if (!mPipTransitionState.isInPip()
+                || mPipTransitionState.getTransitionState() == PipTransitionState.EXITING_PIP
+                || mToken == null) {
             Log.wtf(TAG, "Not allowed to exitPip in current state"
-                    + " mState=" + mState + " mToken=" + mToken);
+                    + " mState=" + mPipTransitionState.getTransitionState() + " mToken=" + mToken);
             return;
         }
 
@@ -427,7 +400,12 @@
         wct.setBoundsChangeTransaction(mToken, tx);
         // Set the exiting state first so if there is fixed rotation later, the running animation
         // won't be interrupted by alpha animation for existing PiP.
-        mState = State.EXITING_PIP;
+        mPipTransitionState.setTransitionState(PipTransitionState.EXITING_PIP);
+
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+            mPipTransitionController.startTransition(destinationBounds, wct);
+            return;
+        }
         mSyncTransactionQueue.queue(wct);
         mSyncTransactionQueue.runInSync(t -> {
             // Make sure to grab the latest source hint rect as it could have been
@@ -465,9 +443,9 @@
      * Removes PiP immediately.
      */
     public void removePip() {
-        if (!mState.isInPip() ||  mToken == null) {
+        if (!mPipTransitionState.isInPip() ||  mToken == null) {
             Log.wtf(TAG, "Not allowed to removePip in current state"
-                    + " mState=" + mState + " mToken=" + mToken);
+                    + " mState=" + mPipTransitionState.getTransitionState() + " mToken=" + mToken);
             return;
         }
 
@@ -481,10 +459,19 @@
         animator.setDuration(mExitAnimationDuration);
         animator.setInterpolator(Interpolators.ALPHA_OUT);
         animator.start();
-        mState = State.EXITING_PIP;
+        mPipTransitionState.setTransitionState(PipTransitionState.EXITING_PIP);
     }
 
     private void removePipImmediately() {
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+            final WindowContainerTransaction wct = new WindowContainerTransaction();
+            wct.setBounds(mToken, null);
+            wct.setWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
+            wct.reorder(mToken, false);
+            mPipTransitionController.startTransition(null, wct);
+            return;
+        }
+
         try {
             // Reset the task bounds first to ensure the activity configuration is reset as well
             final WindowContainerTransaction wct = new WindowContainerTransaction();
@@ -503,7 +490,7 @@
         Objects.requireNonNull(info, "Requires RunningTaskInfo");
         mTaskInfo = info;
         mToken = mTaskInfo.token;
-        mState = State.TASK_APPEARED;
+        mPipTransitionState.setTransitionState(PipTransitionState.TASK_APPEARED);
         mLeash = leash;
         mPictureInPictureParams = mTaskInfo.pictureInPictureParams;
         setBoundsStateForEntry(mTaskInfo.topActivity, mPictureInPictureParams,
@@ -546,6 +533,8 @@
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) {
                 mPipMenuController.attach(mLeash);
+            } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+                mOneShotAnimationType = ANIM_TYPE_BOUNDS;
             }
             return;
         }
@@ -557,7 +546,7 @@
             scheduleAnimateResizePip(currentBounds, destinationBounds, 0 /* startingAngle */,
                     sourceHintRect, TRANSITION_DIRECTION_TO_PIP, mEnterAnimationDuration,
                     null /* updateBoundsCallback */);
-            mState = State.ENTERING_PIP;
+            mPipTransitionState.setTransitionState(PipTransitionState.ENTERING_PIP);
         } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
             enterPipWithAlphaAnimation(destinationBounds, mEnterAnimationDuration);
             mOneShotAnimationType = ANIM_TYPE_BOUNDS;
@@ -584,7 +573,7 @@
         final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
         animateResizePip(currentBounds, destinationBounds, sourceHintRect,
                 TRANSITION_DIRECTION_TO_PIP, mEnterAnimationDuration, 0 /* startingAngle */);
-        mState = State.ENTERING_PIP;
+        mPipTransitionState.setTransitionState(PipTransitionState.ENTERING_PIP);
     }
 
     /**
@@ -609,7 +598,7 @@
                 mSurfaceControlTransactionFactory.getTransaction();
         tx.setAlpha(mLeash, 0f);
         tx.apply();
-        mState = State.ENTRY_SCHEDULED;
+        mPipTransitionState.setTransitionState(PipTransitionState.ENTRY_SCHEDULED);
         applyEnterPipSyncTransaction(destinationBounds, () -> {
             mPipAnimationController
                     .getAnimator(mTaskInfo, mLeash, destinationBounds, 0f, 1f)
@@ -620,7 +609,7 @@
                     .start();
             // mState is set right after the animation is kicked off to block any resize
             // requests such as offsetPip that may have been called prior to the transition.
-            mState = State.ENTERING_PIP;
+            mPipTransitionState.setTransitionState(PipTransitionState.ENTERING_PIP);
         }, null /* boundsChangeTransaction */);
     }
 
@@ -667,7 +656,7 @@
     private void sendOnPipTransitionStarted(
             @PipAnimationController.TransitionDirection int direction) {
         if (direction == TRANSITION_DIRECTION_TO_PIP) {
-            mState = State.ENTERING_PIP;
+            mPipTransitionState.setTransitionState(PipTransitionState.ENTERING_PIP);
         }
         mPipTransitionController.sendOnPipTransitionStarted(direction);
     }
@@ -676,7 +665,7 @@
     void sendOnPipTransitionFinished(
             @PipAnimationController.TransitionDirection int direction) {
         if (direction == TRANSITION_DIRECTION_TO_PIP) {
-            mState = State.ENTERED_PIP;
+            mPipTransitionState.setTransitionState(PipTransitionState.ENTERED_PIP);
         }
         mPipTransitionController.sendOnPipTransitionFinished(direction);
         // Apply the deferred RunningTaskInfo if applicable after all proper callbacks are sent.
@@ -701,7 +690,7 @@
      */
     @Override
     public void onTaskVanished(ActivityManager.RunningTaskInfo info) {
-        if (mState == State.UNDEFINED) {
+        if (mPipTransitionState.getTransitionState() == PipTransitionState.UNDEFINED) {
             return;
         }
         final WindowContainerToken token = info.token;
@@ -713,7 +702,7 @@
         clearWaitForFixedRotation();
         mInSwipePipToHomeTransition = false;
         mPictureInPictureParams = null;
-        mState = State.UNDEFINED;
+        mPipTransitionState.setTransitionState(PipTransitionState.UNDEFINED);
         // Re-set the PIP bounds to none.
         mPipBoundsState.setBounds(new Rect());
         mPipUiEventLoggerLogger.setTaskInfo(null);
@@ -735,8 +724,10 @@
     @Override
     public void onTaskInfoChanged(ActivityManager.RunningTaskInfo info) {
         Objects.requireNonNull(mToken, "onTaskInfoChanged requires valid existing mToken");
-        if (mState != State.ENTERED_PIP && mState != State.EXITING_PIP) {
-            Log.d(TAG, "Defer onTaskInfoChange in current state: " + mState);
+        if (mPipTransitionState.getTransitionState() != PipTransitionState.ENTERED_PIP
+                && mPipTransitionState.getTransitionState() != PipTransitionState.EXITING_PIP) {
+            Log.d(TAG, "Defer onTaskInfoChange in current state: "
+                    + mPipTransitionState.getTransitionState());
             // Defer applying PiP parameters if the task is entering PiP to avoid disturbing
             // the animation.
             mDeferredTaskInfo = info;
@@ -769,7 +760,7 @@
         mNextRotation = newRotation;
         mWaitForFixedRotation = true;
 
-        if (mState.isInPip()) {
+        if (mPipTransitionState.isInPip()) {
             // Fade out the existing PiP to avoid jump cut during seamless rotation.
             fadeExistingPip(false /* show */);
         }
@@ -780,7 +771,7 @@
         if (!mWaitForFixedRotation) {
             return;
         }
-        if (mState == State.TASK_APPEARED) {
+        if (mPipTransitionState.getTransitionState() == PipTransitionState.TASK_APPEARED) {
             if (mInSwipePipToHomeTransition) {
                 onEndOfSwipePipToHomeTransition();
             } else {
@@ -788,9 +779,11 @@
                 enterPipWithAlphaAnimation(mPipBoundsAlgorithm.getEntryDestinationBounds(),
                         mEnterAnimationDuration);
             }
-        } else if (mState == State.ENTERED_PIP && mHasFadeOut) {
+        } else if (mPipTransitionState.getTransitionState() == PipTransitionState.ENTERED_PIP
+                && mHasFadeOut) {
             fadeExistingPip(true /* show */);
-        } else if (mState == State.ENTERING_PIP && mDeferredAnimEndTransaction != null) {
+        } else if (mPipTransitionState.getTransitionState() == PipTransitionState.ENTERING_PIP
+                && mDeferredAnimEndTransaction != null) {
             final PipAnimationController.PipTransitionAnimator<?> animator =
                     mPipAnimationController.getCurrentAnimator();
             final Rect destinationBounds = animator.getDestinationBounds();
@@ -844,13 +837,13 @@
         // note that this can be called when swipe-to-home or fixed-rotation is happening.
         // Skip this entirely if that's the case.
         final boolean waitForFixedRotationOnEnteringPip = mWaitForFixedRotation
-                && (mState != State.ENTERED_PIP);
+                && (mPipTransitionState.getTransitionState() != PipTransitionState.ENTERED_PIP);
         if ((mInSwipePipToHomeTransition || waitForFixedRotationOnEnteringPip) && fromRotation) {
             if (DEBUG) {
                 Log.d(TAG, "Skip onMovementBoundsChanged on rotation change"
                         + " mInSwipePipToHomeTransition=" + mInSwipePipToHomeTransition
                         + " mWaitForFixedRotation=" + mWaitForFixedRotation
-                        + " mState=" + mState);
+                        + " getTransitionState=" + mPipTransitionState.getTransitionState());
             }
             return;
         }
@@ -858,7 +851,7 @@
                 mPipAnimationController.getCurrentAnimator();
         if (animator == null || !animator.isRunning()
                 || animator.getTransitionDirection() != TRANSITION_DIRECTION_TO_PIP) {
-            final boolean rotatingPip = mState.isInPip() && fromRotation;
+            final boolean rotatingPip = mPipTransitionState.isInPip() && fromRotation;
             if (rotatingPip && mWaitForFixedRotation && mHasFadeOut) {
                 // The position will be used by fade-in animation when the fixed rotation is done.
                 mPipBoundsState.setBounds(destinationBoundsOut);
@@ -991,7 +984,7 @@
             Rect currentBounds, Rect destinationBounds, float startingAngle, Rect sourceHintRect,
             @PipAnimationController.TransitionDirection int direction, int durationMs,
             Consumer<Rect> updateBoundsCallback) {
-        if (!mState.isInPip()) {
+        if (!mPipTransitionState.isInPip()) {
             // TODO: tend to use shouldBlockResizeRequest here as well but need to consider
             // the fact that when in exitPip, scheduleAnimateResizePip is executed in the window
             // container transaction callback and we want to set the mState immediately.
@@ -1021,7 +1014,7 @@
         final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
         mSurfaceTransactionHelper
                 .crop(tx, mLeash, toBounds)
-                .round(tx, mLeash, mState.isInPip());
+                .round(tx, mLeash, mPipTransitionState.isInPip());
         if (mPipMenuController.isMenuVisible()) {
             mPipMenuController.resizePipMenu(mLeash, tx, toBounds);
         } else {
@@ -1099,7 +1092,7 @@
     public void scheduleFinishResizePip(Rect destinationBounds,
             @PipAnimationController.TransitionDirection int direction,
             Consumer<Rect> updateBoundsCallback) {
-        if (mState.shouldBlockResizeRequest()) {
+        if (mPipTransitionState.shouldBlockResizeRequest()) {
             return;
         }
 
@@ -1116,7 +1109,7 @@
         mSurfaceTransactionHelper
                 .crop(tx, mLeash, destinationBounds)
                 .resetScale(tx, mLeash, destinationBounds)
-                .round(tx, mLeash, mState.isInPip());
+                .round(tx, mLeash, mPipTransitionState.isInPip());
         return tx;
     }
 
@@ -1125,7 +1118,7 @@
      */
     public void scheduleOffsetPip(Rect originalBounds, int offset, int duration,
             Consumer<Rect> updateBoundsCallback) {
-        if (mState.shouldBlockResizeRequest()) {
+        if (mPipTransitionState.shouldBlockResizeRequest()) {
             return;
         }
         if (mWaitForFixedRotation) {
@@ -1383,7 +1376,7 @@
         pw.println(innerPrefix + "mToken=" + mToken
                 + " binder=" + (mToken != null ? mToken.asBinder() : null));
         pw.println(innerPrefix + "mLeash=" + mLeash);
-        pw.println(innerPrefix + "mState=" + mState);
+        pw.println(innerPrefix + "mState=" + mPipTransitionState.getTransitionState());
         pw.println(innerPrefix + "mOneShotAnimationType=" + mOneShotAnimationType);
         pw.println(innerPrefix + "mPictureInPictureParams=" + mPictureInPictureParams);
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 4759550..b75cde0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -18,6 +18,8 @@
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.view.WindowManager.TRANSIT_PIP;
+import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
 
 import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
 import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_BOUNDS;
@@ -25,6 +27,8 @@
 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
 import static com.android.wm.shell.pip.PipAnimationController.isInPipDirection;
 import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP;
 
 import android.app.TaskInfo;
 import android.content.Context;
@@ -35,6 +39,7 @@
 import android.window.TransitionInfo;
 import android.window.TransitionRequestInfo;
 import android.window.WindowContainerTransaction;
+import android.window.WindowContainerTransactionCallback;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -49,64 +54,166 @@
  */
 public class PipTransition extends PipTransitionController {
 
+    private final PipTransitionState mPipTransitionState;
     private final int mEnterExitAnimationDuration;
     private @PipAnimationController.AnimationType int mOneShotAnimationType = ANIM_TYPE_BOUNDS;
     private Transitions.TransitionFinishCallback mFinishCallback;
+    private Rect mExitDestinationBounds = new Rect();
 
     public PipTransition(Context context,
-            PipBoundsState pipBoundsState, PipMenuController pipMenuController,
+            PipBoundsState pipBoundsState,
+            PipTransitionState pipTransitionState,
+            PipMenuController pipMenuController,
             PipBoundsAlgorithm pipBoundsAlgorithm,
             PipAnimationController pipAnimationController,
             Transitions transitions,
             @NonNull ShellTaskOrganizer shellTaskOrganizer) {
         super(pipBoundsState, pipMenuController, pipBoundsAlgorithm,
                 pipAnimationController, transitions, shellTaskOrganizer);
+        mPipTransitionState = pipTransitionState;
         mEnterExitAnimationDuration = context.getResources()
                 .getInteger(R.integer.config_pipResizeAnimationDuration);
     }
 
     @Override
+    public void setIsFullAnimation(boolean isFullAnimation) {
+        setOneShotAnimationType(isFullAnimation ? ANIM_TYPE_BOUNDS : ANIM_TYPE_ALPHA);
+    }
+
+    /**
+     * Sets the preferred animation type for one time.
+     * This is typically used to set the animation type to
+     * {@link PipAnimationController#ANIM_TYPE_ALPHA}.
+     */
+    private void setOneShotAnimationType(@PipAnimationController.AnimationType int animationType) {
+        mOneShotAnimationType = animationType;
+    }
+
+    @Override
+    public void startTransition(Rect destinationBounds, WindowContainerTransaction out) {
+        if (destinationBounds != null) {
+            mExitDestinationBounds.set(destinationBounds);
+            mTransitions.startTransition(TRANSIT_EXIT_PIP, out, this);
+        } else {
+            mTransitions.startTransition(TRANSIT_REMOVE_PIP, out, this);
+        }
+    }
+
+    @Override
     public boolean startAnimation(@android.annotation.NonNull IBinder transition,
             @android.annotation.NonNull TransitionInfo info,
-            @android.annotation.NonNull SurfaceControl.Transaction t,
+            @android.annotation.NonNull SurfaceControl.Transaction startTransaction,
+            @android.annotation.NonNull SurfaceControl.Transaction finishTransaction,
             @android.annotation.NonNull Transitions.TransitionFinishCallback finishCallback) {
+
+        if (info.getType() == TRANSIT_EXIT_PIP && info.getChanges().size() == 1) {
+            final TransitionInfo.Change change = info.getChanges().get(0);
+            mFinishCallback = finishCallback;
+            startTransaction.apply();
+            boolean success = startExpandAnimation(change.getTaskInfo(), change.getLeash(),
+                    new Rect(mExitDestinationBounds));
+            mExitDestinationBounds.setEmpty();
+            return success;
+        }
+
+        if (info.getType() == TRANSIT_REMOVE_PIP) {
+            startTransaction.apply();
+            finishTransaction.setWindowCrop(info.getChanges().get(0).getLeash(),
+                    mPipBoundsState.getDisplayBounds());
+            finishCallback.onTransitionFinished(null, null);
+            return true;
+        }
+
+        // Search for an Enter PiP transition (along with a show wallpaper one)
+        TransitionInfo.Change enterPip = null;
+        TransitionInfo.Change wallpaper = null;
         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
             final TransitionInfo.Change change = info.getChanges().get(i);
             if (change.getTaskInfo() != null
                     && change.getTaskInfo().configuration.windowConfiguration.getWindowingMode()
                     == WINDOWING_MODE_PINNED) {
-                mFinishCallback = finishCallback;
-                return startEnterAnimation(change.getTaskInfo(), change.getLeash(), t);
+                enterPip = change;
+            } else if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
+                wallpaper = change;
             }
         }
-        return false;
+        if (enterPip == null) {
+            return false;
+        }
+
+        // Show the wallpaper if there is a wallpaper change.
+        if (wallpaper != null) {
+            startTransaction.show(wallpaper.getLeash());
+            startTransaction.setAlpha(wallpaper.getLeash(), 1.f);
+        }
+
+        mPipTransitionState.setTransitionState(PipTransitionState.ENTERING_PIP);
+        mFinishCallback = finishCallback;
+        return startEnterAnimation(enterPip.getTaskInfo(), enterPip.getLeash(),
+                startTransaction, finishTransaction);
     }
 
     @Nullable
     @Override
     public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
             @NonNull TransitionRequestInfo request) {
-        return null;
+        if (request.getType() == TRANSIT_PIP) {
+            WindowContainerTransaction wct = new WindowContainerTransaction();
+            mPipTransitionState.setTransitionState(PipTransitionState.ENTRY_SCHEDULED);
+            final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
+            wct.setActivityWindowingMode(request.getTriggerTask().token, WINDOWING_MODE_UNDEFINED);
+            wct.setBounds(request.getTriggerTask().token, destinationBounds);
+            return wct;
+        } else {
+            return null;
+        }
     }
 
     @Override
     public void onFinishResize(TaskInfo taskInfo, Rect destinationBounds,
             @PipAnimationController.TransitionDirection int direction,
             SurfaceControl.Transaction tx) {
+
+        if (isInPipDirection(direction)) {
+            mPipTransitionState.setTransitionState(PipTransitionState.ENTERED_PIP);
+        }
         WindowContainerTransaction wct = new WindowContainerTransaction();
         prepareFinishResizeTransaction(taskInfo, destinationBounds,
                 direction, tx, wct);
-        mFinishCallback.onTransitionFinished(wct, null);
+        mFinishCallback.onTransitionFinished(wct, new WindowContainerTransactionCallback() {
+            @Override
+            public void onTransactionReady(int id, @NonNull SurfaceControl.Transaction t) {
+                t.merge(tx);
+                t.apply();
+            }
+        });
         finishResizeForMenu(destinationBounds);
     }
 
+    private boolean startExpandAnimation(final TaskInfo taskInfo, final SurfaceControl leash,
+            final Rect destinationBounds) {
+        PipAnimationController.PipTransitionAnimator animator =
+                mPipAnimationController.getAnimator(taskInfo, leash, mPipBoundsState.getBounds(),
+                        mPipBoundsState.getBounds(), destinationBounds, null,
+                        TRANSITION_DIRECTION_LEAVE_PIP, 0 /* startingAngle */, Surface.ROTATION_0);
+
+        animator.setTransitionDirection(TRANSITION_DIRECTION_LEAVE_PIP)
+                .setPipAnimationCallback(mPipAnimationCallback)
+                .setDuration(mEnterExitAnimationDuration)
+                .start();
+
+        return true;
+    }
+
     private boolean startEnterAnimation(final TaskInfo taskInfo, final SurfaceControl leash,
-            final SurfaceControl.Transaction t) {
+            final SurfaceControl.Transaction startTransaction,
+            final SurfaceControl.Transaction finishTransaction) {
         setBoundsStateForEntry(taskInfo.topActivity, taskInfo.pictureInPictureParams,
                 taskInfo.topActivityInfo);
         final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
         final Rect currentBounds = taskInfo.configuration.windowConfiguration.getBounds();
         PipAnimationController.PipTransitionAnimator animator;
+        finishTransaction.setPosition(leash, destinationBounds.left, destinationBounds.top);
         if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) {
             final Rect sourceHintRect =
                     PipBoundsAlgorithm.getValidSourceHintRect(
@@ -115,8 +222,10 @@
                     currentBounds, destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP,
                     0 /* startingAngle */, Surface.ROTATION_0);
         } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
-            t.setAlpha(leash, 0f);
-            t.apply();
+            startTransaction.setAlpha(leash, 0f);
+            // PiP menu is attached late in the process here to avoid any artifacts on the leash
+            // caused by addShellRoot when in gesture navigation mode.
+            mPipMenuController.attach(leash);
             animator = mPipAnimationController.getAnimator(taskInfo, leash, destinationBounds,
                     0f, 1f);
             mOneShotAnimationType = ANIM_TYPE_BOUNDS;
@@ -124,6 +233,7 @@
             throw new RuntimeException("Unrecognized animation type: "
                     + mOneShotAnimationType);
         }
+        startTransaction.apply();
         animator.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP)
                 .setPipAnimationCallback(mPipAnimationCallback)
                 .setDuration(mEnterExitAnimationDuration)
@@ -158,6 +268,5 @@
         }
 
         wct.setBounds(taskInfo.token, taskBounds);
-        wct.setBoundsChangeTransaction(taskInfo.token, tx);
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index d801c91..dedc566 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -29,6 +29,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.view.SurfaceControl;
+import android.window.WindowContainerTransaction;
 
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.transition.Transitions;
@@ -46,6 +47,7 @@
     protected final PipBoundsState mPipBoundsState;
     protected final ShellTaskOrganizer mShellTaskOrganizer;
     protected final PipMenuController mPipMenuController;
+    protected final Transitions mTransitions;
     private final Handler mMainHandler;
     private final List<PipTransitionCallback> mPipTransitionCallbacks = new ArrayList<>();
 
@@ -98,6 +100,22 @@
             SurfaceControl.Transaction tx) {
     }
 
+    /**
+     * Called to inform the transition that the animation should start with the assumption that
+     * PiP is not animating from its original bounds, but rather a continuation of another
+     * animation. For example, gesture navigation would first fade out the PiP activity, and the
+     * transition should be responsible to animate in (such as fade in) the PiP.
+     */
+    public void setIsFullAnimation(boolean isFullAnimation) {
+    }
+
+    /**
+     * Called when the Shell wants to starts a transition/animation.
+     */
+    public void startTransition(Rect destinationBounds, WindowContainerTransaction out) {
+        // Default implementation does nothing.
+    }
+
     public PipTransitionController(PipBoundsState pipBoundsState,
             PipMenuController pipMenuController, PipBoundsAlgorithm pipBoundsAlgorithm,
             PipAnimationController pipAnimationController, Transitions transitions,
@@ -107,6 +125,7 @@
         mShellTaskOrganizer = shellTaskOrganizer;
         mPipBoundsAlgorithm = pipBoundsAlgorithm;
         mPipAnimationController = pipAnimationController;
+        mTransitions = transitions;
         mMainHandler = new Handler(Looper.getMainLooper());
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             transitions.addHandler(this);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java
new file mode 100644
index 0000000..d23aada
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Used to keep track of PiP leash state as it appears and animates by {@link PipTaskOrganizer} and
+ * {@link PipTransition}.
+ */
+public class PipTransitionState {
+
+    public static final int UNDEFINED = 0;
+    public static final int TASK_APPEARED = 1;
+    public static final int ENTRY_SCHEDULED = 2;
+    public static final int ENTERING_PIP = 3;
+    public static final int ENTERED_PIP = 4;
+    public static final int EXITING_PIP = 5;
+
+    // Not a complete set of states but serves what we want right now.
+    @IntDef(prefix = { "TRANSITION_STATE_" }, value =  {
+            UNDEFINED,
+            TASK_APPEARED,
+            ENTRY_SCHEDULED,
+            ENTERING_PIP,
+            ENTERED_PIP,
+            EXITING_PIP
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface TransitionState {}
+
+    private @TransitionState int mState;
+
+    public PipTransitionState() {
+        mState = UNDEFINED;
+    }
+
+    public void setTransitionState(@TransitionState int state) {
+        mState = state;
+    }
+
+    public @TransitionState int getTransitionState() {
+        return mState;
+    }
+
+    public boolean isInPip() {
+        return mState >= TASK_APPEARED
+                && mState != EXITING_PIP;
+    }
+
+    /**
+     * Resize request can be initiated in other component, ignore if we are no longer in PIP,
+     * still waiting for animation or we're exiting from it.
+     *
+     * @return {@code true} if the resize request should be blocked/ignored.
+     */
+    public boolean shouldBlockResizeRequest() {
+        return mState < ENTERING_PIP
+                || mState == EXITING_PIP;
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 4f3ec96..62b50c5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -67,6 +67,7 @@
 import com.android.wm.shell.pip.IPipAnimationListener;
 import com.android.wm.shell.pip.PinnedStackListenerForwarder;
 import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.pip.PipAnimationController;
 import com.android.wm.shell.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipMediaController;
@@ -528,6 +529,8 @@
 
     private void setPinnedStackAnimationType(int animationType) {
         mPipTaskOrganizer.setOneShotAnimationType(animationType);
+        mPipTransitionController.setIsFullAnimation(
+                animationType == PipAnimationController.ANIM_TYPE_BOUNDS);
     }
 
     private void setPinnedStackAnimationListener(IPipAnimationListener callback) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
index b7caf72..551476d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
@@ -58,7 +58,8 @@
 
     @Override
     public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
-            @NonNull SurfaceControl.Transaction t,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @android.annotation.NonNull SurfaceControl.Transaction finishTransaction,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         return false;
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
index 8f0892f..df1f5e6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
@@ -20,6 +20,8 @@
 import android.content.Intent;
 import android.os.Bundle;
 import android.os.UserHandle;
+import android.view.RemoteAnimationAdapter;
+import android.view.RemoteAnimationTarget;
 import android.window.IRemoteTransition;
 
 import com.android.wm.shell.splitscreen.ISplitScreenListener;
@@ -77,9 +79,22 @@
             int position, in Bundle options) = 9;
 
     /**
-     * Starts tasks simultaneously in one transition. The first task in the list will be in the
-     * main-stage and on the left/top.
+     * Starts tasks simultaneously in one transition.
      */
     oneway void startTasks(int mainTaskId, in Bundle mainOptions, int sideTaskId,
             in Bundle sideOptions, int sidePosition, in IRemoteTransition remoteTransition) = 10;
+
+    /**
+     * Version of startTasks using legacy transition system.
+     */
+     oneway void startTasksWithLegacyTransition(int mainTaskId, in Bundle mainOptions,
+                            int sideTaskId, in Bundle sideOptions, int sidePosition,
+                            in RemoteAnimationAdapter adapter) = 11;
+
+    /**
+     * Blocking call that notifies and gets additional split-screen targets when entering
+     * recents (for example: the dividerBar).
+     * @param cancel is true if leaving recents back to split (eg. the gesture was cancelled).
+     */
+    RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel) = 12;
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/OutlineManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/OutlineManager.java
new file mode 100644
index 0000000..5d5a6e5
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/OutlineManager.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.splitscreen;
+
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.view.IWindow;
+import android.view.LayoutInflater;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
+import android.view.WindowlessWindowManager;
+
+import com.android.wm.shell.R;
+
+import java.util.function.Supplier;
+
+/**
+ * Handles drawing outline of the bounds of provided root surface. The outline will be drown with
+ * the consideration of display insets like status bar, navigation bar and display cutout.
+ */
+class OutlineManager extends WindowlessWindowManager {
+    private static final String WINDOW_NAME = "SplitOutlineLayer";
+    private final Context mContext;
+    private final int mOutlineColor;
+    private final Rect mOutlineBounds = new Rect();
+    private final Rect mTmpBounds = new Rect();
+    private final Supplier<SurfaceControl> mOutlineSurfaceSupplier;
+    private SurfaceControlViewHost mViewHost;
+
+    /**
+     * Constructs {@link #OutlineManager} with indicated outline color for the provided root
+     * surface.
+     */
+    OutlineManager(Context context, Configuration configuration,
+            Supplier<SurfaceControl> outlineSurfaceSupplier, int color) {
+        super(configuration, null /* rootSurface */, null /* hostInputToken */);
+        mContext = context.createDisplayContext(context.getDisplay());
+        mOutlineSurfaceSupplier = outlineSurfaceSupplier;
+        mOutlineColor = color;
+    }
+
+    @Override
+    protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) {
+        b.setParent(mOutlineSurfaceSupplier.get());
+    }
+
+    boolean updateOutlineBounds(Rect rootBounds) {
+        computeOutlineBounds(mContext, rootBounds, mTmpBounds);
+        if (mOutlineBounds.equals(mTmpBounds)) {
+            return false;
+        }
+        mOutlineBounds.set(mTmpBounds);
+
+        if (mViewHost == null) {
+            mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this);
+        }
+        if (mViewHost.getView() == null) {
+            final OutlineRoot rootView = (OutlineRoot) LayoutInflater.from(mContext)
+                    .inflate(R.layout.split_outline, null);
+            rootView.updateOutlineBounds(mOutlineBounds, mOutlineColor);
+
+            final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+                    rootBounds.width(), rootBounds.height(),
+                    TYPE_APPLICATION_OVERLAY,
+                    FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCHABLE,
+                    PixelFormat.TRANSLUCENT);
+            lp.token = new Binder();
+            lp.setTitle(WINDOW_NAME);
+            lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
+            // TODO(b/189839391): Set INPUT_FEATURE_NO_INPUT_CHANNEL after WM supports
+            //  TRUSTED_OVERLAY for windowless window without input channel.
+            mViewHost.setView(rootView, lp);
+        } else {
+            ((OutlineRoot) mViewHost.getView()).updateOutlineBounds(mOutlineBounds, mOutlineColor);
+            final WindowManager.LayoutParams lp =
+                    (WindowManager.LayoutParams) mViewHost.getView().getLayoutParams();
+            lp.width = rootBounds.width();
+            lp.height = rootBounds.height();
+            mViewHost.relayout(lp);
+        }
+
+        return true;
+    }
+
+    private static void computeOutlineBounds(Context context, Rect rootBounds, Rect outBounds) {
+        computeDisplayStableBounds(context, outBounds);
+        outBounds.intersect(rootBounds);
+        // Offset the coordinate from screen based to surface based.
+        outBounds.offset(-rootBounds.left, -rootBounds.top);
+    }
+
+    private static void computeDisplayStableBounds(Context context, Rect outBounds) {
+        final WindowMetrics windowMetrics =
+                context.getSystemService(WindowManager.class).getMaximumWindowMetrics();
+        outBounds.set(windowMetrics.getBounds());
+        outBounds.inset(windowMetrics.getWindowInsets().getInsets(
+                WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout()));
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/OutlineRoot.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/OutlineRoot.java
new file mode 100644
index 0000000..71d48ee
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/OutlineRoot.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.splitscreen;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.wm.shell.R;
+
+/** Root layout for holding split outline. */
+public class OutlineRoot extends FrameLayout {
+    public OutlineRoot(@NonNull Context context) {
+        super(context);
+    }
+
+    public OutlineRoot(@NonNull Context context,
+            @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public OutlineRoot(@NonNull Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public OutlineRoot(@NonNull Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    private OutlineView mOutlineView;
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mOutlineView = findViewById(R.id.split_outline);
+    }
+
+    void updateOutlineBounds(Rect bounds, int color) {
+        mOutlineView.updateOutlineBounds(bounds, color);
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/OutlineView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/OutlineView.java
new file mode 100644
index 0000000..ea66180
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/OutlineView.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.splitscreen;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.util.AttributeSet;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.R;
+
+/** View for drawing split outline. */
+public class OutlineView extends View {
+    private final Paint mPaint = new Paint();
+    private final Rect mBounds = new Rect();
+
+    public OutlineView(@NonNull Context context) {
+        super(context);
+    }
+
+    public OutlineView(@NonNull Context context,
+            @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public OutlineView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public OutlineView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mPaint.setStyle(Paint.Style.STROKE);
+        mPaint.setStrokeWidth(getResources()
+                .getDimension(R.dimen.accessibility_focus_highlight_stroke_width));
+    }
+
+    void updateOutlineBounds(Rect bounds, int color) {
+        if (mBounds.equals(bounds) && mPaint.getColor() == color) return;
+        mBounds.set(bounds);
+        mPaint.setColor(color);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        if (mBounds.isEmpty()) return;
+        final Path path = new Region(mBounds).getBoundaryPath();
+        canvas.drawPath(path, mPaint);
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java
index 82f95a4..a0bdcc3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java
@@ -16,8 +16,12 @@
 
 package com.android.wm.shell.splitscreen;
 
+import android.annotation.CallSuper;
 import android.app.ActivityManager;
+import android.content.Context;
+import android.graphics.Color;
 import android.graphics.Rect;
+import android.view.SurfaceControl;
 import android.view.SurfaceSession;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
@@ -28,15 +32,19 @@
 /**
  * Side stage for split-screen mode. Only tasks that are explicitly pinned to this stage show up
  * here. All other task are launch in the {@link MainStage}.
+ *
  * @see StageCoordinator
  */
 class SideStage extends StageTaskListener {
     private static final String TAG = SideStage.class.getSimpleName();
+    private final Context mContext;
+    private OutlineManager mOutlineManager;
 
-    SideStage(ShellTaskOrganizer taskOrganizer, int displayId,
+    SideStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
             StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
             SurfaceSession surfaceSession) {
         super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession);
+        mContext = context;
     }
 
     void addTask(ActivityManager.RunningTaskInfo task, Rect rootBounds,
@@ -69,4 +77,26 @@
         wct.reparent(task.token, newParent, false /* onTop */);
         return true;
     }
+
+    @Override
+    @CallSuper
+    public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
+        super.onTaskAppeared(taskInfo, leash);
+        if (mRootTaskInfo != null && mRootTaskInfo.taskId == taskInfo.taskId) {
+            mOutlineManager = new OutlineManager(mContext, mRootTaskInfo.configuration,
+                    () -> mRootLeash,
+                    Color.YELLOW);
+        }
+    }
+
+    @Override
+    @CallSuper
+    public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+        super.onTaskInfoChanged(taskInfo);
+        if (mRootTaskInfo != null && mRootTaskInfo.taskId == taskInfo.taskId
+                && mRootTaskInfo.isRunning) {
+            mOutlineManager.updateOutlineBounds(
+                    mRootTaskInfo.configuration.windowConfiguration.getBounds());
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index 002bfb6..d6afeba 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -64,6 +64,12 @@
         return null;
     }
 
+    /**
+     * Called when the keyguard occluded state changes.
+     * @param occluded Indicates if the keyguard is now occluded.
+     */
+    void onKeyguardOccludedChanged(boolean occluded);
+
     /** Get a string representation of a stage type */
     static String stageTypeToString(@StageType int stage) {
         switch (stage) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 9a457b5..36b2777 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -39,6 +39,8 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.Slog;
+import android.view.RemoteAnimationAdapter;
+import android.view.RemoteAnimationTarget;
 import android.window.IRemoteTransition;
 
 import androidx.annotation.BinderThread;
@@ -157,6 +159,10 @@
         mStageCoordinator.exitSplitScreen();
     }
 
+    public void onKeyguardOccludedChanged(boolean occluded) {
+        mStageCoordinator.onKeyguardOccludedChanged(occluded);
+    }
+
     public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
         mStageCoordinator.exitSplitScreenOnHide(exitSplitScreenOnHide);
     }
@@ -262,6 +268,11 @@
         return options;
     }
 
+    RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel) {
+        if (!isSplitScreenVisible()) return null;
+        return new RemoteAnimationTarget[]{mStageCoordinator.getDividerBarLegacyTarget()};
+    }
+
     public void dump(@NonNull PrintWriter pw, String prefix) {
         pw.println(prefix + TAG);
         if (mStageCoordinator != null) {
@@ -284,6 +295,13 @@
             mISplitScreen = new ISplitScreenImpl(SplitScreenController.this);
             return mISplitScreen;
         }
+
+        @Override
+        public void onKeyguardOccludedChanged(boolean occluded) {
+            mMainExecutor.execute(() -> {
+                SplitScreenController.this.onKeyguardOccludedChanged(occluded);
+            });
+        }
     }
 
     /**
@@ -417,6 +435,16 @@
         }
 
         @Override
+        public void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions,
+                int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
+                RemoteAnimationAdapter adapter) {
+            executeRemoteCallWithTaskPermission(mController, "startTasks",
+                    (controller) -> controller.mStageCoordinator.startTasksWithLegacyTransition(
+                            mainTaskId, mainOptions, sideTaskId, sideOptions, sidePosition,
+                            adapter));
+        }
+
+        @Override
         public void startTasks(int mainTaskId, @Nullable Bundle mainOptions,
                 int sideTaskId, @Nullable Bundle sideOptions,
                 @SplitPosition int sidePosition,
@@ -444,5 +472,14 @@
                         controller.startIntent(intent, fillInIntent, stage, position, options);
                     });
         }
+
+        @Override
+        public RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel) {
+            final RemoteAnimationTarget[][] out = new RemoteAnimationTarget[][]{null};
+            executeRemoteCallWithTaskPermission(mController, "onGoingToRecentsLegacy",
+                    (controller) -> out[0] = controller.onGoingToRecentsLegacy(cancel),
+                    true /* blocking */);
+            return out[0];
+        }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index c37789e..69d0be6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -84,17 +84,19 @@
     }
 
     void playAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
-            @NonNull SurfaceControl.Transaction t,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
             @NonNull Transitions.TransitionFinishCallback finishCallback,
             @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot) {
         mFinishCallback = finishCallback;
         mAnimatingTransition = transition;
         if (mRemoteHandler != null) {
-            mRemoteHandler.startAnimation(transition, info, t, mRemoteFinishCB);
+            mRemoteHandler.startAnimation(transition, info, startTransaction, finishTransaction,
+                    mRemoteFinishCB);
             mRemoteHandler = null;
             return;
         }
-        playInternalAnimation(transition, info, t, mainRoot, sideRoot);
+        playInternalAnimation(transition, info, startTransaction, mainRoot, sideRoot);
     }
 
     private void playInternalAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 0264c5a..4e91193 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -20,6 +20,7 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -42,11 +43,21 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.ActivityTaskManager;
+import android.app.WindowConfiguration;
 import android.content.Context;
 import android.graphics.Rect;
+import android.hardware.devicestate.DeviceStateManager;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.RemoteException;
 import android.util.Log;
+import android.util.Slog;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
+import android.view.RemoteAnimationAdapter;
+import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
 import android.view.WindowManager;
@@ -115,7 +126,8 @@
     private final List<SplitScreen.SplitScreenListener> mListeners = new ArrayList<>();
     private final DisplayImeController mDisplayImeController;
     private final SplitScreenTransitions mSplitTransitions;
-    private boolean mExitSplitScreenOnHide = true;
+    private boolean mExitSplitScreenOnHide;
+    private boolean mKeyguardOccluded;
 
     // TODO(b/187041611): remove this flag after totally deprecated legacy split
     /** Whether the device is supporting legacy split or not. */
@@ -150,6 +162,7 @@
                 mSyncQueue,
                 mSurfaceSession);
         mSideStage = new SideStage(
+                mContext,
                 mTaskOrganizer,
                 mDisplayId,
                 mSideStageListener,
@@ -157,6 +170,10 @@
                 mSurfaceSession);
         mDisplayImeController = displayImeController;
         mRootTDAOrganizer.registerListener(displayId, this);
+        final DeviceStateManager deviceStateManager =
+                mContext.getSystemService(DeviceStateManager.class);
+        deviceStateManager.registerCallback(taskOrganizer.getExecutor(),
+                new DeviceStateManager.FoldStateListener(mContext, this::onFoldedStateChanged));
         mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions,
                 mOnTransitionAnimationComplete);
         transitions.addHandler(this);
@@ -241,6 +258,75 @@
                 TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this);
     }
 
+    /** Starts 2 tasks in one legacy transition. */
+    void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions,
+            int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
+            RemoteAnimationAdapter adapter) {
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
+        // Need to add another wrapper here in shell so that we can inject the divider bar
+        // and also manage the process elevation via setRunningRemote
+        IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
+            @Override
+            public void onAnimationStart(@WindowManager.TransitionOldType int transit,
+                    RemoteAnimationTarget[] apps,
+                    RemoteAnimationTarget[] wallpapers,
+                    RemoteAnimationTarget[] nonApps,
+                    final IRemoteAnimationFinishedCallback finishedCallback) {
+                RemoteAnimationTarget[] augmentedNonApps =
+                        new RemoteAnimationTarget[nonApps.length + 1];
+                for (int i = 0; i < nonApps.length; ++i) {
+                    augmentedNonApps[i] = nonApps[i];
+                }
+                augmentedNonApps[augmentedNonApps.length - 1] = getDividerBarLegacyTarget();
+                try {
+                    ActivityTaskManager.getService().setRunningRemoteTransitionDelegate(
+                            adapter.getCallingApplication());
+                    adapter.getRunner().onAnimationStart(transit, apps, wallpapers, nonApps,
+                            finishedCallback);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Error starting remote animation", e);
+                }
+            }
+
+            @Override
+            public void onAnimationCancelled() {
+                try {
+                    adapter.getRunner().onAnimationCancelled();
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Error starting remote animation", e);
+                }
+            }
+        };
+        RemoteAnimationAdapter wrappedAdapter = new RemoteAnimationAdapter(
+                wrapper, adapter.getDuration(), adapter.getStatusBarTransitionDelay());
+
+        if (mainOptions == null) {
+            mainOptions = ActivityOptions.makeRemoteAnimation(wrappedAdapter).toBundle();
+        } else {
+            ActivityOptions mainActivityOptions = ActivityOptions.fromBundle(mainOptions);
+            mainActivityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter));
+        }
+
+        sideOptions = sideOptions != null ? sideOptions : new Bundle();
+        setSideStagePosition(sidePosition);
+
+        // Build a request WCT that will launch both apps such that task 0 is on the main stage
+        // while task 1 is on the side stage.
+        mMainStage.activate(getMainStageBounds(), wct);
+        mSideStage.setBounds(getSideStageBounds(), wct);
+
+        // Make sure the launch options will put tasks in the corresponding split roots
+        addActivityOptions(mainOptions, mMainStage);
+        addActivityOptions(sideOptions, mSideStage);
+
+        // Add task launch requests
+        wct.startTask(mainTaskId, mainOptions);
+        wct.startTask(sideTaskId, sideOptions);
+
+        // Using legacy transitions, so we can't use blast sync since it conflicts.
+        mTaskOrganizer.applyTransaction(wct);
+    }
+
     @SplitLayout.SplitPosition
     int getSideStagePosition() {
         return mSideStagePosition;
@@ -253,17 +339,17 @@
     }
 
     void setSideStagePosition(@SplitPosition int sideStagePosition) {
-        setSideStagePosition(sideStagePosition, true /* updateVisibility */);
+        setSideStagePosition(sideStagePosition, true /* updateBounds */);
     }
 
     private void setSideStagePosition(@SplitPosition int sideStagePosition,
-            boolean updateVisibility) {
+            boolean updateBounds) {
         if (mSideStagePosition == sideStagePosition) return;
         mSideStagePosition = sideStagePosition;
         sendOnStagePositionChanged();
 
-        if (mSideStageListener.mVisible && updateVisibility) {
-            onStageVisibilityChanged(mSideStageListener);
+        if (mSideStageListener.mVisible && updateBounds) {
+            onBoundsChanged(mSplitLayout);
         }
     }
 
@@ -275,6 +361,12 @@
         mTaskOrganizer.applyTransaction(wct);
     }
 
+    void onKeyguardOccludedChanged(boolean occluded) {
+        // Do not exit split directly, because it needs to wait for task info update to determine
+        // which task should remain on top after split dismissed.
+        mKeyguardOccluded = occluded;
+    }
+
     void exitSplitScreen() {
         exitSplitScreen(null /* childrenToTop */);
     }
@@ -403,13 +495,27 @@
         // Divider is only visible if both the main stage and side stages are visible
         setDividerVisibility(isSplitScreenVisible());
 
-        if (mExitSplitScreenOnHide && !mainStageVisible && !sideStageVisible) {
-            // Exit split-screen if both stage are not visible.
-            // TODO: This is only a temporary request from UX and is likely to be removed soon...
-            exitSplitScreen();
+        if (!mainStageVisible && !sideStageVisible) {
+            if (mExitSplitScreenOnHide
+            // Don't dismiss staged split when both stages are not visible due to sleeping display,
+            // like the cases keyguard showing or screen off.
+            || (!mMainStage.mRootTaskInfo.isSleeping && !mSideStage.mRootTaskInfo.isSleeping)) {
+                exitSplitScreen();
+            }
+        } else if (mKeyguardOccluded) {
+            // At least one of the stages is visible while keyguard occluded. Dismiss split because
+            // there's show-when-locked activity showing on top of keyguard. Also make sure the
+            // task contains show-when-locked activity remains on top after split dismissed.
+            final StageTaskListener toTop =
+                    mainStageVisible ? mMainStage : (sideStageVisible ? mSideStage : null);
+            exitSplitScreen(toTop);
         }
 
-        if (mainStageVisible) {
+        // When both stage's visibility changed to visible, main stage might receives visibility
+        // changed before side stage if it has higher z-order than side stage. Make sure we only
+        // update main stage's windowing mode with the visibility changed of side stage to prevent
+        // stacking multiple windowing mode transactions which result to flicker issue.
+        if (mainStageVisible && stageListener == mSideStageListener) {
             final WindowContainerTransaction wct = new WindowContainerTransaction();
             if (sideStageVisible) {
                 // The main stage configuration should to follow split layout when side stage is
@@ -487,10 +593,6 @@
             // Make sure the main stage is active.
             mMainStage.activate(getMainStageBounds(), wct);
             mSideStage.setBounds(getSideStageBounds(), wct);
-            // Reorder side stage to the top whenever there's a new child task appeared in side
-            // stage. This is needed to prevent main stage occludes side stage and makes main stage
-            // flipping between fullscreen and multi-window windowing mode.
-            wct.reorder(mSideStage.mRootTaskInfo.token, true);
             mTaskOrganizer.applyTransaction(wct);
         }
     }
@@ -580,11 +682,18 @@
     public void onDisplayAreaInfoChanged(DisplayAreaInfo displayAreaInfo) {
         mDisplayAreaInfo = displayAreaInfo;
         if (mSplitLayout != null
-                && mSplitLayout.updateConfiguration(mDisplayAreaInfo.configuration)) {
+                && mSplitLayout.updateConfiguration(mDisplayAreaInfo.configuration)
+                && mMainStage.isActive()) {
             onBoundsChanged(mSplitLayout);
         }
     }
 
+    private void onFoldedStateChanged(boolean folded) {
+        if (folded && mMainStage.isActive()) {
+            exitSplitScreen(mMainStage);
+        }
+    }
+
     private Rect getSideStageBounds() {
         return mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
                 ? mSplitLayout.getBounds1() : mSplitLayout.getBounds2();
@@ -672,7 +781,8 @@
     @Override
     public boolean startAnimation(@NonNull IBinder transition,
             @NonNull TransitionInfo info,
-            @NonNull SurfaceControl.Transaction t,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         if (transition != mSplitTransitions.mPendingDismiss
                 && transition != mSplitTransitions.mPendingEnter) {
@@ -717,14 +827,14 @@
 
         boolean shouldAnimate = true;
         if (mSplitTransitions.mPendingEnter == transition) {
-            shouldAnimate = startPendingEnterAnimation(transition, info, t);
+            shouldAnimate = startPendingEnterAnimation(transition, info, startTransaction);
         } else if (mSplitTransitions.mPendingDismiss == transition) {
-            shouldAnimate = startPendingDismissAnimation(transition, info, t);
+            shouldAnimate = startPendingDismissAnimation(transition, info, startTransaction);
         }
         if (!shouldAnimate) return false;
 
-        mSplitTransitions.playAnimation(transition, info, t, finishCallback,
-                mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
+        mSplitTransitions.playAnimation(transition, info, startTransaction, finishTransaction,
+                finishCallback, mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
         return true;
     }
 
@@ -754,7 +864,7 @@
 
             // Update local states (before animating).
             setDividerVisibility(true);
-            setSideStagePosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, false /* updateVisibility */);
+            setSideStagePosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, false /* updateBounds */);
             setSplitsVisible(true);
 
             addDividerBarToTransition(info, t, true /* show */);
@@ -860,6 +970,16 @@
         }
     }
 
+    RemoteAnimationTarget getDividerBarLegacyTarget() {
+        final Rect bounds = mSplitLayout.getDividerBounds();
+        return new RemoteAnimationTarget(-1 /* taskId */, -1 /* mode */,
+                mSplitLayout.getDividerLeash(), false /* isTranslucent */, null /* clipRect */,
+                null /* contentInsets */, Integer.MAX_VALUE /* prefixOrderIndex */,
+                new android.graphics.Point(0, 0) /* position */, bounds, bounds,
+                new WindowConfiguration(), true, null /* startLeash */, null /* startBounds */,
+                null /* taskInfo */, TYPE_DOCK_DIVIDER);
+    }
+
     @Override
     public void dump(@NonNull PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index c6fb5af..4eadf8c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -16,6 +16,13 @@
 
 package com.android.wm.shell.transition;
 
+import static android.app.ActivityOptions.ANIM_CLIP_REVEAL;
+import static android.app.ActivityOptions.ANIM_CUSTOM;
+import static android.app.ActivityOptions.ANIM_NONE;
+import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
+import static android.app.ActivityOptions.ANIM_SCALE_UP;
+import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN;
+import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
@@ -29,17 +36,28 @@
 import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
 import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
 
+import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_CLOSE;
+import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_CLOSE;
+import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_OPEN;
+import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_NONE;
+import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_OPEN;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.graphics.Point;
 import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
 import android.os.IBinder;
+import android.os.SystemProperties;
+import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.view.Choreographer;
 import android.view.SurfaceControl;
+import android.view.SurfaceSession;
 import android.view.animation.AlphaAnimation;
 import android.view.animation.Animation;
 import android.view.animation.Transformation;
@@ -61,30 +79,53 @@
 public class DefaultTransitionHandler implements Transitions.TransitionHandler {
     private static final int MAX_ANIMATION_DURATION = 3000;
 
+    /**
+     * Restrict ability of activities overriding transition animation in a way such that
+     * an activity can do it only when the transition happens within a same task.
+     *
+     * @see android.app.Activity#overridePendingTransition(int, int)
+     */
+    private static final String DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY =
+            "persist.wm.disable_custom_task_animation";
+
+    /**
+     * @see #DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY
+     */
+    static boolean sDisableCustomTaskAnimationProperty =
+            SystemProperties.getBoolean(DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY, true);
+
     private final TransactionPool mTransactionPool;
+    private final Context mContext;
     private final ShellExecutor mMainExecutor;
     private final ShellExecutor mAnimExecutor;
     private final TransitionAnimation mTransitionAnimation;
 
+    private final SurfaceSession mSurfaceSession = new SurfaceSession();
+
     /** Keeps track of the currently-running animations associated with each transition. */
     private final ArrayMap<IBinder, ArrayList<Animator>> mAnimations = new ArrayMap<>();
 
     private final Rect mInsets = new Rect(0, 0, 0, 0);
     private float mTransitionAnimationScaleSetting = 1.0f;
 
+    private final int mCurrentUserId;
+
     DefaultTransitionHandler(@NonNull TransactionPool transactionPool, Context context,
             @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) {
         mTransactionPool = transactionPool;
+        mContext = context;
         mMainExecutor = mainExecutor;
         mAnimExecutor = animExecutor;
         mTransitionAnimation = new TransitionAnimation(context, false /* debug */, Transitions.TAG);
+        mCurrentUserId = UserHandle.myUserId();
 
         AttributeCache.init(context);
     }
 
     @Override
     public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
-            @NonNull SurfaceControl.Transaction t,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
                 "start default transition animation, info = %s", info);
@@ -100,16 +141,18 @@
             mAnimations.remove(transition);
             finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
         };
+
+        final int wallpaperTransit = getWallpaperTransitType(info);
         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
             final TransitionInfo.Change change = info.getChanges().get(i);
             if (change.getMode() == TRANSIT_CHANGE) {
                 // No default animation for this, so just update bounds/position.
-                t.setPosition(change.getLeash(),
+                startTransaction.setPosition(change.getLeash(),
                         change.getEndAbsBounds().left - change.getEndRelOffset().x,
                         change.getEndAbsBounds().top - change.getEndRelOffset().y);
                 if (change.getTaskInfo() != null) {
                     // Skip non-tasks since those usually have null bounds.
-                    t.setWindowCrop(change.getLeash(),
+                    startTransaction.setWindowCrop(change.getLeash(),
                             change.getEndAbsBounds().width(), change.getEndAbsBounds().height());
                 }
             }
@@ -117,12 +160,17 @@
             // Don't animate anything that isn't independent.
             if (!TransitionInfo.isIndependent(change, info)) continue;
 
-            Animation a = loadAnimation(info.getType(), info.getFlags(), change);
+            Animation a = loadAnimation(info, change, wallpaperTransit);
             if (a != null) {
-                startAnimInternal(animations, a, change.getLeash(), onAnimFinish);
+                startAnimInternal(animations, a, change.getLeash(), onAnimFinish,
+                        null /* position */);
+
+                if (info.getAnimationOptions() != null) {
+                    attachThumbnail(animations, onAnimFinish, change, info.getAnimationOptions());
+                }
             }
         }
-        t.apply();
+        startTransaction.apply();
         // run finish now in-case there are no animations
         onAnimFinish.run();
         return true;
@@ -141,68 +189,111 @@
     }
 
     @Nullable
-    private Animation loadAnimation(int type, int flags, TransitionInfo.Change change) {
-        // TODO(b/178678389): It should handle more type animation here
+    private Animation loadAnimation(TransitionInfo info, TransitionInfo.Change change,
+            int wallpaperTransit) {
         Animation a = null;
 
-        final boolean isOpening = Transitions.isOpeningType(type);
+        final int type = info.getType();
+        final int flags = info.getFlags();
         final int changeMode = change.getMode();
         final int changeFlags = change.getFlags();
+        final boolean isOpeningType = Transitions.isOpeningType(type);
+        final boolean enter = Transitions.isOpeningType(changeMode);
+        final boolean isTask = change.getTaskInfo() != null;
+        final TransitionInfo.AnimationOptions options = info.getAnimationOptions();
+        final int overrideType = options != null ? options.getType() : ANIM_NONE;
+        final boolean canCustomContainer = isTask ? !sDisableCustomTaskAnimationProperty : true;
 
         if (type == TRANSIT_RELAUNCH) {
             a = mTransitionAnimation.createRelaunchAnimation(
-                    change.getStartAbsBounds(), mInsets, change.getEndAbsBounds());
+                    change.getEndAbsBounds(), mInsets, change.getEndAbsBounds());
         } else if (type == TRANSIT_KEYGUARD_GOING_AWAY) {
             a = mTransitionAnimation.loadKeyguardExitAnimation(flags,
                     (changeFlags & FLAG_SHOW_WALLPAPER) != 0);
         } else if (type == TRANSIT_KEYGUARD_UNOCCLUDE) {
             a = mTransitionAnimation.loadKeyguardUnoccludeAnimation();
-        } else if (changeMode == TRANSIT_OPEN && isOpening) {
-            if ((changeFlags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
-                // This received a transferred starting window, so don't animate
-                return null;
-            }
-
-            if ((changeFlags & FLAG_IS_VOICE_INTERACTION) != 0) {
-                a = mTransitionAnimation.loadVoiceActivityOpenAnimation(true /** enter */);
-            } else if (change.getTaskInfo() != null) {
-                a = mTransitionAnimation.loadDefaultAnimationAttr(
-                        R.styleable.WindowAnimation_taskOpenEnterAnimation);
+        } else if (overrideType == ANIM_CUSTOM
+                && (canCustomContainer || options.getOverrideTaskTransition())) {
+            a = mTransitionAnimation.loadAnimationRes(options.getPackageName(), enter
+                    ? options.getEnterResId() : options.getExitResId());
+        } else if (overrideType == ANIM_OPEN_CROSS_PROFILE_APPS && enter) {
+            a = mTransitionAnimation.loadCrossProfileAppEnterAnimation();
+        } else if (overrideType == ANIM_CLIP_REVEAL) {
+            a = mTransitionAnimation.createClipRevealAnimationLocked(type, wallpaperTransit, enter,
+                    change.getEndAbsBounds(), change.getEndAbsBounds(),
+                    options.getTransitionBounds());
+        } else if (overrideType == ANIM_SCALE_UP) {
+            a = mTransitionAnimation.createScaleUpAnimationLocked(type, wallpaperTransit, enter,
+                    change.getEndAbsBounds(), options.getTransitionBounds());
+        } else if (overrideType == ANIM_THUMBNAIL_SCALE_UP
+                || overrideType == ANIM_THUMBNAIL_SCALE_DOWN) {
+            final boolean scaleUp = overrideType == ANIM_THUMBNAIL_SCALE_UP;
+            a = mTransitionAnimation.createThumbnailEnterExitAnimationLocked(enter, scaleUp,
+                    change.getEndAbsBounds(), type, wallpaperTransit, options.getThumbnail(),
+                    options.getTransitionBounds());
+        } else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_OPEN) {
+            a = mTransitionAnimation.loadDefaultAnimationAttr(enter
+                    ? R.styleable.WindowAnimation_wallpaperIntraOpenEnterAnimation
+                    : R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation);
+        } else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_CLOSE) {
+            a = mTransitionAnimation.loadDefaultAnimationAttr(enter
+                    ? R.styleable.WindowAnimation_wallpaperIntraCloseEnterAnimation
+                    : R.styleable.WindowAnimation_wallpaperIntraCloseExitAnimation);
+        } else if (wallpaperTransit == WALLPAPER_TRANSITION_OPEN) {
+            a = mTransitionAnimation.loadDefaultAnimationAttr(enter
+                    ? R.styleable.WindowAnimation_wallpaperOpenEnterAnimation
+                    : R.styleable.WindowAnimation_wallpaperOpenExitAnimation);
+        } else if (wallpaperTransit == WALLPAPER_TRANSITION_CLOSE) {
+            a = mTransitionAnimation.loadDefaultAnimationAttr(enter
+                    ? R.styleable.WindowAnimation_wallpaperCloseEnterAnimation
+                    : R.styleable.WindowAnimation_wallpaperCloseExitAnimation);
+        } else if ((changeFlags & FLAG_IS_VOICE_INTERACTION) != 0) {
+            if (isOpeningType) {
+                a = mTransitionAnimation.loadVoiceActivityOpenAnimation(enter);
             } else {
-                a = mTransitionAnimation.loadDefaultAnimationRes(
-                        (changeFlags & FLAG_TRANSLUCENT) == 0
-                        ? R.anim.activity_open_enter : R.anim.activity_translucent_open_enter);
+                a = mTransitionAnimation.loadVoiceActivityExitAnimation(enter);
             }
-        } else if (changeMode == TRANSIT_TO_FRONT && isOpening) {
-            if ((changeFlags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
-                // This received a transferred starting window, so don't animate
-                return null;
-            }
-
-            if ((changeFlags & FLAG_IS_VOICE_INTERACTION) != 0) {
-                a = mTransitionAnimation.loadVoiceActivityOpenAnimation(true /** enter */);
+        } else if ((changeFlags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0 && isOpeningType) {
+            // This received a transferred starting window, so don't animate
+            return null;
+        } else if (type == TRANSIT_OPEN) {
+            if (isTask) {
+                a = mTransitionAnimation.loadDefaultAnimationAttr(enter
+                        ? R.styleable.WindowAnimation_taskOpenEnterAnimation
+                        : R.styleable.WindowAnimation_taskOpenExitAnimation);
             } else {
-                a = mTransitionAnimation.loadDefaultAnimationAttr(
-                        R.styleable.WindowAnimation_taskToFrontEnterAnimation);
+                if ((changeFlags & FLAG_TRANSLUCENT) != 0 && enter) {
+                    a = mTransitionAnimation.loadDefaultAnimationRes(
+                            R.anim.activity_translucent_open_enter);
+                } else {
+                    a = mTransitionAnimation.loadDefaultAnimationAttr(enter
+                            ? R.styleable.WindowAnimation_activityOpenEnterAnimation
+                            : R.styleable.WindowAnimation_activityOpenExitAnimation);
+                }
             }
-        } else if (changeMode == TRANSIT_CLOSE && !isOpening) {
-            if ((changeFlags & FLAG_IS_VOICE_INTERACTION) != 0) {
-                a = mTransitionAnimation.loadVoiceActivityExitAnimation(false /** enter */);
-            } else if (change.getTaskInfo() != null) {
-                a = mTransitionAnimation.loadDefaultAnimationAttr(
-                        R.styleable.WindowAnimation_taskCloseExitAnimation);
+        } else if (type == TRANSIT_TO_FRONT) {
+            a = mTransitionAnimation.loadDefaultAnimationAttr(enter
+                    ? R.styleable.WindowAnimation_taskToFrontEnterAnimation
+                    : R.styleable.WindowAnimation_taskToFrontExitAnimation);
+        } else if (type == TRANSIT_CLOSE) {
+            if (isTask) {
+                a = mTransitionAnimation.loadDefaultAnimationAttr(enter
+                        ? R.styleable.WindowAnimation_taskCloseEnterAnimation
+                        : R.styleable.WindowAnimation_taskCloseExitAnimation);
             } else {
-                a = mTransitionAnimation.loadDefaultAnimationRes(
-                        (changeFlags & FLAG_TRANSLUCENT) == 0
-                        ? R.anim.activity_close_exit : R.anim.activity_translucent_close_exit);
+                if ((changeFlags & FLAG_TRANSLUCENT) != 0 && !enter) {
+                    a = mTransitionAnimation.loadDefaultAnimationRes(
+                            R.anim.activity_translucent_close_exit);
+                } else {
+                    a = mTransitionAnimation.loadDefaultAnimationAttr(enter
+                            ? R.styleable.WindowAnimation_activityCloseEnterAnimation
+                            : R.styleable.WindowAnimation_activityCloseExitAnimation);
+                }
             }
-        } else if (changeMode == TRANSIT_TO_BACK && !isOpening) {
-            if ((changeFlags & FLAG_IS_VOICE_INTERACTION) != 0) {
-                a = mTransitionAnimation.loadVoiceActivityExitAnimation(false /** enter */);
-            } else {
-                a = mTransitionAnimation.loadDefaultAnimationAttr(
-                        R.styleable.WindowAnimation_taskToBackExitAnimation);
-            }
+        } else if (type == TRANSIT_TO_BACK) {
+            a = mTransitionAnimation.loadDefaultAnimationAttr(enter
+                    ? R.styleable.WindowAnimation_taskToBackEnterAnimation
+                    : R.styleable.WindowAnimation_taskToBackExitAnimation);
         } else if (changeMode == TRANSIT_CHANGE) {
             // In the absence of a specific adapter, we just want to keep everything stationary.
             a = new AlphaAnimation(1.f, 1.f);
@@ -210,17 +301,19 @@
         }
 
         if (a != null) {
-            Rect start = change.getStartAbsBounds();
-            Rect end = change.getEndAbsBounds();
+            if (!a.isInitialized()) {
+                Rect end = change.getEndAbsBounds();
+                a.initialize(end.width(), end.height(), end.width(), end.height());
+            }
             a.restrictDuration(MAX_ANIMATION_DURATION);
-            a.initialize(end.width(), end.height(), start.width(), start.height());
             a.scaleCurrentDuration(mTransitionAnimationScaleSetting);
         }
         return a;
     }
 
     private void startAnimInternal(@NonNull ArrayList<Animator> animations, @NonNull Animation anim,
-            @NonNull SurfaceControl leash, @NonNull Runnable finishCallback) {
+            @NonNull SurfaceControl leash, @NonNull Runnable finishCallback,
+            @Nullable Point position) {
         final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
         final ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
         final Transformation transformation = new Transformation();
@@ -231,11 +324,13 @@
         va.addUpdateListener(animation -> {
             final long currentPlayTime = Math.min(va.getDuration(), va.getCurrentPlayTime());
 
-            applyTransformation(currentPlayTime, transaction, leash, anim, transformation, matrix);
+            applyTransformation(currentPlayTime, transaction, leash, anim, transformation, matrix,
+                    position);
         });
 
         final Runnable finisher = () -> {
-            applyTransformation(va.getDuration(), transaction, leash, anim, transformation, matrix);
+            applyTransformation(va.getDuration(), transaction, leash, anim, transformation, matrix,
+                    position);
 
             mTransactionPool.release(transaction);
             mMainExecutor.execute(() -> {
@@ -258,9 +353,112 @@
         mAnimExecutor.execute(va::start);
     }
 
+    private void attachThumbnail(@NonNull ArrayList<Animator> animations,
+            @NonNull Runnable finishCallback, TransitionInfo.Change change,
+            TransitionInfo.AnimationOptions options) {
+        final boolean isTask = change.getTaskInfo() != null;
+        final boolean isOpen = Transitions.isOpeningType(change.getMode());
+        final boolean isClose = Transitions.isClosingType(change.getMode());
+        if (isOpen) {
+            if (options.getType() == ANIM_OPEN_CROSS_PROFILE_APPS && isTask) {
+                attachCrossProfileThunmbnailAnimation(animations, finishCallback, change);
+            } else if (options.getType() == ANIM_THUMBNAIL_SCALE_UP) {
+                attachThumbnailAnimation(animations, finishCallback, change, options);
+            }
+        } else if (isClose && options.getType() == ANIM_THUMBNAIL_SCALE_DOWN) {
+            attachThumbnailAnimation(animations, finishCallback, change, options);
+        }
+    }
+
+    private void attachCrossProfileThunmbnailAnimation(@NonNull ArrayList<Animator> animations,
+            @NonNull Runnable finishCallback, TransitionInfo.Change change) {
+        final int thumbnailDrawableRes = change.getTaskInfo().userId == mCurrentUserId
+                ? R.drawable.ic_account_circle : R.drawable.ic_corp_badge;
+        final Rect bounds = change.getEndAbsBounds();
+        final HardwareBuffer thumbnail = mTransitionAnimation.createCrossProfileAppsThumbnail(
+                thumbnailDrawableRes, bounds);
+        if (thumbnail == null) {
+            return;
+        }
+
+        final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+        final WindowThumbnail wt = WindowThumbnail.createAndAttach(mSurfaceSession,
+                change.getLeash(), thumbnail, transaction);
+        final Animation a =
+                mTransitionAnimation.createCrossProfileAppsThumbnailAnimationLocked(bounds);
+        if (a == null) {
+            return;
+        }
+
+        final Runnable finisher = () -> {
+            wt.destroy(transaction);
+            mTransactionPool.release(transaction);
+
+            finishCallback.run();
+        };
+        a.restrictDuration(MAX_ANIMATION_DURATION);
+        a.scaleCurrentDuration(mTransitionAnimationScaleSetting);
+        startAnimInternal(animations, a, wt.getSurface(), finisher,
+                new Point(bounds.left, bounds.top));
+    }
+
+    private void attachThumbnailAnimation(@NonNull ArrayList<Animator> animations,
+            @NonNull Runnable finishCallback, TransitionInfo.Change change,
+            TransitionInfo.AnimationOptions options) {
+        final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+        final WindowThumbnail wt = WindowThumbnail.createAndAttach(mSurfaceSession,
+                change.getLeash(), options.getThumbnail(), transaction);
+        final Rect bounds = change.getEndAbsBounds();
+        final int orientation = mContext.getResources().getConfiguration().orientation;
+        final Animation a = mTransitionAnimation.createThumbnailAspectScaleAnimationLocked(bounds,
+                mInsets, options.getThumbnail(), orientation, null /* startRect */,
+                options.getTransitionBounds(), options.getType() == ANIM_THUMBNAIL_SCALE_UP);
+
+        final Runnable finisher = () -> {
+            wt.destroy(transaction);
+            mTransactionPool.release(transaction);
+
+            finishCallback.run();
+        };
+        a.restrictDuration(MAX_ANIMATION_DURATION);
+        a.scaleCurrentDuration(mTransitionAnimationScaleSetting);
+        startAnimInternal(animations, a, wt.getSurface(), finisher, null /* position */);
+    }
+
+    private static int getWallpaperTransitType(TransitionInfo info) {
+        boolean hasOpenWallpaper = false;
+        boolean hasCloseWallpaper = false;
+
+        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+            final TransitionInfo.Change change = info.getChanges().get(i);
+            if ((change.getFlags() & FLAG_SHOW_WALLPAPER) != 0) {
+                if (Transitions.isOpeningType(change.getMode())) {
+                    hasOpenWallpaper = true;
+                } else if (Transitions.isClosingType(change.getMode())) {
+                    hasCloseWallpaper = true;
+                }
+            }
+        }
+
+        if (hasOpenWallpaper && hasCloseWallpaper) {
+            return Transitions.isOpeningType(info.getType())
+                    ? WALLPAPER_TRANSITION_INTRA_OPEN : WALLPAPER_TRANSITION_INTRA_CLOSE;
+        } else if (hasOpenWallpaper) {
+            return WALLPAPER_TRANSITION_OPEN;
+        } else if (hasCloseWallpaper) {
+            return WALLPAPER_TRANSITION_CLOSE;
+        } else {
+            return WALLPAPER_TRANSITION_NONE;
+        }
+    }
+
     private static void applyTransformation(long time, SurfaceControl.Transaction t,
-            SurfaceControl leash, Animation anim, Transformation transformation, float[] matrix) {
+            SurfaceControl leash, Animation anim, Transformation transformation, float[] matrix,
+            Point position) {
         anim.getTransformation(time, transformation);
+        if (position != null) {
+            transformation.getMatrix().postTranslate(position.x, position.y);
+        }
         t.setMatrix(leash, transformation.getMatrix(), matrix);
         t.setAlpha(leash, transformation.getAlpha());
         t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
index 4da6664..6bd8053 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
@@ -57,7 +57,8 @@
 
     @Override
     public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
-            @NonNull SurfaceControl.Transaction t,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         if (mTransition != transition) return false;
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Using registered One-shot remote"
@@ -70,19 +71,24 @@
         };
         IRemoteTransitionFinishedCallback cb = new IRemoteTransitionFinishedCallback.Stub() {
             @Override
-            public void onTransitionFinished(WindowContainerTransaction wct) {
+            public void onTransitionFinished(WindowContainerTransaction wct,
+                    SurfaceControl.Transaction sct) {
                 if (mRemote.asBinder() != null) {
                     mRemote.asBinder().unlinkToDeath(remoteDied, 0 /* flags */);
                 }
-                mMainExecutor.execute(
-                        () -> finishCallback.onTransitionFinished(wct, null /* wctCB */));
+                mMainExecutor.execute(() -> {
+                    if (sct != null) {
+                        finishTransaction.merge(sct);
+                    }
+                    finishCallback.onTransitionFinished(wct, null /* wctCB */);
+                });
             }
         };
         try {
             if (mRemote.asBinder() != null) {
                 mRemote.asBinder().linkToDeath(remoteDied, 0 /* flags */);
             }
-            mRemote.startAnimation(transition, info, t, cb);
+            mRemote.startAnimation(transition, info, startTransaction, cb);
         } catch (RemoteException e) {
             Log.e(Transitions.TAG, "Error running remote transition.", e);
             if (mRemote.asBinder() != null) {
@@ -102,7 +108,8 @@
 
         IRemoteTransitionFinishedCallback cb = new IRemoteTransitionFinishedCallback.Stub() {
             @Override
-            public void onTransitionFinished(WindowContainerTransaction wct) {
+            public void onTransitionFinished(WindowContainerTransaction wct,
+                    SurfaceControl.Transaction sct) {
                 mMainExecutor.execute(
                         () -> finishCallback.onTransitionFinished(wct, null /* wctCB */));
             }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
index 9bfb261..f432049 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
@@ -56,14 +56,7 @@
     private final ArrayList<Pair<TransitionFilter, IRemoteTransition>> mFilters =
             new ArrayList<>();
 
-    private final IBinder.DeathRecipient mTransitionDeathRecipient =
-            new IBinder.DeathRecipient() {
-                @Override
-                @BinderThread
-                public void binderDied() {
-                    mMainExecutor.execute(() -> mFilters.clear());
-                }
-            };
+    private final ArrayMap<IBinder, RemoteDeathHandler> mDeathHandlers = new ArrayMap<>();
 
     RemoteTransitionHandler(@NonNull ShellExecutor mainExecutor) {
         mMainExecutor = mainExecutor;
@@ -71,7 +64,9 @@
 
     void addFiltered(TransitionFilter filter, IRemoteTransition remote) {
         try {
-            remote.asBinder().linkToDeath(mTransitionDeathRecipient, 0 /* flags */);
+            RemoteDeathHandler handler = new RemoteDeathHandler(remote.asBinder());
+            remote.asBinder().linkToDeath(handler, 0 /* flags */);
+            mDeathHandlers.put(remote.asBinder(), handler);
         } catch (RemoteException e) {
             Slog.e(TAG, "Failed to link to death");
             return;
@@ -88,7 +83,8 @@
             }
         }
         if (removed) {
-            remote.asBinder().unlinkToDeath(mTransitionDeathRecipient, 0 /* flags */);
+            RemoteDeathHandler handler = mDeathHandlers.remove(remote.asBinder());
+            remote.asBinder().unlinkToDeath(handler, 0 /* flags */);
         }
     }
 
@@ -99,7 +95,8 @@
 
     @Override
     public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
-            @NonNull SurfaceControl.Transaction t,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         IRemoteTransition pendingRemote = mRequestedRemotes.get(transition);
         if (pendingRemote == null) {
@@ -132,11 +129,15 @@
         };
         IRemoteTransitionFinishedCallback cb = new IRemoteTransitionFinishedCallback.Stub() {
             @Override
-            public void onTransitionFinished(WindowContainerTransaction wct) {
+            public void onTransitionFinished(WindowContainerTransaction wct,
+                    SurfaceControl.Transaction sct) {
                 if (remote.asBinder() != null) {
                     remote.asBinder().unlinkToDeath(remoteDied, 0 /* flags */);
                 }
                 mMainExecutor.execute(() -> {
+                    if (sct != null) {
+                        finishTransaction.merge(sct);
+                    }
                     mRequestedRemotes.remove(transition);
                     finishCallback.onTransitionFinished(wct, null /* wctCB */);
                 });
@@ -146,7 +147,7 @@
             if (remote.asBinder() != null) {
                 remote.asBinder().linkToDeath(remoteDied, 0 /* flags */);
             }
-            remote.startAnimation(transition, info, t, cb);
+            remote.startAnimation(transition, info, startTransaction, cb);
         } catch (RemoteException e) {
             Log.e(Transitions.TAG, "Error running remote transition.", e);
             if (remote.asBinder() != null) {
@@ -170,7 +171,8 @@
 
         IRemoteTransitionFinishedCallback cb = new IRemoteTransitionFinishedCallback.Stub() {
             @Override
-            public void onTransitionFinished(WindowContainerTransaction wct) {
+            public void onTransitionFinished(WindowContainerTransaction wct,
+                    SurfaceControl.Transaction sct) {
                 mMainExecutor.execute(() -> {
                     if (!mRequestedRemotes.containsKey(mergeTarget)) {
                         Log.e(TAG, "Merged transition finished after it's mergeTarget (the "
@@ -200,4 +202,25 @@
                 + " for %s: %s", transition, remote);
         return new WindowContainerTransaction();
     }
+
+    /** NOTE: binder deaths can alter the filter order */
+    private class RemoteDeathHandler implements IBinder.DeathRecipient {
+        private final IBinder mRemote;
+
+        RemoteDeathHandler(IBinder remote) {
+            mRemote = remote;
+        }
+
+        @Override
+        @BinderThread
+        public void binderDied() {
+            mMainExecutor.execute(() -> {
+                for (int i = mFilters.size() - 1; i >= 0; --i) {
+                    if (mRemote.equals(mFilters.get(i).second.asBinder())) {
+                        mFilters.remove(i);
+                    }
+                }
+            });
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 60707cc..1ca71af 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -77,6 +77,12 @@
     /** Transition type for launching 2 tasks simultaneously. */
     public static final int TRANSIT_SPLIT_SCREEN_PAIR_OPEN = TRANSIT_FIRST_CUSTOM + 2;
 
+    /** Transition type for exiting PIP via the Shell, via pressing the expand button. */
+    public static final int TRANSIT_EXIT_PIP = TRANSIT_FIRST_CUSTOM + 3;
+
+    /** Transition type for removing PIP via the Shell, either via Dismiss bubble or Close. */
+    public static final int TRANSIT_REMOVE_PIP = TRANSIT_FIRST_CUSTOM + 4;
+
     private final WindowOrganizer mOrganizer;
     private final Context mContext;
     private final ShellExecutor mMainExecutor;
@@ -91,12 +97,13 @@
     private float mTransitionAnimationScaleSetting = 1.0f;
 
     private static final class ActiveTransition {
-        IBinder mToken = null;
-        TransitionHandler mHandler = null;
-        boolean mMerged = false;
-        TransitionInfo mInfo = null;
-        SurfaceControl.Transaction mStartT = null;
-        SurfaceControl.Transaction mFinishT = null;
+        IBinder mToken;
+        TransitionHandler mHandler;
+        boolean mMerged;
+        boolean mAborted;
+        TransitionInfo mInfo;
+        SurfaceControl.Transaction mStartT;
+        SurfaceControl.Transaction mFinishT;
     }
 
     /** Keeps track of currently playing transitions in the order of receipt. */
@@ -382,7 +389,7 @@
     }
 
     boolean startAnimation(@NonNull ActiveTransition active, TransitionHandler handler) {
-        return handler.startAnimation(active.mToken, active.mInfo, active.mStartT,
+        return handler.startAnimation(active.mToken, active.mInfo, active.mStartT, active.mFinishT,
                 (wct, cb) -> onFinish(active.mToken, wct, cb));
     }
 
@@ -416,17 +423,19 @@
 
     /** Special version of finish just for dealing with no-op/invalid transitions. */
     private void onAbort(IBinder transition) {
-        final int activeIdx = findActiveTransition(transition);
-        if (activeIdx < 0) return;
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
-                "Transition animation aborted due to no-op, notifying core %s", transition);
-        mActiveTransitions.remove(activeIdx);
-        mOrganizer.finishTransition(transition, null /* wct */, null /* wctCB */);
+        onFinish(transition, null /* wct */, null /* wctCB */, true /* abort */);
     }
 
     private void onFinish(IBinder transition,
             @Nullable WindowContainerTransaction wct,
             @Nullable WindowContainerTransactionCallback wctCB) {
+        onFinish(transition, wct, wctCB, false /* abort */);
+    }
+
+    private void onFinish(IBinder transition,
+            @Nullable WindowContainerTransaction wct,
+            @Nullable WindowContainerTransactionCallback wctCB,
+            boolean abort) {
         int activeIdx = findActiveTransition(transition);
         if (activeIdx < 0) {
             Log.e(TAG, "Trying to finish a non-running transition. Either remote crashed or "
@@ -434,28 +443,37 @@
             return;
         } else if (activeIdx > 0) {
             // This transition was merged.
-            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition was merged: %s",
-                    transition);
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition was merged (abort=%b:"
+                    + " %s", abort, transition);
             final ActiveTransition active = mActiveTransitions.get(activeIdx);
             active.mMerged = true;
+            active.mAborted = abort;
             if (active.mHandler != null) {
                 active.mHandler.onTransitionMerged(active.mToken);
             }
             return;
         }
+        mActiveTransitions.get(activeIdx).mAborted = abort;
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
-                "Transition animation finished, notifying core %s", transition);
+                "Transition animation finished (abort=%b), notifying core %s", abort, transition);
         // Merge all relevant transactions together
         SurfaceControl.Transaction fullFinish = mActiveTransitions.get(activeIdx).mFinishT;
         for (int iA = activeIdx + 1; iA < mActiveTransitions.size(); ++iA) {
             final ActiveTransition toMerge = mActiveTransitions.get(iA);
             if (!toMerge.mMerged) break;
+            // aborted transitions have no start/finish transactions
+            if (mActiveTransitions.get(iA).mStartT == null) break;
+            if (fullFinish == null) {
+                fullFinish = new SurfaceControl.Transaction();
+            }
             // Include start. It will be a no-op if it was already applied. Otherwise, we need it
             // to maintain consistent state.
             fullFinish.merge(mActiveTransitions.get(iA).mStartT);
             fullFinish.merge(mActiveTransitions.get(iA).mFinishT);
         }
-        fullFinish.apply();
+        if (fullFinish != null) {
+            fullFinish.apply();
+        }
         // Now perform all the finishes.
         mActiveTransitions.remove(activeIdx);
         mOrganizer.finishTransition(transition, wct, wctCB);
@@ -464,6 +482,12 @@
             ActiveTransition merged = mActiveTransitions.remove(activeIdx);
             mOrganizer.finishTransition(merged.mToken, null /* wct */, null /* wctCB */);
         }
+        // sift through aborted transitions
+        while (mActiveTransitions.size() > activeIdx
+                && mActiveTransitions.get(activeIdx).mAborted) {
+            ActiveTransition aborted = mActiveTransitions.remove(activeIdx);
+            mOrganizer.finishTransition(aborted.mToken, null /* wct */, null /* wctCB */);
+        }
         if (mActiveTransitions.size() <= activeIdx) {
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "All active transition animations "
                     + "finished");
@@ -494,6 +518,12 @@
         int mergeIdx = activeIdx + 1;
         while (mergeIdx < mActiveTransitions.size()) {
             ActiveTransition mergeCandidate = mActiveTransitions.get(mergeIdx);
+            if (mergeCandidate.mAborted) {
+                // transition was aborted, so we can skip for now (still leave it in the list
+                // so that it gets cleaned-up in the right order).
+                ++mergeIdx;
+                continue;
+            }
             if (mergeCandidate.mMerged) {
                 throw new IllegalStateException("Can't merge a transition after not-merging"
                         + " a preceding one.");
@@ -566,12 +596,19 @@
          * Starts a transition animation. This is always called if handleRequest returned non-null
          * for a particular transition. Otherwise, it is only called if no other handler before
          * it handled the transition.
-         *
+         * @param startTransaction the transaction given to the handler to be applied before the
+         *                         transition animation. Note the handler is expected to call on
+         *                         {@link SurfaceControl.Transaction#apply()} for startTransaction.
+         * @param finishTransaction the transaction given to the handler to be applied after the
+         *                       transition animation. Unlike startTransaction, the handler is NOT
+         *                       expected to apply this transaction. The Transition system will
+         *                       apply it when finishCallback is called.
          * @param finishCallback Call this when finished. This MUST be called on main thread.
          * @return true if transition was handled, false if not (falls-back to default).
          */
         boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
-                @NonNull SurfaceControl.Transaction t,
+                @NonNull SurfaceControl.Transaction startTransaction,
+                @NonNull SurfaceControl.Transaction finishTransaction,
                 @NonNull TransitionFinishCallback finishCallback);
 
         /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/WindowThumbnail.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/WindowThumbnail.java
new file mode 100644
index 0000000..2c668ed
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/WindowThumbnail.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.transition;
+
+import android.graphics.ColorSpace;
+import android.graphics.GraphicBuffer;
+import android.graphics.PixelFormat;
+import android.hardware.HardwareBuffer;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+
+/**
+ * Represents a surface that is displayed over a transition surface.
+ */
+class WindowThumbnail {
+
+    private SurfaceControl mSurfaceControl;
+
+    private WindowThumbnail() {}
+
+    /** Create a thumbnail surface and attach it over a parent surface. */
+    static WindowThumbnail createAndAttach(SurfaceSession surfaceSession, SurfaceControl parent,
+            HardwareBuffer thumbnailHeader, SurfaceControl.Transaction t) {
+        WindowThumbnail windowThumbnail = new WindowThumbnail();
+        windowThumbnail.mSurfaceControl = new SurfaceControl.Builder(surfaceSession)
+                .setParent(parent)
+                .setName("WindowThumanil : " + parent.toString())
+                .setCallsite("WindowThumanil")
+                .setFormat(PixelFormat.TRANSLUCENT)
+                .build();
+
+        GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer(thumbnailHeader);
+        t.setBuffer(windowThumbnail.mSurfaceControl, graphicBuffer);
+        t.setColorSpace(windowThumbnail.mSurfaceControl, ColorSpace.get(ColorSpace.Named.SRGB));
+        t.setLayer(windowThumbnail.mSurfaceControl, Integer.MAX_VALUE);
+        t.show(windowThumbnail.mSurfaceControl);
+        t.apply();
+
+        return windowThumbnail;
+    }
+
+    SurfaceControl getSurface() {
+        return mSurfaceControl;
+    }
+
+    /** Remove the thumbnail surface and release the surface. */
+    void destroy(SurfaceControl.Transaction t) {
+        if (mSurfaceControl == null) {
+            return;
+        }
+
+        t.remove(mSurfaceControl);
+        t.apply();
+        mSurfaceControl.release();
+        mSurfaceControl = null;
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java
index 20ac5bf..1cbad15 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java
@@ -47,6 +47,8 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.wm.shell.common.HandlerExecutor;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.SyncTransactionQueue.TransactionRunnable;
 
 import org.junit.After;
 import org.junit.Before;
@@ -71,6 +73,8 @@
     ShellTaskOrganizer mOrganizer;
     @Mock
     HandlerExecutor mExecutor;
+    @Mock
+    SyncTransactionQueue mSyncQueue;
 
     SurfaceSession mSession;
     SurfaceControl mLeash;
@@ -99,7 +103,14 @@
         }).when(mExecutor).execute(any());
 
         when(mOrganizer.getExecutor()).thenReturn(mExecutor);
-        mTaskView = new TaskView(mContext, mOrganizer);
+
+        doAnswer((InvocationOnMock invocationOnMock) -> {
+            final TransactionRunnable r = invocationOnMock.getArgument(0);
+            r.runWithTransaction(new SurfaceControl.Transaction());
+            return null;
+        }).when(mSyncQueue).runInSync(any());
+
+        mTaskView = new TaskView(mContext, mOrganizer, mSyncQueue);
         mTaskView.setListener(mExecutor, mViewListener);
     }
 
@@ -112,7 +123,7 @@
 
     @Test
     public void testSetPendingListener_throwsException() {
-        TaskView taskView = new TaskView(mContext, mOrganizer);
+        TaskView taskView = new TaskView(mContext, mOrganizer, mSyncQueue);
         taskView.setListener(mExecutor, mViewListener);
         try {
             taskView.setListener(mExecutor, mViewListener);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index 952dc31..e138595 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -61,7 +61,7 @@
         mSplitLayout = new SplitLayout(
                 "TestSplitLayout",
                 mContext,
-                getConfiguration(false),
+                getConfiguration(),
                 mSplitLayoutHandler,
                 b -> b.setParent(mRootLeash),
                 mDisplayImeController,
@@ -71,9 +71,22 @@
     @Test
     @UiThreadTest
     public void testUpdateConfiguration() {
-        mSplitLayout.init();
-        assertThat(mSplitLayout.updateConfiguration(getConfiguration(false))).isFalse();
-        assertThat(mSplitLayout.updateConfiguration(getConfiguration(true))).isTrue();
+        final Configuration config = getConfiguration();
+
+        // Verify it returns true if new config won't affect split layout.
+        assertThat(mSplitLayout.updateConfiguration(config)).isFalse();
+
+        // Verify updateConfiguration returns true if the orientation changed.
+        config.orientation = ORIENTATION_LANDSCAPE;
+        assertThat(mSplitLayout.updateConfiguration(config)).isTrue();
+
+        // Verify updateConfiguration returns true if it rotated.
+        config.windowConfiguration.setRotation(1);
+        assertThat(mSplitLayout.updateConfiguration(config)).isTrue();
+
+        // Verify updateConfiguration returns true if the root bounds changed.
+        config.windowConfiguration.setBounds(new Rect(0, 0, 2160, 1080));
+        assertThat(mSplitLayout.updateConfiguration(config)).isTrue();
     }
 
     @Test
@@ -108,12 +121,13 @@
         verify(mSplitLayoutHandler).onSnappedToDismiss(eq(true));
     }
 
-    private static Configuration getConfiguration(boolean isLandscape) {
+    private static Configuration getConfiguration() {
         final Configuration configuration = new Configuration();
         configuration.unset();
-        configuration.orientation = isLandscape ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT;
+        configuration.orientation = ORIENTATION_PORTRAIT;
+        configuration.windowConfiguration.setRotation(0);
         configuration.windowConfiguration.setBounds(
-                new Rect(0, 0, isLandscape ? 2160 : 1080, isLandscape ? 1080 : 2160));
+                new Rect(0, 0, 1080, 2160));
         return configuration;
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index 9d7c82b..0270093 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -79,6 +79,7 @@
     @Mock private ShellTaskOrganizer mMockShellTaskOrganizer;
     private TestShellExecutor mMainExecutor;
     private PipBoundsState mPipBoundsState;
+    private PipTransitionState mPipTransitionState;
     private PipBoundsAlgorithm mPipBoundsAlgorithm;
 
     private ComponentName mComponent1;
@@ -90,11 +91,12 @@
         mComponent1 = new ComponentName(mContext, "component1");
         mComponent2 = new ComponentName(mContext, "component2");
         mPipBoundsState = new PipBoundsState(mContext);
+        mPipTransitionState = new PipTransitionState();
         mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState,
                 new PipSnapAlgorithm());
         mMainExecutor = new TestShellExecutor();
         mSpiedPipTaskOrganizer = spy(new PipTaskOrganizer(mContext,
-                mMockSyncTransactionQueue, mPipBoundsState,
+                mMockSyncTransactionQueue, mPipTransitionState, mPipBoundsState,
                 mPipBoundsAlgorithm, mMockPhonePipMenuController,
                 mMockPipAnimationController, mMockPipSurfaceTransactionHelper,
                 mMockPipTransitionController, mMockOptionalSplitScreen, mMockDisplayController,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java
index 56a0056..69ead3a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java
@@ -33,6 +33,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestRunningTaskInfoBuilder;
 import com.android.wm.shell.common.SyncTransactionQueue;
 
@@ -46,7 +47,7 @@
 /** Tests for {@link SideStage} */
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-public class SideStageTests {
+public class SideStageTests extends ShellTestCase {
     @Mock private ShellTaskOrganizer mTaskOrganizer;
     @Mock private StageTaskListener.StageListenerCallbacks mCallbacks;
     @Mock private SyncTransactionQueue mSyncQueue;
@@ -60,8 +61,8 @@
     public void setup() {
         MockitoAnnotations.initMocks(this);
         mRootTask = new TestRunningTaskInfoBuilder().build();
-        mSideStage = new SideStage(mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks, mSyncQueue,
-                mSurfaceSession);
+        mSideStage = new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks,
+                mSyncQueue, mSurfaceSession);
         mSideStage.onTaskAppeared(mRootTask, mRootLeash);
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index aca80f3..b6da868 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -102,7 +102,7 @@
         mMainStage = new MainStage(mTaskOrganizer, DEFAULT_DISPLAY, mock(
                 StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession);
         mMainStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface());
-        mSideStage = new SideStage(mTaskOrganizer, DEFAULT_DISPLAY, mock(
+        mSideStage = new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock(
                 StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession);
         mSideStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface());
         mStageCoordinator = new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY,
@@ -131,6 +131,7 @@
         mSideStage.onTaskAppeared(mSideChild, createMockSurface());
         boolean accepted = mStageCoordinator.startAnimation(transition, info,
                 mock(SurfaceControl.Transaction.class),
+                mock(SurfaceControl.Transaction.class),
                 mock(Transitions.TransitionFinishCallback.class));
         assertTrue(accepted);
 
@@ -168,6 +169,7 @@
         mSideStage.onTaskAppeared(newTask, createMockSurface());
         boolean accepted = mStageCoordinator.startAnimation(transition, info,
                 mock(SurfaceControl.Transaction.class),
+                mock(SurfaceControl.Transaction.class),
                 mock(Transitions.TransitionFinishCallback.class));
         assertFalse(accepted);
         assertTrue(mStageCoordinator.isSplitScreenVisible());
@@ -188,6 +190,7 @@
         mSideStage.onTaskVanished(newTask);
         accepted = mStageCoordinator.startAnimation(transition, info,
                 mock(SurfaceControl.Transaction.class),
+                mock(SurfaceControl.Transaction.class),
                 mock(Transitions.TransitionFinishCallback.class));
         assertFalse(accepted);
         assertTrue(mStageCoordinator.isSplitScreenVisible());
@@ -223,6 +226,7 @@
         mSideStage.onTaskVanished(mSideChild);
         mStageCoordinator.startAnimation(transition, info,
                 mock(SurfaceControl.Transaction.class),
+                mock(SurfaceControl.Transaction.class),
                 mock(Transitions.TransitionFinishCallback.class));
         assertFalse(mStageCoordinator.isSplitScreenVisible());
     }
@@ -244,6 +248,7 @@
         mSideStage.onTaskVanished(mSideChild);
         boolean accepted = mStageCoordinator.startAnimation(transition, info,
                 mock(SurfaceControl.Transaction.class),
+                mock(SurfaceControl.Transaction.class),
                 mock(Transitions.TransitionFinishCallback.class));
         assertTrue(accepted);
         assertFalse(mStageCoordinator.isSplitScreenVisible());
@@ -274,6 +279,7 @@
         mSideStage.onTaskVanished(mSideChild);
         boolean accepted = mStageCoordinator.startAnimation(transition, info,
                 mock(SurfaceControl.Transaction.class),
+                mock(SurfaceControl.Transaction.class),
                 mock(Transitions.TransitionFinishCallback.class));
         assertTrue(accepted);
         assertFalse(mStageCoordinator.isSplitScreenVisible());
@@ -298,6 +304,7 @@
         mSideStage.onTaskAppeared(mSideChild, createMockSurface());
         mStageCoordinator.startAnimation(enterTransit, enterInfo,
                 mock(SurfaceControl.Transaction.class),
+                mock(SurfaceControl.Transaction.class),
                 mock(Transitions.TransitionFinishCallback.class));
         mMainStage.activate(new Rect(0, 0, 100, 100), new WindowContainerTransaction());
     }
@@ -335,10 +342,11 @@
 
         @Override
         public void startAnimation(IBinder transition, TransitionInfo info,
-                SurfaceControl.Transaction t, IRemoteTransitionFinishedCallback finishCallback)
+                SurfaceControl.Transaction startTransaction,
+                IRemoteTransitionFinishedCallback finishCallback)
                 throws RemoteException {
             mCalled = true;
-            finishCallback.onTransitionFinished(mRemoteFinishWCT);
+            finishCallback.onTransitionFinished(mRemoteFinishWCT, null /* sct */);
         }
 
         @Override
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
index 90b5b37..1a30f16 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
@@ -21,11 +21,13 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assume.assumeFalse;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import android.app.ActivityManager;
+import android.os.SystemProperties;
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
 
@@ -52,6 +54,9 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public final class StageTaskListenerTests {
+    private static final boolean ENABLE_SHELL_TRANSITIONS =
+            SystemProperties.getBoolean("persist.debug.shell_transit", false);
+
     @Mock private ShellTaskOrganizer mTaskOrganizer;
     @Mock private StageTaskListener.StageListenerCallbacks mCallbacks;
     @Mock private SyncTransactionQueue mSyncQueue;
@@ -93,6 +98,8 @@
 
     @Test
     public void testChildTaskAppeared() {
+        // With shell transitions, the transition manages status changes, so skip this test.
+        assumeFalse(ENABLE_SHELL_TRANSITIONS);
         final ActivityManager.RunningTaskInfo childTask =
                 new TestRunningTaskInfoBuilder().setParentTaskId(mRootTask.taskId).build();
 
@@ -110,6 +117,8 @@
 
     @Test
     public void testTaskVanished() {
+        // With shell transitions, the transition manages status changes, so skip this test.
+        assumeFalse(ENABLE_SHELL_TRANSITIONS);
         final ActivityManager.RunningTaskInfo childTask =
                 new TestRunningTaskInfoBuilder().setParentTaskId(mRootTask.taskId).build();
         mStageTaskListener.mRootTaskInfo = mRootTask;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index 2d2ab2c..a2b1f64 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -127,11 +127,13 @@
         TestTransitionHandler testHandler = new TestTransitionHandler() {
             @Override
             public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
-                    @NonNull SurfaceControl.Transaction t,
+                    @NonNull SurfaceControl.Transaction startTransaction,
+                    @NonNull SurfaceControl.Transaction finishTransaction,
                     @NonNull Transitions.TransitionFinishCallback finishCallback) {
                 for (TransitionInfo.Change chg : info.getChanges()) {
                     if (chg.getMode() == TRANSIT_CHANGE) {
-                        return super.startAnimation(transition, info, t, finishCallback);
+                        return super.startAnimation(transition, info, startTransaction,
+                                finishTransaction, finishCallback);
                     }
                 }
                 return false;
@@ -211,7 +213,7 @@
                     SurfaceControl.Transaction t,
                     IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {
                 remoteCalled[0] = true;
-                finishCallback.onTransitionFinished(remoteFinishWCT);
+                finishCallback.onTransitionFinished(remoteFinishWCT, null /* sct */);
             }
 
             @Override
@@ -285,7 +287,7 @@
                     SurfaceControl.Transaction t,
                     IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {
                 remoteCalled[0] = true;
-                finishCallback.onTransitionFinished(null /* wct */);
+                finishCallback.onTransitionFinished(null /* wct */, null /* sct */);
             }
 
             @Override
@@ -332,7 +334,7 @@
                     SurfaceControl.Transaction t,
                     IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {
                 remoteCalled[0] = true;
-                finishCallback.onTransitionFinished(remoteFinishWCT);
+                finishCallback.onTransitionFinished(remoteFinishWCT, null /* sct */);
             }
 
             @Override
@@ -358,9 +360,11 @@
         oneShot.setTransition(transitToken);
         IBinder anotherToken = new Binder();
         assertFalse(oneShot.startAnimation(anotherToken, new TransitionInfo(transitType, 0),
-                mock(SurfaceControl.Transaction.class), testFinish));
+                mock(SurfaceControl.Transaction.class), mock(SurfaceControl.Transaction.class),
+                testFinish));
         assertTrue(oneShot.startAnimation(transitToken, new TransitionInfo(transitType, 0),
-                mock(SurfaceControl.Transaction.class), testFinish));
+                mock(SurfaceControl.Transaction.class), mock(SurfaceControl.Transaction.class),
+                testFinish));
     }
 
     @Test
@@ -477,7 +481,8 @@
 
         @Override
         public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
-                @NonNull SurfaceControl.Transaction t,
+                @NonNull SurfaceControl.Transaction startTransaction,
+                @NonNull SurfaceControl.Transaction finishTransaction,
                 @NonNull Transitions.TransitionFinishCallback finishCallback) {
             mFinishes.add(finishCallback);
             return true;
diff --git a/packages/SystemUI/res/layout/global_actions_change_panel.xml b/packages/SystemUI/res/layout/global_actions_change_panel.xml
deleted file mode 100644
index bc9c203..0000000
--- a/packages/SystemUI/res/layout/global_actions_change_panel.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2021 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content">
-    <TextView
-        android:id="@+id/global_actions_change_message"
-        android:layout_width="wrap_content"
-        android:visibility="gone"
-        android:layout_height="wrap_content"
-        android:text="@string/global_actions_change_description" />
-    <ImageView
-        android:id="@+id/global_actions_change_button"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"/>
-</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/global_actions_grid_lite.xml b/packages/SystemUI/res/layout/global_actions_grid_lite.xml
index 5588fd3..2430eec 100644
--- a/packages/SystemUI/res/layout/global_actions_grid_lite.xml
+++ b/packages/SystemUI/res/layout/global_actions_grid_lite.xml
@@ -13,12 +13,13 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<androidx.constraintlayout.widget.ConstraintLayout
+<LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:id="@+id/global_actions_container"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
+    android:orientation="vertical"
     android:gravity="center"
     android:layout_gravity="center">
   <com.android.systemui.globalactions.GlobalActionsLayoutLite
@@ -28,11 +29,8 @@
       android:orientation="vertical"
       android:clipChildren="false"
       android:clipToPadding="false"
-      app:layout_constraintBottom_toBottomOf="parent"
-      app:layout_constraintTop_toTopOf="parent"
-      app:layout_constraintStart_toStartOf="parent"
-      app:layout_constraintEnd_toEndOf="parent"
-      android:layout_weight="1">
+      android:background="@drawable/global_actions_lite_background"
+      android:padding="@dimen/global_actions_lite_padding">
     <androidx.constraintlayout.widget.ConstraintLayout
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
@@ -40,8 +38,6 @@
         android:gravity="center"
         android:translationZ="@dimen/global_actions_translate"
         android:orientation="horizontal"
-        android:background="@drawable/global_actions_lite_background"
-        android:padding="@dimen/global_actions_lite_padding"
         android:layoutDirection="ltr">
       <androidx.constraintlayout.helper.widget.Flow
           android:id="@+id/list_flow"
@@ -57,4 +53,4 @@
           app:flow_horizontalStyle="packed"/>
     </androidx.constraintlayout.widget.ConstraintLayout>
   </com.android.systemui.globalactions.GlobalActionsLayoutLite>
-</androidx.constraintlayout.widget.ConstraintLayout>
+</LinearLayout>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index f5d47ce..d722866 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -603,8 +603,6 @@
 
     <!-- Whether wallet view is shown in landscape / seascape orientations -->
     <bool name="global_actions_show_landscape_wallet_view">false</bool>
-    <!-- Whether global actions should show an informational message about changes in S -->
-    <bool name="global_actions_show_change_info">false</bool>
 
     <!-- Package name of the preferred system app to perform eSOS action -->
     <string name="config_preferredEmergencySosPackage" translatable="false"></string>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 18388a9..b5156b6 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1121,7 +1121,6 @@
     <dimen name="global_actions_button_padding">38dp</dimen>
     <dimen name="global_actions_corner_radius">28dp</dimen>
     <dimen name="global_actions_lite_padding">24dp</dimen>
-    <dimen name="global_actions_info_margin">32dp</dimen>
 
     <!-- The maximum offset in either direction that elements are moved horizontally to prevent
          burn-in on AOD. -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index c4e9ff8..5796c19 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2986,9 +2986,4 @@
 
     <!-- Content description for a chip in the status bar showing that the user is currently on a phone call. [CHAR LIMIT=NONE] -->
     <string name="ongoing_phone_call_content_description">Ongoing phone call</string>
-
-    <!-- Placeholder for string describing changes in global actions -->
-    <string name="global_actions_change_description" translatable="false"><xliff:g>%1$s</xliff:g></string>
-    <!-- URL for more information about changes in global actions -->
-    <string name="global_actions_change_url" translatable="false"></string>
 </resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
index 277b2e3..de9558e 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
@@ -77,8 +77,17 @@
     void onSplitScreenSecondaryBoundsChanged(in Rect bounds, in Rect insets) = 17;
 
     /**
-     * Sent IME status changes
+     * Sent when suggested rotation button could be shown
      */
-    void onImeWindowStatusChanged(int displayId, IBinder token, int vis, int backDisposition,
-                         boolean showImeSwitcher) = 18;
+    void onRotationProposal(int rotation, boolean isValid) = 18;
+
+    /**
+     * Sent when disable flags change
+     */
+    void disable(int displayId, int state1, int state2, boolean animate) = 19;
+
+    /**
+     * Sent when behavior changes. See WindowInsetsController#@Behavior
+     */
+    void onSystemBarAttributesChanged(int displayId, int behavior) = 20;
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
index 7dffc26..e33985d 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
@@ -16,13 +16,24 @@
 
 package com.android.systemui.shared.recents.utilities;
 
+import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT;
+import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.graphics.Color;
+import android.inputmethodservice.InputMethodService;
 import android.os.Handler;
 import android.os.Message;
+import android.util.DisplayMetrics;
+import android.view.Surface;
 
 /* Common code */
 public class Utilities {
 
+    private static final float TABLET_MIN_DPS = 600;
+
     /**
      * Posts a runnable on a handler at the front of the queue ignoring any sync barriers.
      */
@@ -31,6 +42,23 @@
         h.sendMessageAtFrontOfQueue(msg);
     }
 
+    public static boolean isRotationAnimationCCW(int from, int to) {
+        // All 180deg WM rotation animations are CCW, match that
+        if (from == Surface.ROTATION_0 && to == Surface.ROTATION_90) return false;
+        if (from == Surface.ROTATION_0 && to == Surface.ROTATION_180) return true; //180d so CCW
+        if (from == Surface.ROTATION_0 && to == Surface.ROTATION_270) return true;
+        if (from == Surface.ROTATION_90 && to == Surface.ROTATION_0) return true;
+        if (from == Surface.ROTATION_90 && to == Surface.ROTATION_180) return false;
+        if (from == Surface.ROTATION_90 && to == Surface.ROTATION_270) return true; //180d so CCW
+        if (from == Surface.ROTATION_180 && to == Surface.ROTATION_0) return true; //180d so CCW
+        if (from == Surface.ROTATION_180 && to == Surface.ROTATION_90) return true;
+        if (from == Surface.ROTATION_180 && to == Surface.ROTATION_270) return false;
+        if (from == Surface.ROTATION_270 && to == Surface.ROTATION_0) return false;
+        if (from == Surface.ROTATION_270 && to == Surface.ROTATION_90) return true; //180d so CCW
+        if (from == Surface.ROTATION_270 && to == Surface.ROTATION_180) return true;
+        return false; // Default
+    }
+
     /** Calculates the constrast between two colors, using the algorithm provided by the WCAG v2. */
     public static float computeContrastBetweenColors(int bg, int fg) {
         float bgR = Color.red(bg) / 255f;
@@ -58,4 +86,52 @@
     public static float clamp(float value, float min, float max) {
         return Math.max(min, Math.min(max, value));
     }
+
+    /**
+     * @return updated set of flags from InputMethodService based off {@param oldHints}
+     *          Leaves original hints unmodified
+     */
+    public static int calculateBackDispositionHints(int oldHints, int backDisposition,
+            boolean imeShown, boolean showImeSwitcher) {
+        int hints = oldHints;
+        switch (backDisposition) {
+            case InputMethodService.BACK_DISPOSITION_DEFAULT:
+            case InputMethodService.BACK_DISPOSITION_WILL_NOT_DISMISS:
+            case InputMethodService.BACK_DISPOSITION_WILL_DISMISS:
+                if (imeShown) {
+                    hints |= NAVIGATION_HINT_BACK_ALT;
+                } else {
+                    hints &= ~NAVIGATION_HINT_BACK_ALT;
+                }
+                break;
+            case InputMethodService.BACK_DISPOSITION_ADJUST_NOTHING:
+                hints &= ~NAVIGATION_HINT_BACK_ALT;
+                break;
+        }
+        if (showImeSwitcher) {
+            hints |= NAVIGATION_HINT_IME_SHOWN;
+        } else {
+            hints &= ~NAVIGATION_HINT_IME_SHOWN;
+        }
+
+        return hints;
+    }
+
+    /** See {@link #isTablet(Configuration, Context)} */
+    public static boolean isTablet(Context context) {
+        Configuration newConfig = context.getResources().getConfiguration();
+        return isTablet(newConfig, context);
+    }
+
+    /**
+     * @return whether or not {@param newConfig} represents that of a large screen device or not
+     */
+    public static boolean isTablet(Configuration newConfig, Context context) {
+        float density = Resources.getSystem().getDisplayMetrics().density;
+        int size = Math.min((int) (density * newConfig.screenWidthDp),
+                (int) (density* newConfig.screenHeightDp));
+        DisplayMetrics metrics = context.getResources().getDisplayMetrics();
+        float densityRatio = (float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT;
+        return (size / densityRatio) >= TABLET_MIN_DPS;
+    }
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/ViewRippler.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/ViewRippler.java
new file mode 100644
index 0000000..5581a1c
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/ViewRippler.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.recents.utilities;
+
+import android.view.View;
+
+/**
+ * Shows view ripples by toggling the provided Views "pressed" state.
+ * Ripples 4 times.
+ */
+public class ViewRippler {
+    private static final int RIPPLE_OFFSET_MS = 50;
+    private static final int RIPPLE_INTERVAL_MS = 2000;
+    private View mRoot;
+
+    public void start(View root) {
+        stop(); // Stop any pending ripple animations
+
+        mRoot = root;
+
+        // Schedule pending ripples, offset the 1st to avoid problems with visibility change
+        mRoot.postOnAnimationDelayed(mRipple, RIPPLE_OFFSET_MS);
+        mRoot.postOnAnimationDelayed(mRipple, RIPPLE_INTERVAL_MS);
+        mRoot.postOnAnimationDelayed(mRipple, 2 * RIPPLE_INTERVAL_MS);
+        mRoot.postOnAnimationDelayed(mRipple, 3 * RIPPLE_INTERVAL_MS);
+        mRoot.postOnAnimationDelayed(mRipple, 4 * RIPPLE_INTERVAL_MS);
+    }
+
+    public void stop() {
+        if (mRoot != null) mRoot.removeCallbacks(mRipple);
+    }
+
+    private final Runnable mRipple = new Runnable() {
+        @Override
+        public void run() { // Cause the ripple to fire via false presses
+            if (!mRoot.isAttachedToWindow()) return;
+            mRoot.setPressed(true /* pressed */);
+            mRoot.setPressed(false /* pressed */);
+        }
+    };
+}
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index c468e41..4663a9a 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -112,6 +112,10 @@
     public static final int SYSUI_STATE_IME_SHOWING = 1 << 18;
     // The window magnification is overlapped with system gesture insets at the bottom.
     public static final int SYSUI_STATE_MAGNIFICATION_OVERLAP = 1 << 19;
+    // ImeSwitcher is showing
+    public static final int SYSUI_STATE_IME_SWITCHER_SHOWING = 1 << 20;
+    // Device dozing/AOD state
+    public static final int SYSUI_STATE_DEVICE_DOZING = 1 << 21;
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({SYSUI_STATE_SCREEN_PINNING,
@@ -133,7 +137,9 @@
             SYSUI_STATE_ONE_HANDED_ACTIVE,
             SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY,
             SYSUI_STATE_IME_SHOWING,
-            SYSUI_STATE_MAGNIFICATION_OVERLAP
+            SYSUI_STATE_MAGNIFICATION_OVERLAP,
+            SYSUI_STATE_IME_SWITCHER_SHOWING,
+            SYSUI_STATE_DEVICE_DOZING
     })
     public @interface SystemUiStateFlags {}
 
@@ -162,6 +168,8 @@
                 ? "allow_gesture" : "");
         str.add((flags & SYSUI_STATE_IME_SHOWING) != 0 ? "ime_visible" : "");
         str.add((flags & SYSUI_STATE_MAGNIFICATION_OVERLAP) != 0 ? "magnification_overlap" : "");
+        str.add((flags & SYSUI_STATE_IME_SWITCHER_SHOWING) != 0 ? "ime_switcher_showing" : "");
+        str.add((flags & SYSUI_STATE_DEVICE_DOZING) != 0 ? "device_dozing" : "");
         return str.toString();
     }
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
index ee55bf0..025d7ef 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
@@ -69,7 +69,8 @@
         return mRemoteTransition;
     }
 
-    private static IRemoteAnimationRunner.Stub wrapRemoteAnimationRunner(
+    /** Wraps a RemoteAnimationRunnerCompat in an IRemoteAnimationRunner. */
+    public static IRemoteAnimationRunner.Stub wrapRemoteAnimationRunner(
             final RemoteAnimationRunnerCompat remoteAnimationAdapter) {
         return new IRemoteAnimationRunner.Stub() {
             @Override
@@ -260,7 +261,7 @@
                                 t.remove(leashMap.valueAt(i));
                             }
                             t.apply();
-                            finishCallback.onTransitionFinished(null /* wct */);
+                            finishCallback.onTransitionFinished(null /* wct */, null /* sct */);
                         } catch (RemoteException e) {
                             Log.e("ActivityOptionsCompat", "Failed to call app controlled animation"
                                     + " finished callback", e);
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
index 653d730..a77cc87 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
@@ -17,14 +17,17 @@
 package com.android.systemui.shared.system;
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.window.TransitionFilter.CONTAINER_ORDER_TOP;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
+import android.app.ActivityTaskManager;
 import android.graphics.Rect;
 import android.os.IBinder;
 import android.os.Parcelable;
@@ -73,7 +76,7 @@
                     IRemoteTransitionFinishedCallback finishedCallback) {
                 final Runnable finishAdapter = () ->  {
                     try {
-                        finishedCallback.onTransitionFinished(null /* wct */);
+                        finishedCallback.onTransitionFinished(null /* wct */, null /* sct */);
                     } catch (RemoteException e) {
                         Log.e(TAG, "Failed to call transition finished callback", e);
                     }
@@ -87,7 +90,7 @@
                     IRemoteTransitionFinishedCallback finishedCallback) {
                 final Runnable finishAdapter = () ->  {
                     try {
-                        finishedCallback.onTransitionFinished(null /* wct */);
+                        finishedCallback.onTransitionFinished(null /* wct */, null /* sct */);
                     } catch (RemoteException e) {
                         Log.e(TAG, "Failed to call transition finished callback", e);
                     }
@@ -119,6 +122,7 @@
                 // This transition is for opening recents, so recents is on-top. We want to draw
                 // the current going-away task on top of recents, though, so move it to front
                 WindowContainerToken pausingTask = null;
+                SurfaceControl pausingLeash = null;
                 for (int i = info.getChanges().size() - 1; i >= 0; --i) {
                     final TransitionInfo.Change change = info.getChanges().get(i);
                     if (change.getMode() == TRANSIT_CLOSE || change.getMode() == TRANSIT_TO_BACK) {
@@ -135,7 +139,7 @@
                 }
                 t.apply();
                 mRecentsSession.setup(controller, info, finishedCallback, pausingTask,
-                        leashMap);
+                        leashMap, mToken);
                 recents.onAnimationStart(mRecentsSession, apps, wallpapers, new Rect(0, 0, 0, 0),
                         new Rect());
             }
@@ -147,7 +151,7 @@
                 if (!mergeTarget.equals(mToken)) return;
                 if (!mRecentsSession.merge(info, t, recents)) return;
                 try {
-                    finishedCallback.onTransitionFinished(null /* wct */);
+                    finishedCallback.onTransitionFinished(null /* wct */, null /* sct */);
                 } catch (RemoteException e) {
                     Log.e(TAG, "Error merging transition.", e);
                 }
@@ -161,9 +165,13 @@
             mFilter = new TransitionFilter();
         }
         mFilter.mRequirements =
-                new TransitionFilter.Requirement[]{new TransitionFilter.Requirement()};
+                new TransitionFilter.Requirement[]{new TransitionFilter.Requirement(),
+                        new TransitionFilter.Requirement()};
         mFilter.mRequirements[0].mActivityType = ACTIVITY_TYPE_HOME;
         mFilter.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT};
+        mFilter.mRequirements[0].mOrder = CONTAINER_ORDER_TOP;
+        mFilter.mRequirements[1].mActivityType = ACTIVITY_TYPE_STANDARD;
+        mFilter.mRequirements[1].mModes = new int[]{TRANSIT_CLOSE, TRANSIT_TO_BACK};
     }
 
     /**
@@ -178,10 +186,11 @@
         private TransitionInfo mInfo = null;
         private SurfaceControl mOpeningLeash = null;
         private ArrayMap<SurfaceControl, SurfaceControl> mLeashMap = null;
+        private IBinder mTransition = null;
 
         void setup(RecentsAnimationControllerCompat wrapped, TransitionInfo info,
                 IRemoteTransitionFinishedCallback finishCB, WindowContainerToken pausingTask,
-                ArrayMap<SurfaceControl, SurfaceControl> leashMap) {
+                ArrayMap<SurfaceControl, SurfaceControl> leashMap, IBinder transition) {
             if (mInfo != null) {
                 throw new IllegalStateException("Trying to run a new recents animation while"
                         + " recents is already active.");
@@ -191,6 +200,7 @@
             mFinishCB = finishCB;
             mPausingTask = pausingTask;
             mLeashMap = leashMap;
+            mTransition = transition;
         }
 
         @SuppressLint("NewApi")
@@ -263,10 +273,13 @@
             try {
                 if (!toHome && mPausingTask != null && mOpeningLeash == null) {
                     // The gesture went back to opening the app rather than continuing with
-                    // recents, so end the transition by moving the app back to the top.
+                    // recents, so end the transition by moving the app back to the top (and also
+                    // re-showing it's task).
                     final WindowContainerTransaction wct = new WindowContainerTransaction();
                     wct.reorder(mPausingTask, true /* onTop */);
-                    mFinishCB.onTransitionFinished(wct);
+                    final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+                    t.show(mInfo.getChange(mPausingTask).getLeash());
+                    mFinishCB.onTransitionFinished(wct, t);
                 } else {
                     if (mOpeningLeash != null) {
                         // TODO: the launcher animation should handle this
@@ -275,7 +288,7 @@
                         t.setAlpha(mOpeningLeash, 1.f);
                         t.apply();
                     }
-                    mFinishCB.onTransitionFinished(null /* wct */);
+                    mFinishCB.onTransitionFinished(null /* wct */, null /* sct */);
                 }
             } catch (RemoteException e) {
                 Log.e("RemoteTransitionCompat", "Failed to call animation finish callback", e);
@@ -298,6 +311,7 @@
             mInfo = null;
             mOpeningLeash = null;
             mLeashMap = null;
+            mTransition = null;
         }
 
         @Override public void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot) {
@@ -318,6 +332,23 @@
         @Override public boolean removeTask(int taskId) {
             return mWrapped != null ? mWrapped.removeTask(taskId) : false;
         }
+
+        /**
+         * @see IRecentsAnimationController#detachNavigationBarFromApp
+         */
+        @Override public void detachNavigationBarFromApp(boolean moveHomeToTop) {
+            try {
+                ActivityTaskManager.getService().detachNavigationBarFromApp(mTransition);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to detach the navigation bar from app", e);
+            }
+        }
+
+        /**
+         * @see IRecentsAnimationController#animateNavigationBarToApp(long)
+         */
+        @Override public void animateNavigationBarToApp(long duration) {
+        }
     }
 
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index a5b2509..8f14cd8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -14,6 +14,9 @@
 import android.widget.FrameLayout;
 import android.widget.RelativeLayout;
 
+import androidx.annotation.IntDef;
+import androidx.annotation.VisibleForTesting;
+
 import com.android.internal.colorextraction.ColorExtractor;
 import com.android.keyguard.dagger.KeyguardStatusViewScope;
 import com.android.systemui.R;
@@ -22,6 +25,8 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Arrays;
 import java.util.TimeZone;
 
@@ -37,6 +42,13 @@
     private static final long CLOCK_IN_MILLIS = 200;
     private static final long SMARTSPACE_MOVE_MILLIS = 350;
 
+    @IntDef({LARGE, SMALL})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ClockSize { }
+
+    public static final int LARGE = 0;
+    public static final int SMALL = 1;
+
     /**
      * Optional/alternative clock injected via plugin.
      */
@@ -65,13 +77,13 @@
     private float mDarkAmount;
 
     /**
-     * Boolean value indicating if notifications are visible on lock screen. Use null to signify
-     * it is uninitialized.
+     * Indicates which clock is currently displayed - should be one of {@link ClockSize}.
+     * Use null to signify it is uninitialized.
      */
-    private Boolean mHasVisibleNotifications = null;
+    @ClockSize private Integer mDisplayedClockSize = null;
 
-    private AnimatorSet mClockInAnim = null;
-    private AnimatorSet mClockOutAnim = null;
+    @VisibleForTesting AnimatorSet mClockInAnim = null;
+    @VisibleForTesting AnimatorSet mClockOutAnim = null;
     private ObjectAnimator mSmartspaceAnim = null;
 
     /**
@@ -264,19 +276,17 @@
     }
 
     /**
-     * Based upon whether notifications are showing or not, display/hide the large clock and
-     * the smaller version.
+     * Display the desired clock and hide the other one
+     *
+     * @return true if desired clock appeared and false if it was already visible
      */
-    boolean willSwitchToLargeClock(boolean hasVisibleNotifications) {
-        if (mHasVisibleNotifications != null
-                && hasVisibleNotifications == mHasVisibleNotifications) {
+    boolean switchToClock(@ClockSize int clockSize) {
+        if (mDisplayedClockSize != null && clockSize == mDisplayedClockSize) {
             return false;
         }
-        boolean useLargeClock = !hasVisibleNotifications;
-        animateClockChange(useLargeClock);
-
-        mHasVisibleNotifications = hasVisibleNotifications;
-        return useLargeClock;
+        animateClockChange(clockSize == LARGE);
+        mDisplayedClockSize = clockSize;
+        return true;
     }
 
     public Paint getPaint() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 632919a..9e456cf 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -19,6 +19,8 @@
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
+import static com.android.keyguard.KeyguardClockSwitch.LARGE;
+
 import android.app.WallpaperManager;
 import android.text.TextUtils;
 import android.view.View;
@@ -245,10 +247,12 @@
     }
 
     /**
-     * Set whether or not the lock screen is showing notifications.
+     * Set which clock should be displayed on the keyguard. The other one will be automatically
+     * hidden.
      */
-    public void setHasVisibleNotifications(boolean hasVisibleNotifications) {
-        if (mView.willSwitchToLargeClock(hasVisibleNotifications)) {
+    public void displayClock(@KeyguardClockSwitch.ClockSize int clockSize) {
+        boolean appeared = mView.switchToClock(clockSize);
+        if (appeared && clockSize == LARGE) {
             mLargeClockViewController.animateAppear();
         }
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index ca4d73b..840e8c8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -42,7 +42,6 @@
 import android.view.WindowManager;
 import android.widget.FrameLayout;
 
-import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 import androidx.dynamicanimation.animation.DynamicAnimation;
 import androidx.dynamicanimation.animation.SpringAnimation;
@@ -104,7 +103,6 @@
 
     private boolean mIsSecurityViewLeftAligned = true;
     private boolean mOneHandedMode = false;
-    private SecurityMode mSecurityMode = SecurityMode.Invalid;
     private ViewPropertyAnimator mRunningOneHandedAnimator;
 
     private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback =
@@ -248,66 +246,47 @@
     }
 
     void onResume(SecurityMode securityMode, boolean faceAuthEnabled) {
-        mSecurityMode = securityMode;
         mSecurityViewFlipper.setWindowInsetsAnimationCallback(mWindowInsetsAnimationCallback);
         updateBiometricRetry(securityMode, faceAuthEnabled);
-
-        updateLayoutForSecurityMode(securityMode);
     }
 
-    void updateLayoutForSecurityMode(SecurityMode securityMode) {
-        mSecurityMode = securityMode;
-        mOneHandedMode = canUseOneHandedBouncer();
-
-        if (mOneHandedMode) {
-            mIsSecurityViewLeftAligned = isOneHandedKeyguardLeftAligned(mContext);
-        }
-
+    /**
+     * Sets whether this security container is in one handed mode. If so, it will measure its
+     * child SecurityViewFlipper in one half of the screen, and move it when tapping on the opposite
+     * side of the screen.
+     */
+    public void setOneHandedMode(boolean oneHandedMode) {
+        mOneHandedMode = oneHandedMode;
         updateSecurityViewGravity();
         updateSecurityViewLocation(false);
     }
 
-    /** Update keyguard position based on a tapped X coordinate. */
-    public void updateKeyguardPosition(float x) {
-        if (mOneHandedMode) {
-            moveBouncerForXCoordinate(x, /* animate= */false);
-        }
+    /** Returns whether this security container is in one-handed mode. */
+    public boolean isOneHandedMode() {
+        return mOneHandedMode;
     }
 
-    /** Return whether the one-handed keyguard should be enabled. */
-    private boolean canUseOneHandedBouncer() {
-        // Is it enabled?
-        if (!getResources().getBoolean(
-                com.android.internal.R.bool.config_enableDynamicKeyguardPositioning)) {
-            return false;
-        }
-
-        if (!KeyguardSecurityModel.isSecurityViewOneHanded(mSecurityMode)) {
-            return false;
-        }
-
-        return getResources().getBoolean(R.bool.can_use_one_handed_bouncer);
+    /**
+     * When in one-handed mode, sets if the inner SecurityViewFlipper should be aligned to the
+     * left-hand side of the screen or not, and whether to animate when moving between the two.
+     */
+    public void setOneHandedModeLeftAligned(boolean leftAligned, boolean animate) {
+        mIsSecurityViewLeftAligned = leftAligned;
+        updateSecurityViewLocation(animate);
     }
 
-    /** Read whether the one-handed keyguard should be on the left/right from settings. */
-    private boolean isOneHandedKeyguardLeftAligned(Context context) {
-        try {
-            return Settings.Global.getInt(context.getContentResolver(),
-                    Settings.Global.ONE_HANDED_KEYGUARD_SIDE)
-                    == Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT;
-        } catch (Settings.SettingNotFoundException ex) {
-            return true;
-        }
+    /** Returns whether the inner SecurityViewFlipper is left-aligned when in one-handed mode. */
+    public boolean isOneHandedModeLeftAligned() {
+        return mIsSecurityViewLeftAligned;
     }
 
     private void updateSecurityViewGravity() {
-        View securityView = findKeyguardSecurityView();
-
-        if (securityView == null) {
+        if (mSecurityViewFlipper == null) {
             return;
         }
 
-        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) securityView.getLayoutParams();
+        FrameLayout.LayoutParams lp =
+                (FrameLayout.LayoutParams) mSecurityViewFlipper.getLayoutParams();
 
         if (mOneHandedMode) {
             lp.gravity = Gravity.LEFT | Gravity.BOTTOM;
@@ -315,7 +294,7 @@
             lp.gravity = Gravity.CENTER_HORIZONTAL;
         }
 
-        securityView.setLayoutParams(lp);
+        mSecurityViewFlipper.setLayoutParams(lp);
     }
 
     /**
@@ -324,14 +303,12 @@
      * by the security view .
      */
     private void updateSecurityViewLocation(boolean animate) {
-        View securityView = findKeyguardSecurityView();
-
-        if (securityView == null) {
+        if (mSecurityViewFlipper == null) {
             return;
         }
 
         if (!mOneHandedMode) {
-            securityView.setTranslationX(0);
+            mSecurityViewFlipper.setTranslationX(0);
             return;
         }
 
@@ -343,7 +320,8 @@
         int targetTranslation = mIsSecurityViewLeftAligned ? 0 : (int) (getMeasuredWidth() / 2f);
 
         if (animate) {
-            mRunningOneHandedAnimator = securityView.animate().translationX(targetTranslation);
+            mRunningOneHandedAnimator =
+                    mSecurityViewFlipper.animate().translationX(targetTranslation);
             mRunningOneHandedAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
             mRunningOneHandedAnimator.setListener(new AnimatorListenerAdapter() {
                 @Override
@@ -355,27 +333,10 @@
             mRunningOneHandedAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
             mRunningOneHandedAnimator.start();
         } else {
-            securityView.setTranslationX(targetTranslation);
+            mSecurityViewFlipper.setTranslationX(targetTranslation);
         }
     }
 
-    @Nullable
-    private KeyguardSecurityViewFlipper findKeyguardSecurityView() {
-        for (int i = 0; i < getChildCount(); i++) {
-            View child = getChildAt(i);
-
-            if (isKeyguardSecurityView(child)) {
-                return (KeyguardSecurityViewFlipper) child;
-            }
-        }
-
-        return null;
-    }
-
-    private boolean isKeyguardSecurityView(View view) {
-        return view instanceof KeyguardSecurityViewFlipper;
-    }
-
     public void onPause() {
         if (mAlertDialog != null) {
             mAlertDialog.dismiss();
@@ -635,7 +596,7 @@
         for (int i = 0; i < getChildCount(); i++) {
             final View view = getChildAt(i);
             if (view.getVisibility() != GONE) {
-                if (mOneHandedMode && isKeyguardSecurityView(view)) {
+                if (mOneHandedMode && view == mSecurityViewFlipper) {
                     measureChildWithMargins(view, halfWidthMeasureSpec, 0,
                             heightMeasureSpec, 0);
                 } else {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index fde8213..cb5c6c3 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -32,6 +32,7 @@
 import android.content.res.Configuration;
 import android.metrics.LogMaker;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.util.Log;
 import android.util.Slog;
 import android.view.MotionEvent;
@@ -49,6 +50,8 @@
 import com.android.keyguard.dagger.KeyguardBouncerScope;
 import com.android.settingslib.utils.ThreadUtils;
 import com.android.systemui.Gefingerpoken;
+import com.android.systemui.R;
+import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -74,12 +77,14 @@
     private final KeyguardSecurityViewFlipperController mSecurityViewFlipperController;
     private final SecurityCallback mSecurityCallback;
     private final ConfigurationController mConfigurationController;
+    private final FalsingCollector mFalsingCollector;
 
     private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED;
 
     private SecurityMode mCurrentSecurityMode = SecurityMode.Invalid;
 
-    private final Gefingerpoken mGlobalTouchListener = new Gefingerpoken() {
+    @VisibleForTesting
+    final Gefingerpoken mGlobalTouchListener = new Gefingerpoken() {
         private MotionEvent mTouchDown;
         @Override
         public boolean onInterceptTouchEvent(MotionEvent ev) {
@@ -91,6 +96,17 @@
             // Do just a bit of our own falsing. People should only be tapping on the input, not
             // swiping.
             if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
+                // If we're in one handed mode, the user can tap on the opposite side of the screen
+                // to move the bouncer across. In that case, inhibit the falsing (otherwise the taps
+                // to move the bouncer to each screen side can end up closing it instead).
+                if (mView.isOneHandedMode()) {
+                    if ((mView.isOneHandedModeLeftAligned() && ev.getX() > mView.getWidth() / 2f)
+                            || (!mView.isOneHandedModeLeftAligned()
+                            && ev.getX() <= mView.getWidth() / 2f)) {
+                        mFalsingCollector.avoidGesture();
+                    }
+                }
+
                 if (mTouchDown != null) {
                     mTouchDown.recycle();
                     mTouchDown = null;
@@ -202,7 +218,8 @@
             KeyguardStateController keyguardStateController,
             SecurityCallback securityCallback,
             KeyguardSecurityViewFlipperController securityViewFlipperController,
-            ConfigurationController configurationController) {
+            ConfigurationController configurationController,
+            FalsingCollector falsingCollector) {
         super(view);
         mLockPatternUtils = lockPatternUtils;
         mUpdateMonitor = keyguardUpdateMonitor;
@@ -216,6 +233,7 @@
                 mKeyguardSecurityCallback);
         mConfigurationController = configurationController;
         mLastOrientation = getResources().getConfiguration().orientation;
+        mFalsingCollector = falsingCollector;
     }
 
     @Override
@@ -440,13 +458,49 @@
         if (newView != null) {
             newView.onResume(KeyguardSecurityView.VIEW_REVEALED);
             mSecurityViewFlipperController.show(newView);
-            mView.updateLayoutForSecurityMode(securityMode);
+            configureOneHandedMode();
         }
 
         mSecurityCallback.onSecurityModeChanged(
                 securityMode, newView != null && newView.needsInput());
     }
 
+    /** Read whether the one-handed keyguard should be on the left/right from settings. */
+    private boolean isOneHandedKeyguardLeftAligned() {
+        try {
+            return Settings.Global.getInt(mView.getContext().getContentResolver(),
+                    Settings.Global.ONE_HANDED_KEYGUARD_SIDE)
+                    == Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT;
+        } catch (Settings.SettingNotFoundException ex) {
+            return true;
+        }
+    }
+
+    private boolean canUseOneHandedBouncer() {
+        // Is it enabled?
+        if (!getResources().getBoolean(
+                com.android.internal.R.bool.config_enableDynamicKeyguardPositioning)) {
+            return false;
+        }
+
+        if (!KeyguardSecurityModel.isSecurityViewOneHanded(mCurrentSecurityMode)) {
+            return false;
+        }
+
+        return getResources().getBoolean(R.bool.can_use_one_handed_bouncer);
+    }
+
+    private void configureOneHandedMode() {
+        boolean oneHandedMode = canUseOneHandedBouncer();
+
+        mView.setOneHandedMode(oneHandedMode);
+
+        if (oneHandedMode) {
+            mView.setOneHandedModeLeftAligned(
+                    isOneHandedKeyguardLeftAligned(), /* animate= */false);
+        }
+    }
+
     public void reportFailedUnlockAttempt(int userId, int timeoutMs) {
         // +1 for this time
         final int failedAttempts = mLockPatternUtils.getCurrentFailedPasswordAttempts(userId) + 1;
@@ -511,13 +565,15 @@
         int newOrientation = getResources().getConfiguration().orientation;
         if (newOrientation != mLastOrientation) {
             mLastOrientation = newOrientation;
-            mView.updateLayoutForSecurityMode(mCurrentSecurityMode);
+            configureOneHandedMode();
         }
     }
 
     /** Update keyguard position based on a tapped X coordinate. */
     public void updateKeyguardPosition(float x) {
-        mView.updateKeyguardPosition(x);
+        if (mView.isOneHandedMode()) {
+            mView.setOneHandedModeLeftAligned(x <= mView.getWidth() / 2f, false);
+        }
     }
 
     static class Factory {
@@ -533,6 +589,7 @@
         private final KeyguardStateController mKeyguardStateController;
         private final KeyguardSecurityViewFlipperController mSecurityViewFlipperController;
         private final ConfigurationController mConfigurationController;
+        private final FalsingCollector mFalsingCollector;
 
         @Inject
         Factory(KeyguardSecurityContainer view,
@@ -545,7 +602,8 @@
                 UiEventLogger uiEventLogger,
                 KeyguardStateController keyguardStateController,
                 KeyguardSecurityViewFlipperController securityViewFlipperController,
-                ConfigurationController configurationController) {
+                ConfigurationController configurationController,
+                FalsingCollector falsingCollector) {
             mView = view;
             mAdminSecondaryLockScreenControllerFactory = adminSecondaryLockScreenControllerFactory;
             mLockPatternUtils = lockPatternUtils;
@@ -556,6 +614,7 @@
             mKeyguardStateController = keyguardStateController;
             mSecurityViewFlipperController = securityViewFlipperController;
             mConfigurationController = configurationController;
+            mFalsingCollector = falsingCollector;
         }
 
         public KeyguardSecurityContainerController create(
@@ -564,7 +623,7 @@
                     mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils,
                     mKeyguardUpdateMonitor, mKeyguardSecurityModel, mMetricsLogger, mUiEventLogger,
                     mKeyguardStateController, securityCallback, mSecurityViewFlipperController,
-                    mConfigurationController);
+                    mConfigurationController, mFalsingCollector);
         }
 
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 2096c31..2158401 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -19,6 +19,7 @@
 import android.graphics.Rect;
 import android.util.Slog;
 
+import com.android.keyguard.KeyguardClockSwitch.ClockSize;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
@@ -126,10 +127,11 @@
     }
 
     /**
-     * Set whether or not the lock screen is showing notifications.
+     * Set which clock should be displayed on the keyguard. The other one will be automatically
+     * hidden.
      */
-    public void setHasVisibleNotifications(boolean hasVisibleNotifications) {
-        mKeyguardClockSwitchController.setHasVisibleNotifications(hasVisibleNotifications);
+    public void displayClock(@ClockSize int clockSize) {
+        mKeyguardClockSwitchController.displayClock(clockSize);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index bd000b2..1344be6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -2785,6 +2785,20 @@
         updateBiometricListeningState();
     }
 
+    /** Notifies that the occluded state changed. */
+    public void onKeyguardOccludedChanged(boolean occluded) {
+        Assert.isMainThread();
+        if (DEBUG) {
+            Log.d(TAG, "onKeyguardOccludedChanged(" + occluded + ")");
+        }
+        for (int i = 0; i < mCallbacks.size(); i++) {
+            KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+            if (cb != null) {
+                cb.onKeyguardOccludedChanged(occluded);
+            }
+        }
+    }
+
     /**
      * Handle {@link #MSG_KEYGUARD_RESET}
      */
@@ -2967,6 +2981,7 @@
         callback.onPhoneStateChanged(mPhoneState);
         callback.onRefreshCarrierInfo();
         callback.onClockVisibilityChanged();
+        callback.onKeyguardOccludedChanged(mKeyguardOccluded);
         callback.onKeyguardVisibilityChangedRaw(mKeyguardIsVisible);
         callback.onTelephonyCapable(mTelephonyCapable);
         callback.onLockScreenModeChanged(mLockScreenMode);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 9849a7e..6aa7aaa 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -88,6 +88,12 @@
      */
     public void onKeyguardVisibilityChanged(boolean showing) { }
 
+    /**
+     * Called when the keyguard occluded state changes.
+     * @param occluded Indicates if the keyguard is now occluded.
+     */
+    public void onKeyguardOccludedChanged(boolean occluded) { }
+
     public void onKeyguardVisibilityChangedRaw(boolean showing) {
         final long now = SystemClock.elapsedRealtime();
         if (showing == mShowing
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index ca2c034..fa56453 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -161,7 +161,7 @@
         mNotificationShadeController = notificationShadeController;
         // Saving in instance variable since to prevent GC since
         // NotificationShadeWindowController.registerCallback() only keeps weak references.
-        mNotificationShadeCallback = (keyguardShowing, keyguardOccluded, bouncerShowing) ->
+        mNotificationShadeCallback = (keyguardShowing, keyguardOccluded, bouncerShowing, mDozing) ->
                 registerOrUnregisterDismissNotificationShadeAction();
         mStatusBar = statusBar;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 717c715..a51e3fc 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -28,6 +28,7 @@
 import android.annotation.UiContext;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Insets;
 import android.graphics.Matrix;
@@ -35,6 +36,7 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.Region;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.RemoteException;
@@ -76,6 +78,8 @@
         MirrorWindowControl.MirrorWindowDelegate, MagnificationGestureDetector.OnGestureListener {
 
     private static final String TAG = "WindowMagnificationController";
+    @SuppressWarnings("isloggabletaglength")
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG) || Build.IS_DEBUGGABLE;
     // Delay to avoid updating state description too frequently.
     private static final int UPDATE_STATE_DESCRIPTION_DELAY_MS = 100;
     // It should be consistent with the value defined in WindowMagnificationGestureHandler.
@@ -158,14 +162,15 @@
         mRotation = display.getRotation();
 
         mWm = context.getSystemService(WindowManager.class);
-        mWindowBounds = mWm.getCurrentWindowMetrics().getBounds();
+        mWindowBounds = new Rect(mWm.getCurrentWindowMetrics().getBounds());
 
         mResources = mContext.getResources();
         mScale = mResources.getInteger(R.integer.magnification_default_scale);
         mBounceEffectDuration = mResources.getInteger(
                 com.android.internal.R.integer.config_shortAnimTime);
         updateDimensions();
-        setInitialStartBounds();
+        setMagnificationFrameWith(mWindowBounds, mWindowBounds.width() / 2,
+                mWindowBounds.height() / 2);
         computeBounceAnimationScale();
 
         mMirrorWindowControl = mirrorWindowControl;
@@ -286,18 +291,59 @@
      * @param configDiff a bit mask of the differences between the configurations
      */
     void onConfigurationChanged(int configDiff) {
+        if (DEBUG) {
+            Log.d(TAG, "onConfigurationChanged = " + Configuration.configurationDiffToString(
+                    configDiff));
+        }
+        if ((configDiff & ActivityInfo.CONFIG_ORIENTATION) != 0) {
+            onRotate();
+        }
+
+        if ((configDiff & ActivityInfo.CONFIG_LOCALE) != 0) {
+            updateAccessibilityWindowTitleIfNeeded();
+        }
+
+        boolean reCreateWindow = false;
         if ((configDiff & ActivityInfo.CONFIG_DENSITY) != 0) {
             updateDimensions();
             computeBounceAnimationScale();
-            if (isWindowVisible()) {
-                deleteWindowMagnification();
-                enableWindowMagnification(Float.NaN, Float.NaN, Float.NaN);
-            }
-        } else if ((configDiff & ActivityInfo.CONFIG_ORIENTATION) != 0) {
-            onRotate();
-        } else if ((configDiff & ActivityInfo.CONFIG_LOCALE) != 0) {
-            updateAccessibilityWindowTitleIfNeeded();
+            reCreateWindow = true;
         }
+
+        if ((configDiff & ActivityInfo.CONFIG_SCREEN_SIZE) != 0) {
+            reCreateWindow |= handleScreenSizeChanged();
+        }
+
+        // Recreate the window again to correct the window appearance due to density or
+        // window size changed not caused by rotation.
+        if (isWindowVisible() && reCreateWindow) {
+            deleteWindowMagnification();
+            enableWindowMagnification(Float.NaN, Float.NaN, Float.NaN);
+        }
+    }
+
+    /**
+     * Calculates the magnification frame if the window bounds is changed.
+     * Note that the orientation also changes the wind bounds, so it should be handled first.
+     *
+     * @return {@code true} if the magnification frame is changed with the new window bounds.
+     */
+    private boolean handleScreenSizeChanged() {
+        final Rect oldWindowBounds = new Rect(mWindowBounds);
+        final Rect currentWindowBounds = mWm.getCurrentWindowMetrics().getBounds();
+
+        if (currentWindowBounds.equals(oldWindowBounds)) {
+            if (DEBUG) {
+                Log.d(TAG, "updateMagnificationFrame -- window bounds is not changed");
+            }
+            return false;
+        }
+        mWindowBounds.set(currentWindowBounds);
+        final float newCenterX = (getCenterX()) * mWindowBounds.width() / oldWindowBounds.width();
+        final float newCenterY = (getCenterY()) * mWindowBounds.height() / oldWindowBounds.height();
+        setMagnificationFrameWith(mWindowBounds, (int) newCenterX, (int) newCenterY);
+        calculateMagnificationFrameBoundary();
+        return true;
     }
 
     private void updateSystemUIStateIfNeeded() {
@@ -311,30 +357,42 @@
         mWm.updateViewLayout(mMirrorView, params);
     }
 
-    /** Handles MirrorWindow position when the device rotation changed. */
+    /**
+     * Keep MirrorWindow position on the screen unchanged when device rotates 90° clockwise or
+     * anti-clockwise.
+     */
     private void onRotate() {
         final Display display = mContext.getDisplay();
         final int oldRotation = mRotation;
-        mWindowBounds = mWm.getCurrentWindowMetrics().getBounds();
-
-        setMagnificationFrameBoundary();
         mRotation = display.getRotation();
+        final int rotationDegree = getDegreeFromRotation(mRotation, oldRotation);
+        if (rotationDegree == 0 || rotationDegree == 180) {
+            Log.w(TAG, "onRotate -- rotate with the device. skip it");
+            return;
+        }
+        final Rect currentWindowBounds = new Rect(mWm.getCurrentWindowMetrics().getBounds());
+        if (currentWindowBounds.width() != mWindowBounds.height()
+                || currentWindowBounds.height() != mWindowBounds.width()) {
+            Log.w(TAG, "onRotate -- unexpected window height/width");
+            return;
+        }
+
+        mWindowBounds.set(currentWindowBounds);
+
+        calculateMagnificationFrameBoundary();
 
         if (!isWindowVisible()) {
             return;
         }
         // Keep MirrorWindow position on the screen unchanged when device rotates 90°
         // clockwise or anti-clockwise.
-        final int rotationDegree = getDegreeFromRotation(mRotation, oldRotation);
+
         final Matrix matrix = new Matrix();
         matrix.setRotate(rotationDegree);
         if (rotationDegree == 90) {
             matrix.postTranslate(mWindowBounds.width(), 0);
         } else if (rotationDegree == 270) {
             matrix.postTranslate(0, mWindowBounds.height());
-        } else {
-            Log.w(TAG, "Invalid rotation change. " + rotationDegree);
-            return;
         }
         // The rect of MirrorView is going to be transformed.
         LayoutParams params =
@@ -440,12 +498,12 @@
         }
     }
 
-    private void setInitialStartBounds() {
+    private void setMagnificationFrameWith(Rect windowBounds, int centerX, int centerY) {
         // Sets the initial frame area for the mirror and places it in the center of the display.
-        final int initSize = Math.min(mWindowBounds.width(), mWindowBounds.height()) / 2
+        final int initSize = Math.min(windowBounds.width(), windowBounds.height()) / 2
                 + 2 * mMirrorSurfaceMargin;
-        final int initX = mWindowBounds.width() / 2 - initSize / 2;
-        final int initY = mWindowBounds.height() / 2 - initSize / 2;
+        final int initX = centerX - initSize / 2;
+        final int initY = centerY - initSize / 2;
         mMagnificationFrame.set(initX, initY, initX + initSize, initY + initSize);
     }
 
@@ -553,7 +611,7 @@
         mSourceBounds.set(left, top, right, bottom);
     }
 
-    private void setMagnificationFrameBoundary() {
+    private void calculateMagnificationFrameBoundary() {
         // Calculates width and height for magnification frame could exceed out the screen.
         // TODO : re-calculating again when scale is changed.
         // The half width of magnification frame.
@@ -644,7 +702,7 @@
                 : centerY - mMagnificationFrame.exactCenterY();
         mScale = Float.isNaN(scale) ? mScale : scale;
 
-        setMagnificationFrameBoundary();
+        calculateMagnificationFrameBoundary();
         updateMagnificationFramePosition((int) offsetX, (int) offsetY);
         if (!isWindowVisible()) {
             createMirrorWindow();
@@ -764,6 +822,8 @@
         pw.println("      mOverlapWithGestureInsets:" + mOverlapWithGestureInsets);
         pw.println("      mScale:" + mScale);
         pw.println("      mMirrorViewBounds:" + (isWindowVisible() ? mMirrorViewBounds : "empty"));
+        pw.println("      mSourceBounds:"
+                 + (isWindowVisible() ? mSourceBounds : "empty"));
         pw.println("      mSystemGestureTop:" + mSystemGestureTop);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
index c97a30e..83fa994 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
@@ -60,9 +60,11 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.model.SysUiState;
+import com.android.systemui.navigationbar.NavigationBarA11yHelper;
 import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.navigationbar.NavigationBarOverlayController;
 import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.navigationbar.TaskbarDelegate;
 import com.android.systemui.plugins.PluginInitializerImpl;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.ReduceBrightColorsController;
@@ -234,6 +236,8 @@
             UiEventLogger uiEventLogger,
             NavigationBarOverlayController navBarOverlayController,
             ConfigurationController configurationController,
+            NavigationBarA11yHelper navigationBarA11yHelper,
+            TaskbarDelegate taskbarDelegate,
             UserTracker userTracker) {
         return new NavigationBarController(context,
                 windowManager,
@@ -261,6 +265,8 @@
                 uiEventLogger,
                 navBarOverlayController,
                 configurationController,
+                navigationBarA11yHelper,
+                taskbarDelegate,
                 userTracker);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index bc4ced4..98fb3c9 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -199,7 +199,6 @@
                 iWindowManager,
                 backgroundExecutor,
                 uiEventLogger,
-                null,
                 ringerModeTracker,
                 sysUiState,
                 handler,
@@ -338,8 +337,7 @@
             super(context, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions,
                     adapter, overflowAdapter, sysuiColorExtractor, statusBarService,
                     notificationShadeWindowController, sysuiState, onRotateCallback,
-                    keyguardShowing, powerAdapter, uiEventLogger, null,
-                    statusBar);
+                    keyguardShowing, powerAdapter, uiEventLogger, statusBar);
             mWalletFactory = walletFactory;
 
             // Update window attributes
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 5acb303..2afce71 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -190,7 +190,6 @@
     private final MetricsLogger mMetricsLogger;
     private final UiEventLogger mUiEventLogger;
     private final SysUiState mSysUiState;
-    private final GlobalActionsInfoProvider mInfoProvider;
 
     // Used for RingerModeTracker
     private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this);
@@ -333,10 +332,9 @@
             IWindowManager iWindowManager,
             @Background Executor backgroundExecutor,
             UiEventLogger uiEventLogger,
-            GlobalActionsInfoProvider infoProvider,
             RingerModeTracker ringerModeTracker,
             SysUiState sysUiState,
-            @Main Handler handler,
+             @Main Handler handler,
             PackageManager packageManager,
             StatusBar statusBar) {
         mContext = context;
@@ -358,7 +356,6 @@
         mTelecomManager = telecomManager;
         mMetricsLogger = metricsLogger;
         mUiEventLogger = uiEventLogger;
-        mInfoProvider = infoProvider;
         mSysuiColorExtractor = colorExtractor;
         mStatusBarService = statusBarService;
         mNotificationShadeWindowController = notificationShadeWindowController;
@@ -653,7 +650,7 @@
                 mAdapter, mOverflowAdapter, mSysuiColorExtractor,
                 mStatusBarService, mNotificationShadeWindowController,
                 mSysUiState, this::onRotate, mKeyguardShowing, mPowerAdapter, mUiEventLogger,
-                mInfoProvider, mStatusBar);
+                mStatusBar);
 
         dialog.setOnDismissListener(this);
         dialog.setOnShowListener(this);
@@ -2124,7 +2121,6 @@
         private Dialog mPowerOptionsDialog;
         protected final Runnable mOnRotateCallback;
         private UiEventLogger mUiEventLogger;
-        private GlobalActionsInfoProvider mInfoProvider;
         private GestureDetector mGestureDetector;
         private StatusBar mStatusBar;
 
@@ -2178,7 +2174,7 @@
                 NotificationShadeWindowController notificationShadeWindowController,
                 SysUiState sysuiState, Runnable onRotateCallback, boolean keyguardShowing,
                 MyPowerOptionsAdapter powerAdapter, UiEventLogger uiEventLogger,
-                @Nullable GlobalActionsInfoProvider infoProvider, StatusBar statusBar) {
+                StatusBar statusBar) {
             super(context, themeRes);
             mContext = context;
             mAdapter = adapter;
@@ -2191,7 +2187,6 @@
             mOnRotateCallback = onRotateCallback;
             mKeyguardShowing = keyguardShowing;
             mUiEventLogger = uiEventLogger;
-            mInfoProvider = infoProvider;
             mStatusBar = statusBar;
 
             mGestureDetector = new GestureDetector(mContext, mGestureListener);
@@ -2309,10 +2304,6 @@
                 mBackgroundDrawable = new ScrimDrawable();
                 mScrimAlpha = 1.0f;
             }
-
-            if (mInfoProvider != null && mInfoProvider.shouldShowMessage()) {
-                mInfoProvider.addPanel(mContext, mContainer, mAdapter.getCount(), () -> dismiss());
-            }
         }
 
         protected void fixNavBarClipping() {
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsInfoProvider.kt b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsInfoProvider.kt
deleted file mode 100644
index 25837e3..0000000
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsInfoProvider.kt
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.globalactions
-
-import android.app.PendingIntent
-import android.content.Context
-import android.content.Intent
-import android.content.res.Configuration
-import android.net.Uri
-import android.service.quickaccesswallet.QuickAccessWalletClient
-import android.util.Log
-import android.view.LayoutInflater
-import android.view.ViewGroup
-import android.widget.TextView
-import com.android.systemui.R
-import com.android.systemui.controls.controller.ControlsController
-import com.android.systemui.plugins.ActivityStarter
-import javax.inject.Inject
-
-private const val TAG = "GlobalActionsInfo"
-
-/** Maximum number of times to show change info message  */
-private const val MAX_VIEW_COUNT = 3
-
-/** Maximum number of buttons allowed in landscape before this panel does not fit */
-private const val MAX_BUTTONS_LANDSCAPE = 4
-
-private const val PREFERENCE = "global_actions_info_prefs"
-private const val KEY_VIEW_COUNT = "view_count"
-
-class GlobalActionsInfoProvider @Inject constructor(
-    private val context: Context,
-    private val walletClient: QuickAccessWalletClient,
-    private val controlsController: ControlsController,
-    private val activityStarter: ActivityStarter
-) {
-
-    private var pendingIntent: PendingIntent
-
-    init {
-        val url = context.resources.getString(R.string.global_actions_change_url)
-        val intent = Intent(Intent.ACTION_VIEW).apply {
-            setData(Uri.parse(url))
-            addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-        }
-        pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
-    }
-
-    fun addPanel(context: Context, parent: ViewGroup, nActions: Int, dismissParent: Runnable) {
-        // This panel does not fit on landscape with two rows of buttons showing,
-        // so skip adding the panel (and incrementing view counT) in that case
-        val isLandscape =
-                context.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
-        if (isLandscape && nActions > MAX_BUTTONS_LANDSCAPE) {
-            return
-        }
-
-        val view = LayoutInflater.from(context).inflate(R.layout.global_actions_change_panel,
-                parent, false)
-
-        val walletTitle = walletClient.serviceLabel ?: context.getString(R.string.wallet_title)
-        val message = view.findViewById<TextView>(R.id.global_actions_change_message)
-        message?.setText(context.getString(R.string.global_actions_change_description, walletTitle))
-
-        view.setOnClickListener { _ ->
-            dismissParent.run()
-            activityStarter.postStartActivityDismissingKeyguard(pendingIntent)
-        }
-        parent.addView(view, 0) // Add to top
-        incrementViewCount()
-    }
-
-    fun shouldShowMessage(): Boolean {
-        // This is only relevant for some devices
-        val isEligible = context.resources.getBoolean(
-                R.bool.global_actions_show_change_info)
-        if (!isEligible) {
-            return false
-        }
-
-        val sharedPrefs = context.getSharedPreferences(PREFERENCE, Context.MODE_PRIVATE)
-
-        // Only show to users who previously had these items set up
-        val viewCount = if (sharedPrefs.contains(KEY_VIEW_COUNT) || hadContent()) {
-            sharedPrefs.getInt(KEY_VIEW_COUNT, 0)
-        } else {
-            -1
-        }
-
-        // Limit number of times this is displayed
-        return viewCount > -1 && viewCount < MAX_VIEW_COUNT
-    }
-
-    private fun hadContent(): Boolean {
-        // Check whether user would have seen content in the power menu that has now moved
-        val hadControls = controlsController.getFavorites().size > 0
-        val hadCards = walletClient.isWalletFeatureAvailable
-        Log.d(TAG, "Previously had controls $hadControls, cards $hadCards")
-        return hadControls || hadCards
-    }
-
-    private fun incrementViewCount() {
-        val sharedPrefs = context.getSharedPreferences(PREFERENCE, Context.MODE_PRIVATE)
-        val count = sharedPrefs.getInt(KEY_VIEW_COUNT, 0)
-        sharedPrefs.edit().putInt(KEY_VIEW_COUNT, count + 1).apply()
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 26f38dd..90afec6a 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -23,10 +23,12 @@
 import static android.app.StatusBarManager.WindowType;
 import static android.app.StatusBarManager.WindowVisibleState;
 import static android.app.StatusBarManager.windowStateToString;
+import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
 import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.containsType;
+import static android.view.ViewRootImpl.INSETS_LAYOUT_GENERALIZATION;
 import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_NAVIGATION_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS;
@@ -40,10 +42,12 @@
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.HOME_BUTTON_LONG_PRESS_DURATION_MS;
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NAV_BAR_HANDLE_FORCE_OPAQUE;
 import static com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
+import static com.android.systemui.shared.recents.utilities.Utilities.isTablet;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
@@ -54,9 +58,7 @@
 import static com.android.systemui.statusbar.phone.StatusBar.DEBUG_WINDOW_STATE;
 import static com.android.systemui.statusbar.phone.StatusBar.dumpBarTransitions;
 
-import android.accessibilityservice.AccessibilityServiceInfo;
 import android.annotation.IdRes;
-import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.app.IActivityTaskManager;
@@ -68,6 +70,7 @@
 import android.content.IntentFilter;
 import android.content.res.Configuration;
 import android.database.ContentObserver;
+import android.graphics.Insets;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.graphics.RectF;
@@ -89,6 +92,7 @@
 import android.view.Gravity;
 import android.view.HapticFeedbackConstants;
 import android.view.IWindowManager;
+import android.view.InsetsState;
 import android.view.InsetsState.InternalInsetsType;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
@@ -101,7 +105,6 @@
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener;
 import android.view.inputmethod.InputMethodManager;
 
 import androidx.annotation.VisibleForTesting;
@@ -130,6 +133,7 @@
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.shared.recents.utilities.Utilities;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.AutoHideUiElement;
@@ -151,7 +155,6 @@
 import com.android.wm.shell.pip.Pip;
 
 import java.io.PrintWriter;
-import java.util.List;
 import java.util.Locale;
 import java.util.Optional;
 import java.util.function.Consumer;
@@ -162,8 +165,7 @@
  * Contains logic for a navigation bar view.
  */
 public class NavigationBar implements View.OnAttachStateChangeListener,
-        Callbacks, NavigationModeController.ModeChangedListener,
-        AccessibilityButtonModeObserver.ModeChangedListener {
+        Callbacks, NavigationModeController.ModeChangedListener {
 
     public static final String TAG = "NavigationBar";
     private static final boolean DEBUG = false;
@@ -180,7 +182,6 @@
     private final Context mContext;
     private final WindowManager mWindowManager;
     private final AccessibilityManager mAccessibilityManager;
-    private final AccessibilityManagerWrapper mAccessibilityManagerWrapper;
     private final DeviceProvisionedController mDeviceProvisionedController;
     private final StatusBarStateController mStatusBarStateController;
     private final MetricsLogger mMetricsLogger;
@@ -201,11 +202,13 @@
     private final Handler mHandler;
     private final NavigationBarOverlayController mNavbarOverlayController;
     private final UiEventLogger mUiEventLogger;
+    private final NavigationBarA11yHelper mNavigationBarA11yHelper;
     private final UserTracker mUserTracker;
     private final NotificationShadeDepthController mNotificationShadeDepthController;
 
     private Bundle mSavedState;
     private NavigationBarView mNavigationBarView;
+    private NavigationBarFrame mFrame;
 
     private @WindowVisibleState int mNavigationBarWindowState = WINDOW_STATE_SHOWING;
 
@@ -483,11 +486,11 @@
             @Main Handler mainHandler,
             NavigationBarOverlayController navbarOverlayController,
             UiEventLogger uiEventLogger,
+            NavigationBarA11yHelper navigationBarA11yHelper,
             UserTracker userTracker) {
         mContext = context;
         mWindowManager = windowManager;
         mAccessibilityManager = accessibilityManager;
-        mAccessibilityManagerWrapper = accessibilityManagerWrapper;
         mDeviceProvisionedController = deviceProvisionedController;
         mStatusBarStateController = statusBarStateController;
         mMetricsLogger = metricsLogger;
@@ -508,11 +511,11 @@
         mHandler = mainHandler;
         mNavbarOverlayController = navbarOverlayController;
         mUiEventLogger = uiEventLogger;
+        mNavigationBarA11yHelper = navigationBarA11yHelper;
         mUserTracker = userTracker;
         mNotificationShadeDepthController = notificationShadeDepthController;
 
         mNavBarMode = mNavigationModeController.addListener(this);
-        mAccessibilityButtonModeObserver.addListener(this);
     }
 
     public NavigationBarView getView() {
@@ -520,34 +523,17 @@
     }
 
     public View createView(Bundle savedState) {
-        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
-                WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT,
-                WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
-                WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
-                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
-                        | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
-                        | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
-                        | WindowManager.LayoutParams.FLAG_SLIPPERY,
-                PixelFormat.TRANSLUCENT);
-        lp.token = new Binder();
-        lp.accessibilityTitle = mContext.getString(R.string.nav_bar);
-        lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
-        lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-        lp.windowAnimations = 0;
-        lp.setTitle("NavigationBar" + mContext.getDisplayId());
-        lp.setFitInsetsTypes(0 /* types */);
-        lp.setTrustedOverlay();
-
-        NavigationBarFrame frame = (NavigationBarFrame) LayoutInflater.from(mContext).inflate(
+        mFrame = (NavigationBarFrame) LayoutInflater.from(mContext).inflate(
                 R.layout.navigation_bar_window, null);
-        View barView = LayoutInflater.from(frame.getContext()).inflate(
-                R.layout.navigation_bar, frame);
+        View barView = LayoutInflater.from(mFrame.getContext()).inflate(
+                R.layout.navigation_bar, mFrame);
         barView.addOnAttachStateChangeListener(this);
         mNavigationBarView = barView.findViewById(R.id.navigation_bar_view);
 
         if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + barView);
-        mContext.getSystemService(WindowManager.class).addView(frame, lp);
+        mContext.getSystemService(WindowManager.class).addView(mFrame,
+                getBarLayoutParams(mContext.getResources().getConfiguration().windowConfiguration
+                        .getRotation()));
         mDisplayId = mContext.getDisplayId();
         mIsOnDefaultDisplay = mDisplayId == DEFAULT_DISPLAY;
 
@@ -605,9 +591,8 @@
         mContext.getSystemService(WindowManager.class).removeViewImmediate(
                 mNavigationBarView.getRootView());
         mNavigationModeController.removeListener(this);
-        mAccessibilityButtonModeObserver.removeListener(this);
 
-        mAccessibilityManagerWrapper.removeCallback(mAccessibilityListener);
+        mNavigationBarA11yHelper.removeA11yEventListener(mAccessibilityListener);
         mContentResolver.unregisterContentObserver(mAssistContentObserver);
         mDeviceProvisionedController.removeCallback(mUserSetupListener);
         mNotificationShadeDepthController.removeListener(mDepthListener);
@@ -629,7 +614,7 @@
         mNavigationBarView.setWindowVisible(isNavBarWindowVisible());
         mNavigationBarView.setBehavior(mBehavior);
 
-        mAccessibilityManagerWrapper.addCallback(mAccessibilityListener);
+        mNavigationBarA11yHelper.registerA11yEventListener(mAccessibilityListener);
 
         mSplitScreenOptional.ifPresent(mNavigationBarView::registerDockedListener);
         mPipOptional.ifPresent(mNavigationBarView::registerPipExclusionBoundsChangeListener);
@@ -704,6 +689,7 @@
         mHandler.removeCallbacks(mAutoDim);
         mHandler.removeCallbacks(mOnVariableDurationHomeLongClick);
         mHandler.removeCallbacks(mEnableLayoutTransitions);
+        mFrame = null;
         mNavigationBarView = null;
         mOrientationHandle = null;
     }
@@ -722,6 +708,7 @@
      * Called when a non-reloading configuration change happens and we need to update.
      */
     public void onConfigurationChanged(Configuration newConfig) {
+        final int rotation = newConfig.windowConfiguration.getRotation();
         final Locale locale = mContext.getResources().getConfiguration().locale;
         final int ld = TextUtils.getLayoutDirectionFromLocale(locale);
         if (!locale.equals(mLocale) || ld != mLayoutDirection) {
@@ -735,9 +722,8 @@
             refreshLayout(ld);
         }
 
-        repositionNavigationBar();
+        repositionNavigationBar(rotation);
         if (canShowSecondaryHandle()) {
-            int rotation = newConfig.windowConfiguration.getRotation();
             if (rotation != mCurrentRotation) {
                 mCurrentRotation = rotation;
                 orientSecondaryHomeHandle();
@@ -889,30 +875,15 @@
             return;
         }
         boolean imeShown = (vis & InputMethodService.IME_VISIBLE) != 0;
-        int hints = mNavigationIconHints;
-        switch (backDisposition) {
-            case InputMethodService.BACK_DISPOSITION_DEFAULT:
-            case InputMethodService.BACK_DISPOSITION_WILL_NOT_DISMISS:
-            case InputMethodService.BACK_DISPOSITION_WILL_DISMISS:
-                if (imeShown) {
-                    hints |= NAVIGATION_HINT_BACK_ALT;
-                } else {
-                    hints &= ~NAVIGATION_HINT_BACK_ALT;
-                }
-                break;
-            case InputMethodService.BACK_DISPOSITION_ADJUST_NOTHING:
-                hints &= ~NAVIGATION_HINT_BACK_ALT;
-                break;
-        }
-        if (showImeSwitcher) {
-            hints |= NAVIGATION_HINT_IME_SHOWN;
-        } else {
-            hints &= ~NAVIGATION_HINT_IME_SHOWN;
-        }
+        int hints = Utilities.calculateBackDispositionHints(mNavigationIconHints, backDisposition,
+                imeShown, showImeSwitcher);
         if (hints == mNavigationIconHints) return;
 
         mNavigationIconHints = hints;
-        mNavigationBarView.setNavigationIconHints(hints);
+        if (!isTablet(mContext)) {
+            // All IME functions handled by launcher via Sysui flags for large screen
+            mNavigationBarView.setNavigationIconHints(hints);
+        }
         checkBarModes();
         updateSystemUiStateFlags(-1);
     }
@@ -984,7 +955,7 @@
     @Override
     public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
             AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-            @Behavior int behavior, boolean isFullscreen) {
+            @Behavior int behavior, InsetsState requestedState, String packageName) {
         if (displayId != mDisplayId) {
             return;
         }
@@ -1120,13 +1091,12 @@
                 || (mDisabledFlags1 & StatusBarManager.DISABLE_SEARCH) != 0;
     }
 
-    private void repositionNavigationBar() {
-        if (!mNavigationBarView.isAttachedToWindow()) return;
+    private void repositionNavigationBar(int rotation) {
+        if (mNavigationBarView == null || !mNavigationBarView.isAttachedToWindow()) return;
 
         prepareNavigationBarView();
 
-        mWindowManager.updateViewLayout((View) mNavigationBarView.getParent(),
-                ((View) mNavigationBarView.getParent()).getLayoutParams());
+        mWindowManager.updateViewLayout(mFrame, getBarLayoutParams(rotation));
     }
 
     private void updateScreenPinningGestures() {
@@ -1168,7 +1138,7 @@
         ButtonDispatcher accessibilityButton = mNavigationBarView.getAccessibilityButton();
         accessibilityButton.setOnClickListener(this::onAccessibilityClick);
         accessibilityButton.setOnLongClickListener(this::onAccessibilityLongClick);
-        updateAccessibilityServicesState(mAccessibilityManager);
+        updateAccessibilityServicesState();
 
         ButtonDispatcher imeSwitcherButton = mNavigationBarView.getImeSwitchButton();
         imeSwitcherButton.setOnClickListener(this::onImeSwitcherClick);
@@ -1389,9 +1359,8 @@
         return true;
     }
 
-    void updateAccessibilityServicesState(AccessibilityManager accessibilityManager) {
-        boolean[] feedbackEnabled = new boolean[1];
-        int a11yFlags = getA11yButtonState(feedbackEnabled);
+    void updateAccessibilityServicesState() {
+        int a11yFlags = mNavigationBarA11yHelper.getA11yButtonState();
 
         boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
         boolean longClickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
@@ -1410,7 +1379,7 @@
 
     public void updateSystemUiStateFlags(int a11yFlags) {
         if (a11yFlags < 0) {
-            a11yFlags = getA11yButtonState(null);
+            a11yFlags = mNavigationBarA11yHelper.getA11yButtonState();
         }
         boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
         boolean longClickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
@@ -1420,6 +1389,8 @@
                 .setFlag(SYSUI_STATE_NAV_BAR_HIDDEN, !isNavBarWindowVisible())
                 .setFlag(SYSUI_STATE_IME_SHOWING,
                         (mNavigationIconHints & NAVIGATION_HINT_BACK_ALT) != 0)
+                .setFlag(SYSUI_STATE_IME_SWITCHER_SHOWING,
+                        (mNavigationIconHints & NAVIGATION_HINT_IME_SHOWN) != 0)
                 .setFlag(SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY,
                         allowSystemGestureIgnoringBarVisibility())
                 .commitUpdate(mDisplayId);
@@ -1435,45 +1406,6 @@
         }
     }
 
-    /**
-     * Returns the system UI flags corresponding the the current accessibility button state
-     *
-     * @param outFeedbackEnabled if non-null, sets it to true if accessibility feedback is enabled.
-     */
-    public int getA11yButtonState(@Nullable boolean[] outFeedbackEnabled) {
-        boolean feedbackEnabled = false;
-        // AccessibilityManagerService resolves services for the current user since the local
-        // AccessibilityManager is created from a Context with the INTERACT_ACROSS_USERS permission
-        final List<AccessibilityServiceInfo> services =
-                mAccessibilityManager.getEnabledAccessibilityServiceList(
-                        AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
-        final List<String> a11yButtonTargets =
-                mAccessibilityManager.getAccessibilityShortcutTargets(
-                        AccessibilityManager.ACCESSIBILITY_BUTTON);
-        final int requestingServices = a11yButtonTargets.size();
-        for (int i = services.size() - 1; i >= 0; --i) {
-            AccessibilityServiceInfo info = services.get(i);
-            if (info.feedbackType != 0 && info.feedbackType !=
-                    AccessibilityServiceInfo.FEEDBACK_GENERIC) {
-                feedbackEnabled = true;
-            }
-        }
-
-        if (outFeedbackEnabled != null) {
-            outFeedbackEnabled[0] = feedbackEnabled;
-        }
-
-        // If accessibility button is floating menu mode, click and long click state should be
-        // disabled.
-        if (mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode()
-                == ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU) {
-            return 0;
-        }
-
-        return (requestingServices >= 1 ? SYSUI_STATE_A11Y_BUTTON_CLICKABLE : 0)
-                | (requestingServices >= 2 ? SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE : 0);
-    }
-
     private void updateAssistantEntrypoints() {
         mAssistantAvailable = mAssistManagerLazy.get()
                 .getAssistInfoForUser(UserHandle.USER_CURRENT) != null;
@@ -1575,11 +1507,6 @@
         }
     }
 
-    @Override
-    public void onAccessibilityButtonModeChanged(int mode) {
-        updateAccessibilityServicesState(mAccessibilityManager);
-    }
-
     public void disableAnimationsDuringHide(long delay) {
         mNavigationBarView.setLayoutTransitionsEnabled(false);
         mHandler.postDelayed(mEnableLayoutTransitions,
@@ -1604,22 +1531,110 @@
         mNavigationBarView.getBarTransitions().finishAnimations();
     }
 
-    private final AccessibilityServicesStateChangeListener mAccessibilityListener =
+    private final NavigationBarA11yHelper.NavA11yEventListener mAccessibilityListener =
             this::updateAccessibilityServicesState;
 
+    private WindowManager.LayoutParams getBarLayoutParams(int rotation) {
+        WindowManager.LayoutParams lp = getBarLayoutParamsForRotation(rotation);
+        lp.paramsForRotation = new WindowManager.LayoutParams[4];
+        for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) {
+            lp.paramsForRotation[rot] = getBarLayoutParamsForRotation(rot);
+        }
+        return lp;
+    }
+
+    private WindowManager.LayoutParams getBarLayoutParamsForRotation(int rotation) {
+        int width = WindowManager.LayoutParams.MATCH_PARENT;
+        int height = WindowManager.LayoutParams.MATCH_PARENT;
+        int insetsHeight = -1;
+        int gravity = Gravity.BOTTOM;
+        if (INSETS_LAYOUT_GENERALIZATION) {
+            boolean navBarCanMove = true;
+            if (mWindowManager != null && mWindowManager.getCurrentWindowMetrics() != null) {
+                Rect displaySize = mWindowManager.getCurrentWindowMetrics().getBounds();
+                navBarCanMove = displaySize.width() != displaySize.height()
+                        && mContext.getResources().getBoolean(
+                        com.android.internal.R.bool.config_navBarCanMove);
+            }
+            if (!navBarCanMove) {
+                height = mContext.getResources().getDimensionPixelSize(
+                        com.android.internal.R.dimen.navigation_bar_frame_height);
+                insetsHeight = mContext.getResources().getDimensionPixelSize(
+                        com.android.internal.R.dimen.navigation_bar_height);
+            } else {
+                switch (rotation) {
+                    case ROTATION_UNDEFINED:
+                    case Surface.ROTATION_0:
+                    case Surface.ROTATION_180:
+                        height = mContext.getResources().getDimensionPixelSize(
+                                com.android.internal.R.dimen.navigation_bar_frame_height);
+                        insetsHeight = mContext.getResources().getDimensionPixelSize(
+                                com.android.internal.R.dimen.navigation_bar_height);
+                        break;
+                    case Surface.ROTATION_90:
+                        gravity = Gravity.RIGHT;
+                        width = mContext.getResources().getDimensionPixelSize(
+                                com.android.internal.R.dimen.navigation_bar_width);
+                        break;
+                    case Surface.ROTATION_270:
+                        gravity = Gravity.LEFT;
+                        width = mContext.getResources().getDimensionPixelSize(
+                                com.android.internal.R.dimen.navigation_bar_width);
+                        break;
+                }
+            }
+        }
+        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+                width,
+                height,
+                WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
+                WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
+                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                        | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+                        | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
+                        | WindowManager.LayoutParams.FLAG_SLIPPERY,
+                PixelFormat.TRANSLUCENT);
+        if (INSETS_LAYOUT_GENERALIZATION) {
+            lp.gravity = gravity;
+            if (insetsHeight != -1) {
+                lp.providedInternalInsets = Insets.of(0, height - insetsHeight, 0, 0);
+            } else {
+                lp.providedInternalInsets = Insets.NONE;
+            }
+        }
+        lp.token = new Binder();
+        lp.accessibilityTitle = mContext.getString(R.string.nav_bar);
+        lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
+        lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+        lp.windowAnimations = 0;
+        lp.setTitle("NavigationBar" + mContext.getDisplayId());
+        lp.setFitInsetsTypes(0 /* types */);
+        lp.setTrustedOverlay();
+        return lp;
+    }
+
     private boolean canShowSecondaryHandle() {
         return mNavBarMode == NAV_BAR_MODE_GESTURAL && mOrientationHandle != null;
     }
 
     private final Consumer<Integer> mRotationWatcher = rotation -> {
-        if (mNavigationBarView.needsReorient(rotation)) {
-            repositionNavigationBar();
+        if (mNavigationBarView != null
+                && mNavigationBarView.needsReorient(rotation)) {
+            repositionNavigationBar(rotation);
         }
     };
 
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
+            // This receiver is unregistered when the view is detached, but on devices with multiple
+            // displays, it can sometimes still receive an ACTION_SCREEN_ON/ACTION_SCREEN_OFF on
+            // display switch, after it was detached, so this null check ensures no crash in that
+            // scenario.
+            if (mNavigationBarView == null) {
+                return;
+            }
             String action = intent.getAction();
             if (Intent.ACTION_SCREEN_OFF.equals(action)
                     || Intent.ACTION_SCREEN_ON.equals(action)) {
@@ -1628,7 +1643,7 @@
             }
             if (Intent.ACTION_USER_SWITCHED.equals(action)) {
                 // The accessibility settings may be different for the new user
-                updateAccessibilityServicesState(mAccessibilityManager);
+                updateAccessibilityServicesState();
             }
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarA11yHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarA11yHelper.java
new file mode 100644
index 0000000..13e6d8b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarA11yHelper.java
@@ -0,0 +1,90 @@
+package com.android.systemui.navigationbar;
+
+import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
+
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
+
+import android.view.accessibility.AccessibilityManager;
+
+import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.inject.Inject;
+
+/**
+ * Extracts shared elements of a11y necessary between navbar and taskbar delegate
+ */
+@SysUISingleton
+public final class NavigationBarA11yHelper implements
+        AccessibilityButtonModeObserver.ModeChangedListener {
+    private final AccessibilityManager mAccessibilityManager;
+    private final AccessibilityButtonModeObserver mAccessibilityButtonModeObserver;
+    private final List<NavA11yEventListener> mA11yEventListeners = new ArrayList<>();
+
+    @Inject
+    public NavigationBarA11yHelper(AccessibilityManager accessibilityManager,
+            AccessibilityManagerWrapper accessibilityManagerWrapper,
+            AccessibilityButtonModeObserver accessibilityButtonModeObserver) {
+        mAccessibilityManager = accessibilityManager;
+        accessibilityManagerWrapper.addCallback(
+                accessibilityManager1 -> NavigationBarA11yHelper.this.dispatchEventUpdate());
+        mAccessibilityButtonModeObserver = accessibilityButtonModeObserver;
+
+        mAccessibilityButtonModeObserver.addListener(this);
+    }
+
+    public void registerA11yEventListener(NavA11yEventListener listener) {
+        mA11yEventListeners.add(listener);
+    }
+
+    public void removeA11yEventListener(NavA11yEventListener listener) {
+        mA11yEventListeners.remove(listener);
+    }
+
+    private void dispatchEventUpdate() {
+        for (NavA11yEventListener listener : mA11yEventListeners) {
+            listener.updateAccessibilityServicesState();
+        }
+    }
+
+    @Override
+    public void onAccessibilityButtonModeChanged(int mode) {
+        dispatchEventUpdate();
+    }
+
+    /**
+     * See {@link QuickStepContract#SYSUI_STATE_A11Y_BUTTON_CLICKABLE} and
+     * {@link QuickStepContract#SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE}
+     *
+     * @return the a11y button clickable and long_clickable states, or 0 if there is no
+     *         a11y button in the navbar
+     */
+    public int getA11yButtonState() {
+        // AccessibilityManagerService resolves services for the current user since the local
+        // AccessibilityManager is created from a Context with the INTERACT_ACROSS_USERS permission
+        final List<String> a11yButtonTargets =
+                mAccessibilityManager.getAccessibilityShortcutTargets(
+                        AccessibilityManager.ACCESSIBILITY_BUTTON);
+        final int requestingServices = a11yButtonTargets.size();
+
+        // If accessibility button is floating menu mode, click and long click state should be
+        // disabled.
+        if (mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode()
+                == ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU) {
+            return 0;
+        }
+
+        return (requestingServices >= 1 ? SYSUI_STATE_A11Y_BUTTON_CLICKABLE : 0)
+                | (requestingServices >= 2 ? SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE : 0);
+    }
+
+    public interface NavA11yEventListener {
+        void updateAccessibilityServicesState();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index b9e9240..6344c59 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -19,16 +19,15 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
 
+import static com.android.systemui.shared.recents.utilities.Utilities.isTablet;
+
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
-import android.content.res.Resources;
 import android.hardware.display.DisplayManager;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.RemoteException;
-import android.os.SystemProperties;
-import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.Display;
@@ -86,8 +85,6 @@
         ConfigurationController.ConfigurationListener,
         NavigationModeController.ModeChangedListener, Dumpable {
 
-    private static final float TABLET_MIN_DPS = 600;
-
     private static final String TAG = NavigationBarController.class.getSimpleName();
 
     private final Context mContext;
@@ -113,6 +110,7 @@
     private final SystemActions mSystemActions;
     private final UiEventLogger mUiEventLogger;
     private final Handler mHandler;
+    private final NavigationBarA11yHelper mNavigationBarA11yHelper;
     private final DisplayManager mDisplayManager;
     private final NavigationBarOverlayController mNavBarOverlayController;
     private final TaskbarDelegate mTaskbarDelegate;
@@ -157,6 +155,8 @@
             UiEventLogger uiEventLogger,
             NavigationBarOverlayController navBarOverlayController,
             ConfigurationController configurationController,
+            NavigationBarA11yHelper navigationBarA11yHelper,
+            TaskbarDelegate taskbarDelegate,
             UserTracker userTracker) {
         mContext = context;
         mWindowManager = windowManager;
@@ -182,6 +182,7 @@
         mSystemActions = systemActions;
         mUiEventLogger = uiEventLogger;
         mHandler = mainHandler;
+        mNavigationBarA11yHelper = navigationBarA11yHelper;
         mDisplayManager = mContext.getSystemService(DisplayManager.class);
         commandQueue.addCallback(this);
         configurationController.addCallback(this);
@@ -189,15 +190,17 @@
         mNavBarOverlayController = navBarOverlayController;
         mNavMode = mNavigationModeController.addListener(this);
         mNavigationModeController.addListener(this);
-        mTaskbarDelegate = new TaskbarDelegate(mOverviewProxyService);
-        mIsTablet = isTablet(mContext.getResources().getConfiguration());
+        mTaskbarDelegate = taskbarDelegate;
+        mTaskbarDelegate.setOverviewProxyService(overviewProxyService,
+                navigationBarA11yHelper, mSysUiFlagsContainer);
+        mIsTablet = isTablet(mContext);
         mUserTracker = userTracker;
     }
 
     @Override
     public void onConfigChanged(Configuration newConfig) {
         boolean isOldConfigTablet = mIsTablet;
-        mIsTablet = isTablet(newConfig);
+        mIsTablet = isTablet(newConfig, mContext);
         boolean largeScreenChanged = mIsTablet != isOldConfigTablet;
         // If we folded/unfolded while in 3 button, show navbar in folded state, hide in unfolded
         if (largeScreenChanged && updateNavbarForTaskbar()) {
@@ -237,25 +240,28 @@
         });
     }
 
-    /**
-     * @return {@code true} if navbar was added/removed, false otherwise
-     */
-    public boolean updateNavbarForTaskbar() {
-        if (!isThreeButtonTaskbarFlagEnabled()) {
-            return false;
+    /** @see #initializeTaskbarIfNecessary() */
+    private boolean updateNavbarForTaskbar() {
+        boolean taskbarShown = initializeTaskbarIfNecessary();
+        if (!taskbarShown && mNavigationBars.get(mContext.getDisplayId()) == null) {
+            createNavigationBar(mContext.getDisplay(), null, null);
         }
+        return taskbarShown;
+    }
 
-        if (mIsTablet && mNavMode == NAV_BAR_MODE_3BUTTON) {
+    /** @return {@code true} if taskbar is enabled, false otherwise */
+    private boolean initializeTaskbarIfNecessary() {
+        boolean isShowingTaskbar = mIsTablet && mNavMode == NAV_BAR_MODE_3BUTTON;
+        if (isShowingTaskbar) {
             // Remove navigation bar when taskbar is showing, currently only for 3 button mode
             removeNavigationBar(mContext.getDisplayId());
             mCommandQueue.addCallback(mTaskbarDelegate);
-        } else if (mNavigationBars.get(mContext.getDisplayId()) == null) {
-            // Add navigation bar after taskbar goes away
-            createNavigationBar(mContext.getDisplay(), null, null);
+            mTaskbarDelegate.init(mContext.getDisplayId());
+        } else {
             mCommandQueue.removeCallback(mTaskbarDelegate);
+            mTaskbarDelegate.destroy();
         }
-
-        return true;
+        return isShowingTaskbar;
     }
 
     @Override
@@ -266,7 +272,7 @@
     @Override
     public void onDisplayReady(int displayId) {
         Display display = mDisplayManager.getDisplay(displayId);
-        mIsTablet = isTablet(mContext.getResources().getConfiguration());
+        mIsTablet = isTablet(mContext);
         createNavigationBar(display, null /* savedState */, null /* result */);
     }
 
@@ -302,7 +308,7 @@
      */
     public void createNavigationBars(final boolean includeDefaultDisplay,
             RegisterStatusBarResult result) {
-        if (updateNavbarForTaskbar()) {
+        if (initializeTaskbarIfNecessary()) {
             return;
         }
 
@@ -326,7 +332,7 @@
             return;
         }
 
-        if (isThreeButtonTaskbarEnabled()) {
+        if (mIsTablet && mNavMode == NAV_BAR_MODE_3BUTTON) {
             return;
         }
 
@@ -371,6 +377,7 @@
                 mHandler,
                 mNavBarOverlayController,
                 mUiEventLogger,
+                mNavigationBarA11yHelper,
                 mUserTracker);
         mNavigationBars.put(displayId, navBar);
 
@@ -462,24 +469,6 @@
         return mNavigationBars.get(DEFAULT_DISPLAY);
     }
 
-    private boolean isThreeButtonTaskbarEnabled() {
-        return mIsTablet && mNavMode == NAV_BAR_MODE_3BUTTON &&
-                isThreeButtonTaskbarFlagEnabled();
-    }
-
-    private boolean isThreeButtonTaskbarFlagEnabled() {
-        return SystemProperties.getBoolean("persist.debug.taskbar_three_button", false);
-    }
-
-    private boolean isTablet(Configuration newConfig) {
-        float density = Resources.getSystem().getDisplayMetrics().density;
-        int size = Math.min((int) (density * newConfig.screenWidthDp),
-                (int) (density* newConfig.screenHeightDp));
-        DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
-        float densityRatio = (float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT;
-        return (size / densityRatio) >= TABLET_MIN_DPS;
-    }
-
     @Override
     public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
         for (int i = 0; i < mNavigationBars.size(); i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
index 649ac87..a5b7911 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
@@ -47,6 +47,8 @@
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.navigationbar.buttons.KeyButtonDrawable;
+import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.recents.utilities.ViewRippler;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
@@ -311,7 +313,7 @@
 
         // Prepare to show the navbar icon by updating the icon style to change anim params
         mLastRotationSuggestion = rotation; // Remember rotation for click
-        final boolean rotationCCW = isRotationAnimationCCW(windowRotation, rotation);
+        final boolean rotationCCW = Utilities.isRotationAnimationCCW(windowRotation, rotation);
         if (windowRotation == Surface.ROTATION_0 || windowRotation == Surface.ROTATION_180) {
             mIconResId = rotationCCW
                     ? R.drawable.ic_sysbar_rotate_button_ccw_start_90
@@ -431,23 +433,6 @@
         return rotation == NATURAL_ROTATION;
     }
 
-    private boolean isRotationAnimationCCW(int from, int to) {
-        // All 180deg WM rotation animations are CCW, match that
-        if (from == Surface.ROTATION_0 && to == Surface.ROTATION_90) return false;
-        if (from == Surface.ROTATION_0 && to == Surface.ROTATION_180) return true; //180d so CCW
-        if (from == Surface.ROTATION_0 && to == Surface.ROTATION_270) return true;
-        if (from == Surface.ROTATION_90 && to == Surface.ROTATION_0) return true;
-        if (from == Surface.ROTATION_90 && to == Surface.ROTATION_180) return false;
-        if (from == Surface.ROTATION_90 && to == Surface.ROTATION_270) return true; //180d so CCW
-        if (from == Surface.ROTATION_180 && to == Surface.ROTATION_0) return true; //180d so CCW
-        if (from == Surface.ROTATION_180 && to == Surface.ROTATION_90) return true;
-        if (from == Surface.ROTATION_180 && to == Surface.ROTATION_270) return false;
-        if (from == Surface.ROTATION_270 && to == Surface.ROTATION_0) return false;
-        if (from == Surface.ROTATION_270 && to == Surface.ROTATION_90) return true; //180d so CCW
-        if (from == Surface.ROTATION_270 && to == Surface.ROTATION_180) return true;
-        return false; // Default
-    }
-
     private void rescheduleRotationTimeout(final boolean reasonHover) {
         // May be called due to a new rotation proposal or a change in hover state
         if (reasonHover) {
@@ -520,38 +505,6 @@
         }
     }
 
-    private class ViewRippler {
-        private static final int RIPPLE_OFFSET_MS = 50;
-        private static final int RIPPLE_INTERVAL_MS = 2000;
-        private View mRoot;
-
-        public void start(View root) {
-            stop(); // Stop any pending ripple animations
-
-            mRoot = root;
-
-            // Schedule pending ripples, offset the 1st to avoid problems with visibility change
-            mRoot.postOnAnimationDelayed(mRipple, RIPPLE_OFFSET_MS);
-            mRoot.postOnAnimationDelayed(mRipple, RIPPLE_INTERVAL_MS);
-            mRoot.postOnAnimationDelayed(mRipple, 2 * RIPPLE_INTERVAL_MS);
-            mRoot.postOnAnimationDelayed(mRipple, 3 * RIPPLE_INTERVAL_MS);
-            mRoot.postOnAnimationDelayed(mRipple, 4 * RIPPLE_INTERVAL_MS);
-        }
-
-        public void stop() {
-            if (mRoot != null) mRoot.removeCallbacks(mRipple);
-        }
-
-        private final Runnable mRipple = new Runnable() {
-            @Override
-            public void run() { // Cause the ripple to fire via false presses
-                if (!mRoot.isAttachedToWindow()) return;
-                mRoot.setPressed(true /* pressed */);
-                mRoot.setPressed(false /* pressed */);
-            }
-        };
-    }
-
     enum RotationButtonEvent implements UiEventLogger.UiEventEnum {
         @UiEvent(doc = "The rotation button was shown")
         ROTATION_SUGGESTION_SHOWN(206),
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 03147d8..1d44146 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -16,23 +16,100 @@
 
 package com.android.systemui.navigationbar;
 
-import android.os.IBinder;
+import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT;
+import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
 
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
+
+import android.inputmethodservice.InputMethodService;
+import android.os.IBinder;
+import android.view.InsetsState;
+
+import com.android.internal.view.AppearanceRegion;
+import com.android.systemui.model.SysUiState;
 import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.shared.recents.utilities.Utilities;
 import com.android.systemui.statusbar.CommandQueue;
 
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+@Singleton
 public class TaskbarDelegate implements CommandQueue.Callbacks {
 
-    private final OverviewProxyService mOverviewProxyService;
+    private OverviewProxyService mOverviewProxyService;
+    private NavigationBarA11yHelper mNavigationBarA11yHelper;
+    private SysUiState mSysUiState;
+    private int mDisplayId;
+    private int mNavigationIconHints;
+    private final NavigationBarA11yHelper.NavA11yEventListener mNavA11yEventListener =
+            this::updateSysuiFlags;
+    @Inject
+    public TaskbarDelegate() { /* no-op */ }
 
-    public TaskbarDelegate(OverviewProxyService overviewProxyService) {
+    public void setOverviewProxyService(OverviewProxyService overviewProxyService,
+            NavigationBarA11yHelper navigationBarA11yHelper,
+            SysUiState sysUiState) {
+        // TODO: adding this in the ctor results in a dagger dependency cycle :(
         mOverviewProxyService = overviewProxyService;
+        mNavigationBarA11yHelper = navigationBarA11yHelper;
+        mSysUiState = sysUiState;
+    }
+
+    public void destroy() {
+        mNavigationBarA11yHelper.removeA11yEventListener(mNavA11yEventListener);
+    }
+
+    public void init(int displayId) {
+        mDisplayId = displayId;
+        mNavigationBarA11yHelper.registerA11yEventListener(mNavA11yEventListener);
+        // Set initial state for any listeners
+        updateSysuiFlags();
+    }
+
+    private void updateSysuiFlags() {
+        int a11yFlags = mNavigationBarA11yHelper.getA11yButtonState();
+        boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
+        boolean longClickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
+
+        mSysUiState.setFlag(SYSUI_STATE_A11Y_BUTTON_CLICKABLE, clickable)
+                .setFlag(SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE, longClickable)
+                .setFlag(SYSUI_STATE_IME_SHOWING,
+                        (mNavigationIconHints & NAVIGATION_HINT_BACK_ALT) != 0)
+                .setFlag(SYSUI_STATE_IME_SWITCHER_SHOWING,
+                        (mNavigationIconHints & NAVIGATION_HINT_IME_SHOWN) != 0)
+                .commitUpdate(mDisplayId);
     }
 
     @Override
     public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition,
             boolean showImeSwitcher) {
-        mOverviewProxyService.notifyImeWindowStatus(displayId, token, vis, backDisposition,
-                showImeSwitcher);
+        boolean imeShown = (vis & InputMethodService.IME_VISIBLE) != 0;
+        int hints = Utilities.calculateBackDispositionHints(mNavigationIconHints, backDisposition,
+                imeShown, showImeSwitcher);
+        if (hints != mNavigationIconHints) {
+            mNavigationIconHints = hints;
+            updateSysuiFlags();
+        }
+    }
+
+    @Override
+    public void onRotationProposal(int rotation, boolean isValid) {
+        mOverviewProxyService.onRotationProposal(rotation, isValid);
+    }
+
+    @Override
+    public void disable(int displayId, int state1, int state2, boolean animate) {
+        mOverviewProxyService.disable(displayId, state1, state2, animate);
+    }
+
+    @Override
+    public void onSystemBarAttributesChanged(int displayId, int appearance,
+            AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme, int behavior,
+            InsetsState requestedState, String packageName) {
+        mOverviewProxyService.onSystemBarAttributesChanged(displayId, behavior);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index cb0c411..5325943 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -34,6 +34,7 @@
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_WINDOW_CORNER_RADIUS;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DOZING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED;
@@ -736,12 +737,13 @@
     }
 
     private void onStatusBarStateChanged(boolean keyguardShowing, boolean keyguardOccluded,
-            boolean bouncerShowing) {
+            boolean bouncerShowing, boolean isDozing) {
         mSysUiState.setFlag(SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING,
                         keyguardShowing && !keyguardOccluded)
                 .setFlag(SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED,
                         keyguardShowing && keyguardOccluded)
                 .setFlag(SYSUI_STATE_BOUNCER_SHOWING, bouncerShowing)
+                .setFlag(SYSUI_STATE_DEVICE_DOZING, isDozing)
                 .commitUpdate(mContext.getDisplayId());
     }
 
@@ -967,19 +969,40 @@
         }
     }
 
-    public void notifyImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition,
-            boolean showImeSwitcher) {
+    public void disable(int displayId, int state1, int state2, boolean animate) {
         try {
             if (mOverviewProxy != null) {
-                mOverviewProxy.onImeWindowStatusChanged(displayId, token, vis, backDisposition,
-                        showImeSwitcher);
+                mOverviewProxy.disable(displayId, state1, state2, animate);
             } else {
-                Log.e(TAG_OPS, "Failed to get overview proxy for setting IME status.");
+                Log.e(TAG_OPS, "Failed to get overview proxy for disable flags.");
             }
         } catch (RemoteException e) {
-            Log.e(TAG_OPS, "Failed to call notifyImeWindowStatus()", e);
+            Log.e(TAG_OPS, "Failed to call disable()", e);
         }
+    }
 
+    public void onRotationProposal(int rotation, boolean isValid) {
+        try {
+            if (mOverviewProxy != null) {
+                mOverviewProxy.onRotationProposal(rotation, isValid);
+            } else {
+                Log.e(TAG_OPS, "Failed to get overview proxy for proposing rotation.");
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG_OPS, "Failed to call onRotationProposal()", e);
+        }
+    }
+
+    public void onSystemBarAttributesChanged(int displayId, int behavior) {
+        try {
+            if (mOverviewProxy != null) {
+                mOverviewProxy.onSystemBarAttributesChanged(displayId, behavior);
+            } else {
+                Log.e(TAG_OPS, "Failed to get overview proxy for system bar attr change.");
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG_OPS, "Failed to call onSystemBarAttributesChanged()", e);
+        }
     }
 
     private void updateEnabledState() {
@@ -1030,7 +1053,5 @@
         default void onAssistantProgress(@FloatRange(from = 0.0, to = 1.0) float progress) {}
         default void onAssistantGestureCompletion(float velocity) {}
         default void startAssistant(Bundle bundle) {}
-        default void onImeWindowStatusChanged(int displayId, IBinder token, int vis,
-                int backDisposition, boolean showImeSwitcher) {}
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 8e52b0d..c7f8dcf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -50,6 +50,7 @@
 import android.util.Log;
 import android.util.Pair;
 import android.util.SparseArray;
+import android.view.InsetsState;
 import android.view.InsetsState.InternalInsetsType;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
@@ -337,7 +338,7 @@
          */
         default void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
                 AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-                @Behavior int behavior, boolean isFullscreen) { }
+                @Behavior int behavior, InsetsState requestedState, String packageName) { }
 
         /**
          * @see IStatusBar#showTransient(int, int[]).
@@ -996,7 +997,7 @@
     @Override
     public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
             AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-            @Behavior int behavior, boolean isFullscreen) {
+            @Behavior int behavior, InsetsState requestedState, String packageName) {
         synchronized (mLock) {
             SomeArgs args = SomeArgs.obtain();
             args.argi1 = displayId;
@@ -1004,7 +1005,8 @@
             args.argi3 = navbarColorManagedByIme ? 1 : 0;
             args.arg1 = appearanceRegions;
             args.argi4 = behavior;
-            args.argi5 = isFullscreen ? 1 : 0;
+            args.arg2 = requestedState;
+            args.arg3 = packageName;
             mHandler.obtainMessage(MSG_SYSTEM_BAR_CHANGED, args).sendToTarget();
         }
     }
@@ -1387,7 +1389,7 @@
                     for (int i = 0; i < mCallbacks.size(); i++) {
                         mCallbacks.get(i).onSystemBarAttributesChanged(args.argi1, args.argi2,
                                 (AppearanceRegion[]) args.arg1, args.argi3 == 1, args.argi4,
-                                args.argi5 == 1);
+                                (InsetsState) args.arg2, (String) args.arg3);
                     }
                     args.recycle();
                     break;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index 0725bf9..d4f5bd2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -16,6 +16,10 @@
 
 package com.android.systemui.statusbar;
 
+import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
+import static android.view.InsetsState.ITYPE_STATUS_BAR;
+import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
+
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_FROM_AOD;
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_TO_AOD;
 
@@ -23,10 +27,16 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
+import android.os.SystemProperties;
 import android.text.format.DateFormat;
 import android.util.FloatProperty;
 import android.util.Log;
+import android.view.InsetsFlags;
+import android.view.InsetsState;
 import android.view.View;
+import android.view.ViewDebug;
+import android.view.WindowInsetsController.Appearance;
+import android.view.WindowInsetsController.Behavior;
 import android.view.animation.Interpolator;
 
 import androidx.annotation.NonNull;
@@ -56,6 +66,9 @@
 public class StatusBarStateControllerImpl implements SysuiStatusBarStateController,
         CallbackController<StateListener>, Dumpable {
     private static final String TAG = "SbStateController";
+    private static final boolean DEBUG_IMMERSIVE_APPS =
+            SystemProperties.getBoolean("persist.debug.immersive_apps", false);
+
     // Must be a power of 2
     private static final int HISTORY_SIZE = 32;
 
@@ -420,7 +433,10 @@
     }
 
     @Override
-    public void setFullscreenState(boolean isFullscreen) {
+    public void setSystemBarAttributes(@Appearance int appearance, @Behavior int behavior,
+            InsetsState requestedState, String packageName) {
+        boolean isFullscreen = !requestedState.getSourceOrDefaultVisibility(ITYPE_STATUS_BAR)
+                || !requestedState.getSourceOrDefaultVisibility(ITYPE_NAVIGATION_BAR);
         if (mIsFullscreen != isFullscreen) {
             mIsFullscreen = isFullscreen;
             synchronized (mListeners) {
@@ -429,6 +445,19 @@
                 }
             }
         }
+
+        // TODO (b/190543382): Finish the logging logic.
+        // This section can be removed if we don't need to print it on logcat.
+        if (DEBUG_IMMERSIVE_APPS) {
+            boolean dim = (appearance & APPEARANCE_LOW_PROFILE_BARS) != 0;
+            String behaviorName = ViewDebug.flagsToString(InsetsFlags.class, "behavior", behavior);
+            String requestedVisibilityString = requestedState.toSourceVisibilityString();
+            if (requestedVisibilityString.isEmpty()) {
+                requestedVisibilityString = "none";
+            }
+            Log.d(TAG, packageName + " dim=" + dim + " behavior=" + behaviorName
+                    + " requested visibilities: " + requestedVisibilityString);
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
index 2520050..0bbae2a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
@@ -19,7 +19,10 @@
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
 import android.annotation.IntDef;
+import android.view.InsetsState;
 import android.view.View;
+import android.view.WindowInsetsController.Appearance;
+import android.view.WindowInsetsController.Behavior;
 
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.phone.StatusBar;
@@ -155,9 +158,10 @@
     boolean isKeyguardRequested();
 
     /**
-     * Set the fullscreen state
+     * Set the system bar attributes
      */
-    void setFullscreenState(boolean isFullscreen);
+    void setSystemBarAttributes(@Appearance int appearance, @Behavior int behavior,
+            InsetsState requestedState, String packageName);
 
     /**
      * Set pulsing
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 4ad7202..f22c139 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -112,7 +112,6 @@
 import com.android.systemui.statusbar.policy.HeadsUpUtil;
 import com.android.systemui.statusbar.policy.ScrollAdapter;
 import com.android.systemui.util.Assert;
-import com.android.systemui.util.leak.RotationUtils;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -949,7 +948,7 @@
             return;
         }
         // Portrait is easy, just use the dimen for paddings
-        if (RotationUtils.getRotation(mContext) == RotationUtils.ROTATION_NONE) {
+        if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
             mSidePaddings = mMinimumPaddings;
             return;
         }
@@ -5642,6 +5641,10 @@
         mSwipeHelper.resetExposedMenuView(animate, force);
     }
 
+    boolean isUsingSplitNotificationShade() {
+        return mShouldUseSplitNotificationShade;
+    }
+
     static boolean matchesSelection(
             ExpandableNotificationRow row,
             @SelectedRows int selection) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 09afedb..a92682a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -1116,11 +1116,16 @@
     /**
      * Update whether we should show the empty shade view (no notifications in the shade).
      * If so, send the update to our view.
+     *
+     * When in split mode, notifications are always visible regardless of the state of the
+     * QuickSettings panel. That being the case, empty view is always shown if the other conditions
+     * are true.
      */
     public void updateShowEmptyShadeView() {
         mShowEmptyShadeView = mBarState != KEYGUARD
-                && !mView.isQsExpanded()
+                && (!mView.isQsExpanded() || mView.isUsingSplitNotificationShade())
                 && mView.getVisibleNotificationCount() == 0;
+
         mView.updateEmptyShadeView(
                 mShowEmptyShadeView,
                 mZenModeController.areNotificationsHiddenInShade());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
index 7d13405..c213707 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
@@ -21,6 +21,7 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.annotation.Nullable;
+import android.view.InsetsState;
 import android.view.View;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
@@ -149,7 +150,7 @@
         @Override
         public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
                 AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-                @Behavior int behavior, boolean isFullscreen) {
+                @Behavior int behavior, InsetsState requestedState, String packageName) {
             if (displayId != mDisplayId) {
                 return;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 567318c..920e2a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -23,6 +23,8 @@
 import static androidx.constraintlayout.widget.ConstraintSet.START;
 
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE;
+import static com.android.keyguard.KeyguardClockSwitch.LARGE;
+import static com.android.keyguard.KeyguardClockSwitch.SMALL;
 import static com.android.systemui.classifier.Classifier.QS_COLLAPSE;
 import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
@@ -58,6 +60,8 @@
 import android.os.UserManager;
 import android.os.VibrationEffect;
 import android.provider.Settings;
+import android.transition.ChangeBounds;
+import android.transition.TransitionManager;
 import android.util.Log;
 import android.util.MathUtils;
 import android.view.LayoutInflater;
@@ -629,6 +633,8 @@
 
     private KeyguardMediaController mKeyguardMediaController;
 
+    private boolean mStatusViewCentered = false;
+
     private View.AccessibilityDelegate mAccessibilityDelegate = new View.AccessibilityDelegate() {
         @Override
         public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
@@ -1036,16 +1042,16 @@
             constraintSet.connect(
                     R.id.notification_stack_scroller, START,
                     R.id.qs_edge_guideline, START);
-            constraintSet.connect(R.id.keyguard_status_view, END, R.id.qs_edge_guideline, END);
         } else {
             constraintSet.connect(R.id.qs_frame, END, PARENT_ID, END);
             constraintSet.connect(R.id.notification_stack_scroller, START, PARENT_ID, START);
-            constraintSet.connect(R.id.keyguard_status_view, END, PARENT_ID, END);
         }
         constraintSet.getConstraint(R.id.notification_stack_scroller).layout.mWidth = panelWidth;
         constraintSet.getConstraint(R.id.qs_frame).layout.mWidth = qsWidth;
         constraintSet.applyTo(mNotificationContainerParent);
 
+        updateKeyguardStatusViewAlignment(false /* animate */);
+
         mKeyguardMediaController.refreshMediaPosition();
     }
 
@@ -1263,7 +1269,14 @@
             updateClockAppearance();
         }
         if (!onKeyguard) {
-            stackScrollerPadding = getUnlockedStackScrollerPadding();
+            if (mShouldUseSplitNotificationShade) {
+                // Quick settings are not on the top of the notifications
+                // when in split shade mode (they are on the left side),
+                // so we should not add a padding for them
+                stackScrollerPadding = 0;
+            } else {
+                stackScrollerPadding = getUnlockedStackScrollerPadding();
+            }
         } else {
             stackScrollerPadding = mClockPositionResult.stackScrollerPaddingExpanded;
         }
@@ -1284,7 +1297,12 @@
         boolean bypassEnabled = mKeyguardBypassController.getBypassEnabled();
         final boolean hasVisibleNotifications = mNotificationStackScrollLayoutController
                 .getVisibleNotificationCount() != 0 || mMediaDataManager.hasActiveMedia();
-        mKeyguardStatusViewController.setHasVisibleNotifications(hasVisibleNotifications);
+        if (hasVisibleNotifications && !mShouldUseSplitNotificationShade) {
+            mKeyguardStatusViewController.displayClock(SMALL);
+        } else {
+            mKeyguardStatusViewController.displayClock(LARGE);
+        }
+        updateKeyguardStatusViewAlignment(true /* animate */);
         int userIconHeight = mKeyguardQsUserSwitchController != null
                 ? mKeyguardQsUserSwitchController.getUserIconHeight() : 0;
         float expandedFraction =
@@ -1328,6 +1346,26 @@
         updateClock();
     }
 
+    private void updateKeyguardStatusViewAlignment(boolean animate) {
+        boolean hasVisibleNotifications = mNotificationStackScrollLayoutController
+                .getVisibleNotificationCount() != 0 || mMediaDataManager.hasActiveMedia();
+        boolean shouldBeCentered = !mShouldUseSplitNotificationShade || !hasVisibleNotifications;
+        if (mStatusViewCentered != shouldBeCentered) {
+            mStatusViewCentered = shouldBeCentered;
+            ConstraintSet constraintSet = new ConstraintSet();
+            constraintSet.clone(mNotificationContainerParent);
+            int statusConstraint = shouldBeCentered ? PARENT_ID : R.id.qs_edge_guideline;
+            constraintSet.connect(R.id.keyguard_status_view, END, statusConstraint, END);
+            if (animate) {
+                ChangeBounds transition = new ChangeBounds();
+                transition.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+                transition.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+                TransitionManager.beginDelayedTransition(mNotificationContainerParent, transition);
+            }
+            constraintSet.applyTo(mNotificationContainerParent);
+        }
+    }
+
     /**
      * @return the padding of the stackscroller when unlocked
      */
@@ -2262,7 +2300,8 @@
 
     private void updateQSExpansionEnabledAmbient() {
         final float scrollRangeToTop = mAmbientState.getTopPadding() - mQuickQsOffsetHeight;
-        mQsExpansionEnabledAmbient = mAmbientState.getScrollY() <= scrollRangeToTop;
+        mQsExpansionEnabledAmbient = mShouldUseSplitNotificationShade
+                || (mAmbientState.getScrollY() <= scrollRangeToTop);
         setQsExpansionEnabled();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
index 022faf78..0ff6490 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
@@ -469,7 +469,8 @@
             if (cb != null) {
                 cb.onStateChanged(mCurrentState.mKeyguardShowing,
                         mCurrentState.mKeyguardOccluded,
-                        mCurrentState.mBouncerShowing);
+                        mCurrentState.mBouncerShowing,
+                        mCurrentState.mDozing);
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index cfcea96..44ed279 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -359,21 +359,7 @@
         mAnimateChange = state.getAnimateChange();
         mAnimationDuration = state.getAnimationDuration();
 
-        mInFrontTint = state.getFrontTint();
-        mBehindTint = state.getBehindTint();
-        mNotificationsTint = state.getNotifTint();
-        mBubbleTint = state.getBubbleTint();
-
-        mInFrontAlpha = state.getFrontAlpha();
-        mBehindAlpha = state.getBehindAlpha();
-        mBubbleAlpha = state.getBubbleAlpha();
-        mNotificationsAlpha = state.getNotifAlpha();
-        if (isNaN(mBehindAlpha) || isNaN(mInFrontAlpha) || isNaN(mNotificationsAlpha)) {
-            throw new IllegalStateException("Scrim opacity is NaN for state: " + state + ", front: "
-                    + mInFrontAlpha + ", back: " + mBehindAlpha + ", notif: "
-                    + mNotificationsAlpha);
-        }
-        applyStateToAlpha();
+        applyState();
 
         // Scrim might acquire focus when user is navigating with a D-pad or a keyboard.
         // We need to disable focus otherwise AOD would end up with a gray overlay.
@@ -637,7 +623,19 @@
         }
     }
 
-    private void applyStateToAlpha() {
+    private void applyState() {
+        mInFrontTint = mState.getFrontTint();
+        mBehindTint = mState.getBehindTint();
+        mNotificationsTint = mState.getNotifTint();
+        mBubbleTint = mState.getBubbleTint();
+
+        mInFrontAlpha = mState.getFrontAlpha();
+        mBehindAlpha = mState.getBehindAlpha();
+        mBubbleAlpha = mState.getBubbleAlpha();
+        mNotificationsAlpha = mState.getNotifAlpha();
+
+        assertAlphasValid();
+
         if (!mExpansionAffectsAlpha) {
             return;
         }
@@ -695,6 +693,11 @@
                 mBehindTint = behindTint;
             }
         }
+
+        assertAlphasValid();
+    }
+
+    private void assertAlphasValid() {
         if (isNaN(mBehindAlpha) || isNaN(mInFrontAlpha) || isNaN(mNotificationsAlpha)) {
             throw new IllegalStateException("Scrim opacity is NaN for state: " + mState
                     + ", front: " + mInFrontAlpha + ", back: " + mBehindAlpha + ", notif: "
@@ -734,7 +737,7 @@
 
 
     private void applyAndDispatchState() {
-        applyStateToAlpha();
+        applyState();
         if (mUpdatePending) {
             return;
         }
@@ -1203,6 +1206,7 @@
         pw.println(" ScrimController: ");
         pw.print("  state: ");
         pw.println(mState);
+        pw.println("    mClipQsScrim = " + mState.mClipQsScrim);
 
         pw.print("  frontScrim:");
         pw.print(" viewAlpha=");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 89711fa..d2a1750 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -104,6 +104,7 @@
 import android.view.Display;
 import android.view.IRemoteAnimationRunner;
 import android.view.IWindowManager;
+import android.view.InsetsState;
 import android.view.InsetsState.InternalInsetsType;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -984,7 +985,8 @@
             showTransientUnchecked();
         }
         onSystemBarAttributesChanged(mDisplayId, result.mAppearance, result.mAppearanceRegions,
-                result.mNavbarColorManagedByIme, result.mBehavior, result.mAppFullscreen);
+                result.mNavbarColorManagedByIme, result.mBehavior, result.mRequestedState,
+                result.mPackageName);
 
         // StatusBarManagerService has a back up of IME token and it's restored here.
         setImeWindowStatus(mDisplayId, result.mImeToken, result.mImeWindowVis,
@@ -2477,7 +2479,7 @@
     @Override
     public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
             AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-            @Behavior int behavior, boolean isFullscreen) {
+            @Behavior int behavior, InsetsState requestedState, String packageName) {
         if (displayId != mDisplayId) {
             return;
         }
@@ -2490,7 +2492,8 @@
                 mStatusBarMode, navbarColorManagedByIme);
 
         updateBubblesVisibility();
-        mStatusBarStateController.setFullscreenState(isFullscreen);
+        mStatusBarStateController.setSystemBarAttributes(
+                appearance, behavior, requestedState, packageName);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 6f63b17..e009c0c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -968,6 +968,9 @@
             mStatusBar.setBouncerShowing(bouncerShowing);
         }
 
+        if (occluded != mLastOccluded || mFirstUpdate) {
+            mKeyguardUpdateManager.onKeyguardOccludedChanged(occluded);
+        }
         if ((showing && !occluded) != (mLastShowing && !mLastOccluded) || mFirstUpdate) {
             mKeyguardUpdateManager.onKeyguardVisibilityChanged(showing && !occluded);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java
index f33ff27..ac43b67 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java
@@ -16,5 +16,6 @@
 package com.android.systemui.statusbar.phone;
 
 public interface StatusBarWindowCallback {
-    void onStateChanged(boolean keyguardShowing, boolean keyguardOccluded, boolean bouncerShowing);
+    void onStateChanged(boolean keyguardShowing, boolean keyguardOccluded, boolean bouncerShowing,
+            boolean isDozing);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
index 9a25a70..3d3b58a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
+import static android.view.ViewRootImpl.INSETS_LAYOUT_GENERALIZATION;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
@@ -34,6 +36,7 @@
 import android.util.Log;
 import android.view.Gravity;
 import android.view.IWindowManager;
+import android.view.Surface;
 import android.view.ViewGroup;
 import android.view.WindowManager;
 
@@ -118,21 +121,7 @@
         // Now that the status bar window encompasses the sliding panel and its
         // translucent backdrop, the entire thing is made TRANSLUCENT and is
         // hardware-accelerated.
-        mLp = new WindowManager.LayoutParams(
-                ViewGroup.LayoutParams.MATCH_PARENT,
-                mBarHeight,
-                WindowManager.LayoutParams.TYPE_STATUS_BAR,
-                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-                        | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
-                        | WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
-                PixelFormat.TRANSLUCENT);
-        mLp.privateFlags |= PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
-        mLp.token = new Binder();
-        mLp.gravity = Gravity.TOP;
-        mLp.setFitInsetsTypes(0 /* types */);
-        mLp.setTitle("StatusBar");
-        mLp.packageName = mContext.getPackageName();
-        mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+        mLp = getBarLayoutParams(mContext.getDisplay().getRotation());
 
         mWindowManager.addView(mStatusBarView, mLp);
         mLpChanged.copyFrom(mLp);
@@ -141,6 +130,63 @@
         calculateStatusBarLocationsForAllRotations();
     }
 
+    private WindowManager.LayoutParams getBarLayoutParams(int rotation) {
+        WindowManager.LayoutParams lp = getBarLayoutParamsForRotation(rotation);
+        lp.paramsForRotation = new WindowManager.LayoutParams[4];
+        for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) {
+            lp.paramsForRotation[rot] = getBarLayoutParamsForRotation(rot);
+        }
+        return lp;
+    }
+
+    private WindowManager.LayoutParams getBarLayoutParamsForRotation(int rotation) {
+        int height = mBarHeight;
+        if (INSETS_LAYOUT_GENERALIZATION) {
+            Rect displayBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+            int defaultAndUpsideDownHeight;
+            int theOtherHeight;
+            if (displayBounds.width() > displayBounds.height()) {
+                defaultAndUpsideDownHeight = mContext.getResources().getDimensionPixelSize(
+                        com.android.internal.R.dimen.status_bar_height_landscape);
+                theOtherHeight = mContext.getResources().getDimensionPixelSize(
+                        com.android.internal.R.dimen.status_bar_height_portrait);
+            } else {
+                defaultAndUpsideDownHeight = mContext.getResources().getDimensionPixelSize(
+                        com.android.internal.R.dimen.status_bar_height_portrait);
+                theOtherHeight = mContext.getResources().getDimensionPixelSize(
+                        com.android.internal.R.dimen.status_bar_height_landscape);
+            }
+            switch (rotation) {
+                case ROTATION_UNDEFINED:
+                case Surface.ROTATION_0:
+                case Surface.ROTATION_180:
+                    height = defaultAndUpsideDownHeight;
+                    break;
+                case Surface.ROTATION_90:
+                case Surface.ROTATION_270:
+                    height = theOtherHeight;
+                    break;
+            }
+        }
+        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+                WindowManager.LayoutParams.MATCH_PARENT,
+                height,
+                WindowManager.LayoutParams.TYPE_STATUS_BAR,
+                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                        | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
+                        | WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
+                PixelFormat.TRANSLUCENT);
+        lp.privateFlags |= PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
+        lp.token = new Binder();
+        lp.gravity = Gravity.TOP;
+        lp.setFitInsetsTypes(0 /* types */);
+        lp.setTitle("StatusBar");
+        lp.packageName = mContext.getPackageName();
+        lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+        return lp;
+
+    }
+
     private void calculateStatusBarLocationsForAllRotations() {
         Rect[] bounds = new Rect[4];
         bounds[0] = mContentInsetsProvider
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
index 1f1817c..5e70d0d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
@@ -21,7 +21,7 @@
 import android.database.DataSetObserver;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.LayerDrawable;
-import android.os.UserManager;
+import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.View;
@@ -76,7 +76,6 @@
     private final KeyguardVisibilityHelper mKeyguardVisibilityHelper;
     private final KeyguardUserDetailAdapter mUserDetailAdapter;
     private NotificationPanelViewController mNotificationPanelViewController;
-    private UserManager mUserManager;
     UserSwitcherController.UserRecord mCurrentUser;
 
     // State info for the user switch and keyguard
@@ -115,7 +114,6 @@
             UserAvatarView view,
             Context context,
             @Main Resources resources,
-            UserManager userManager,
             ScreenLifecycle screenLifecycle,
             UserSwitcherController userSwitcherController,
             KeyguardStateController keyguardStateController,
@@ -129,7 +127,6 @@
         if (DEBUG) Log.d(TAG, "New KeyguardQsUserSwitchController");
         mContext = context;
         mResources = resources;
-        mUserManager = userManager;
         mScreenLifecycle = screenLifecycle;
         mUserSwitcherController = userSwitcherController;
         mKeyguardStateController = keyguardStateController;
@@ -227,47 +224,39 @@
             return;
         }
 
-        if (mCurrentUser == null) {
-            mView.setVisibility(View.GONE);
-            return;
-        }
-
-        mView.setVisibility(View.VISIBLE);
-
-        String currentUserName = mCurrentUser.info.name;
         String contentDescription = null;
-
-        if (!TextUtils.isEmpty(currentUserName)) {
+        if (mCurrentUser != null && mCurrentUser.info != null && !TextUtils.isEmpty(
+                mCurrentUser.info.name)) {
+            // If we know the current user's name, have TalkBack to announce "Signed in as [user
+            // name]" when the icon is selected
+            contentDescription = mContext.getString(R.string.accessibility_quick_settings_user,
+                    mCurrentUser.info.name);
+        } else {
+            // As a fallback, have TalkBack announce "Switch user"
             contentDescription = mContext.getString(
-                    R.string.accessibility_quick_settings_user,
-                    currentUserName);
+                    R.string.accessibility_multi_user_switch_switcher);
         }
 
         if (!TextUtils.equals(mView.getContentDescription(), contentDescription)) {
             mView.setContentDescription(contentDescription);
         }
 
-        mView.setDrawableWithBadge(getCurrentUserIcon().mutate(), mCurrentUser.resolveId());
+        int userId = mCurrentUser != null ? mCurrentUser.resolveId() : UserHandle.USER_NULL;
+        mView.setDrawableWithBadge(getCurrentUserIcon().mutate(), userId);
     }
 
     Drawable getCurrentUserIcon() {
         Drawable drawable;
-        if (mCurrentUser.picture == null) {
-            if (mCurrentUser.isCurrent && mCurrentUser.isGuest) {
+        if (mCurrentUser == null || mCurrentUser.picture == null) {
+            if (mCurrentUser != null && mCurrentUser.isGuest) {
                 drawable = mContext.getDrawable(R.drawable.ic_avatar_guest_user);
             } else {
-                drawable = mAdapter.getIconDrawable(mContext, mCurrentUser);
+                drawable = mContext.getDrawable(R.drawable.ic_avatar_user);
             }
-            int iconColorRes;
-            if (mCurrentUser.isSwitchToEnabled) {
-                iconColorRes = R.color.kg_user_switcher_avatar_icon_color;
-            } else {
-                iconColorRes = R.color.kg_user_switcher_restricted_avatar_icon_color;
-            }
+            int iconColorRes = R.color.kg_user_switcher_avatar_icon_color;
             drawable.setTint(mResources.getColor(iconColorRes, mContext.getTheme()));
         } else {
-            int avatarSize =
-                    (int) mResources.getDimension(R.dimen.kg_framed_avatar_size);
+            int avatarSize = (int) mResources.getDimension(R.dimen.kg_framed_avatar_size);
             drawable = new CircleFramedDrawable(mCurrentUser.picture, avatarSize);
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 5de7846a..9446732 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -104,6 +104,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.graphics.drawable.BackgroundBlurDrawable;
+import com.android.internal.view.RotationPolicy;
 import com.android.settingslib.Utils;
 import com.android.systemui.Dependency;
 import com.android.systemui.Prefs;
@@ -400,7 +401,9 @@
         mDialog.setCanceledOnTouchOutside(true);
         mDialog.setOnShowListener(dialog -> {
             mDialogView.getViewTreeObserver().addOnComputeInternalInsetsListener(this);
-            if (!isLandscape()) mDialogView.setTranslationX(mDialogView.getWidth() / 2.0f);
+            if (!shouldSlideInVolumeTray()) {
+                mDialogView.setTranslationX(mDialogView.getWidth() / 2.0f);
+            }
             mDialogView.setAlpha(0);
             mDialogView.animate()
                     .alpha(1)
@@ -587,6 +590,10 @@
         return (int) (alpha * 255);
     }
 
+    private boolean shouldSlideInVolumeTray() {
+        return mContext.getDisplay().getRotation() != RotationPolicy.NATURAL_ROTATION;
+    }
+
     private boolean isLandscape() {
         return mContext.getResources().getConfiguration().orientation ==
                 Configuration.ORIENTATION_LANDSCAPE;
@@ -1320,7 +1327,7 @@
 
                     hideRingerDrawer();
                 }, 50));
-        if (!isLandscape()) animator.translationX(mDialogView.getWidth() / 2.0f);
+        if (!shouldSlideInVolumeTray()) animator.translationX(mDialogView.getWidth() / 2.0f);
         animator.start();
         checkODICaptionsTooltip(true);
         mController.notifyVisible(false);
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
index 2216a91..3be1d3c 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
@@ -38,6 +38,7 @@
 import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
 import com.android.wm.shell.pip.PipTaskOrganizer;
 import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.pip.PipTransitionState;
 import com.android.wm.shell.pip.PipUiEventLogger;
 import com.android.wm.shell.pip.tv.TvPipController;
 import com.android.wm.shell.pip.tv.TvPipMenuController;
@@ -144,10 +145,17 @@
 
     @WMSingleton
     @Provides
+    static PipTransitionState providePipTransitionState() {
+        return new PipTransitionState();
+    }
+
+    @WMSingleton
+    @Provides
     static PipTaskOrganizer providePipTaskOrganizer(Context context,
             TvPipMenuController tvPipMenuController,
             SyncTransactionQueue syncTransactionQueue,
             PipBoundsState pipBoundsState,
+            PipTransitionState pipTransitionState,
             PipBoundsAlgorithm pipBoundsAlgorithm,
             PipAnimationController pipAnimationController,
             PipTransitionController pipTransitionController,
@@ -157,7 +165,7 @@
             PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
             @ShellMainThread ShellExecutor mainExecutor) {
         return new PipTaskOrganizer(context,
-                syncTransactionQueue, pipBoundsState, pipBoundsAlgorithm,
+                syncTransactionQueue, pipTransitionState, pipBoundsState, pipBoundsAlgorithm,
                 tvPipMenuController, pipAnimationController, pipSurfaceTransactionHelper,
                 pipTransitionController, splitScreenOptional, displayController, pipUiEventLogger,
                 shellTaskOrganizer, mainExecutor);
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index bc956dc..f2db4f1 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -64,6 +64,7 @@
 import com.android.wm.shell.onehanded.OneHandedUiEventLogger;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.protolog.ShellProtoLogImpl;
+import com.android.wm.shell.splitscreen.SplitScreen;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -104,7 +105,8 @@
 
     // Shell interfaces
     private final Optional<Pip> mPipOptional;
-    private final Optional<LegacySplitScreen> mSplitScreenOptional;
+    private final Optional<LegacySplitScreen> mLegacySplitScreenOptional;
+    private final Optional<SplitScreen> mSplitScreenOptional;
     private final Optional<OneHanded> mOneHandedOptional;
     private final Optional<HideDisplayCutout> mHideDisplayCutoutOptional;
     private final Optional<ShellCommandHandler> mShellCommandHandler;
@@ -120,6 +122,7 @@
     private final Executor mSysUiMainExecutor;
 
     private boolean mIsSysUiStateValid;
+    private KeyguardUpdateMonitorCallback mLegacySplitScreenKeyguardCallback;
     private KeyguardUpdateMonitorCallback mSplitScreenKeyguardCallback;
     private KeyguardUpdateMonitorCallback mPipKeyguardCallback;
     private KeyguardUpdateMonitorCallback mOneHandedKeyguardCallback;
@@ -128,7 +131,8 @@
     @Inject
     public WMShell(Context context,
             Optional<Pip> pipOptional,
-            Optional<LegacySplitScreen> splitScreenOptional,
+            Optional<LegacySplitScreen> legacySplitScreenOptional,
+            Optional<SplitScreen> splitScreenOptional,
             Optional<OneHanded> oneHandedOptional,
             Optional<HideDisplayCutout> hideDisplayCutoutOptional,
             Optional<ShellCommandHandler> shellCommandHandler,
@@ -149,6 +153,7 @@
         mScreenLifecycle = screenLifecycle;
         mSysUiState = sysUiState;
         mPipOptional = pipOptional;
+        mLegacySplitScreenOptional = legacySplitScreenOptional;
         mSplitScreenOptional = splitScreenOptional;
         mOneHandedOptional = oneHandedOptional;
         mHideDisplayCutoutOptional = hideDisplayCutoutOptional;
@@ -165,6 +170,7 @@
         mProtoTracer.add(this);
         mCommandQueue.addCallback(this);
         mPipOptional.ifPresent(this::initPip);
+        mLegacySplitScreenOptional.ifPresent(this::initLegacySplitScreen);
         mSplitScreenOptional.ifPresent(this::initSplitScreen);
         mOneHandedOptional.ifPresent(this::initOneHanded);
         mHideDisplayCutoutOptional.ifPresent(this::initHideDisplayCutout);
@@ -218,8 +224,8 @@
     }
 
     @VisibleForTesting
-    void initSplitScreen(LegacySplitScreen legacySplitScreen) {
-        mSplitScreenKeyguardCallback = new KeyguardUpdateMonitorCallback() {
+    void initLegacySplitScreen(LegacySplitScreen legacySplitScreen) {
+        mLegacySplitScreenKeyguardCallback = new KeyguardUpdateMonitorCallback() {
             @Override
             public void onKeyguardVisibilityChanged(boolean showing) {
                 // Hide the divider when keyguard is showing. Even though keyguard/statusbar is
@@ -229,6 +235,17 @@
                 legacySplitScreen.onKeyguardVisibilityChanged(showing);
             }
         };
+        mKeyguardUpdateMonitor.registerCallback(mLegacySplitScreenKeyguardCallback);
+    }
+
+    @VisibleForTesting
+    void initSplitScreen(SplitScreen splitScreen) {
+        mSplitScreenKeyguardCallback = new KeyguardUpdateMonitorCallback() {
+            @Override
+            public void onKeyguardOccludedChanged(boolean occluded) {
+                splitScreen.onKeyguardOccludedChanged(occluded);
+            }
+        };
         mKeyguardUpdateMonitor.registerCallback(mSplitScreenKeyguardCallback);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
index 6ef7450..7e733a9 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
@@ -194,11 +194,12 @@
             ShellTaskOrganizer organizer,
             DisplayController displayController,
             @ShellMainThread ShellExecutor mainExecutor,
-            @ShellMainThread Handler mainHandler) {
+            @ShellMainThread Handler mainHandler,
+            SyncTransactionQueue syncQueue) {
         return Optional.of(BubbleController.create(context, null /* synchronizer */,
                 floatingContentCoordinator, statusBarService, windowManager,
                 windowManagerShellWrapper, launcherApps, taskStackListener,
-                uiEventLogger, organizer, displayController, mainExecutor, mainHandler));
+                uiEventLogger, organizer, displayController, mainExecutor, mainHandler, syncQueue));
     }
 
     //
@@ -424,8 +425,9 @@
     @Provides
     static TaskViewFactoryController provideTaskViewFactoryController(
             ShellTaskOrganizer shellTaskOrganizer,
-            @ShellMainThread ShellExecutor mainExecutor) {
-        return new TaskViewFactoryController(shellTaskOrganizer, mainExecutor);
+            @ShellMainThread ShellExecutor mainExecutor,
+            SyncTransactionQueue syncQueue) {
+        return new TaskViewFactoryController(shellTaskOrganizer, mainExecutor, syncQueue);
     }
 
     //
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
index 36fd9be..be7813e 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
@@ -48,6 +48,7 @@
 import com.android.wm.shell.pip.PipTaskOrganizer;
 import com.android.wm.shell.pip.PipTransition;
 import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.pip.PipTransitionState;
 import com.android.wm.shell.pip.PipUiEventLogger;
 import com.android.wm.shell.pip.phone.PhonePipMenuController;
 import com.android.wm.shell.pip.phone.PipAppOpsListener;
@@ -184,8 +185,15 @@
 
     @WMSingleton
     @Provides
+    static PipTransitionState providePipTransitionState() {
+        return new PipTransitionState();
+    }
+
+    @WMSingleton
+    @Provides
     static PipTaskOrganizer providePipTaskOrganizer(Context context,
             SyncTransactionQueue syncTransactionQueue,
+            PipTransitionState pipTransitionState,
             PipBoundsState pipBoundsState,
             PipBoundsAlgorithm pipBoundsAlgorithm,
             PhonePipMenuController menuPhoneController,
@@ -197,7 +205,7 @@
             PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
             @ShellMainThread ShellExecutor mainExecutor) {
         return new PipTaskOrganizer(context,
-                syncTransactionQueue, pipBoundsState, pipBoundsAlgorithm,
+                syncTransactionQueue, pipTransitionState, pipBoundsState, pipBoundsAlgorithm,
                 menuPhoneController, pipAnimationController, pipSurfaceTransactionHelper,
                 pipTransitionController, splitScreenOptional, displayController, pipUiEventLogger,
                 shellTaskOrganizer, mainExecutor);
@@ -215,8 +223,9 @@
     static PipTransitionController providePipTransitionController(Context context,
             Transitions transitions, ShellTaskOrganizer shellTaskOrganizer,
             PipAnimationController pipAnimationController, PipBoundsAlgorithm pipBoundsAlgorithm,
-            PipBoundsState pipBoundsState, PhonePipMenuController pipMenuController) {
-        return new PipTransition(context, pipBoundsState, pipMenuController,
+            PipBoundsState pipBoundsState, PipTransitionState pipTransitionState,
+            PhonePipMenuController pipMenuController) {
+        return new PipTransition(context, pipBoundsState, pipTransitionState, pipMenuController,
                 pipBoundsAlgorithm, pipAnimationController, transitions, shellTaskOrganizer);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
index 10ed1d7..ce02b83 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
@@ -19,6 +19,9 @@
 import static android.view.View.GONE;
 import static android.view.View.VISIBLE;
 
+import static com.android.keyguard.KeyguardClockSwitch.LARGE;
+import static com.android.keyguard.KeyguardClockSwitch.SMALL;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.mock;
@@ -247,4 +250,36 @@
 
         verify(plugin).setStyle(style);
     }
+
+    @Test
+    public void switchingToBigClock_makesSmallClockDisappear() {
+        mKeyguardClockSwitch.switchToClock(LARGE);
+
+        mKeyguardClockSwitch.mClockInAnim.end();
+        mKeyguardClockSwitch.mClockOutAnim.end();
+
+        assertThat(mLargeClockFrame.getAlpha()).isEqualTo(1);
+        assertThat(mLargeClockFrame.getVisibility()).isEqualTo(VISIBLE);
+        assertThat(mClockFrame.getAlpha()).isEqualTo(0);
+    }
+
+    @Test
+    public void switchingToSmallClock_makesBigClockDisappear() {
+        mKeyguardClockSwitch.switchToClock(SMALL);
+
+        mKeyguardClockSwitch.mClockInAnim.end();
+        mKeyguardClockSwitch.mClockOutAnim.end();
+
+        assertThat(mClockFrame.getAlpha()).isEqualTo(1);
+        assertThat(mClockFrame.getVisibility()).isEqualTo(VISIBLE);
+        // only big clock is removed at switch
+        assertThat(mLargeClockFrame.getParent()).isNull();
+        assertThat(mLargeClockFrame.getAlpha()).isEqualTo(0);
+    }
+
+    @Test
+    public void switchingToBigClock_returnsTrueOnlyWhenItWasNotVisibleBefore() {
+        assertThat(mKeyguardClockSwitch.switchToClock(LARGE)).isTrue();
+        assertThat(mKeyguardClockSwitch.switchToClock(LARGE)).isFalse();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index ca857c5..64bdc2e 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -19,11 +19,13 @@
 import static android.view.WindowInsets.Type.ime;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -33,6 +35,7 @@
 import android.content.res.Resources;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.view.MotionEvent;
 import android.view.WindowInsetsController;
 
 import androidx.test.filters.SmallTest;
@@ -43,6 +46,7 @@
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
@@ -58,6 +62,7 @@
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper()
 public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
+    private static final int VIEW_WIDTH = 1600;
 
     @Rule
     public MockitoRule mRule = MockitoJUnit.rule();
@@ -100,6 +105,8 @@
     private EmergencyButtonController mEmergencyButtonController;
     @Mock
     private Resources mResources;
+    @Mock
+    private FalsingCollector mFalsingCollector;
     private Configuration mConfiguration;
 
     private KeyguardSecurityContainerController mKeyguardSecurityContainerController;
@@ -112,7 +119,9 @@
         mConfiguration.setToDefaults(); // Defaults to ORIENTATION_UNDEFINED.
 
         when(mResources.getConfiguration()).thenReturn(mConfiguration);
+        when(mView.getContext()).thenReturn(mContext);
         when(mView.getResources()).thenReturn(mResources);
+        when(mView.getWidth()).thenReturn(VIEW_WIDTH);
         when(mAdminSecondaryLockScreenControllerFactory.create(any(KeyguardSecurityCallback.class)))
                 .thenReturn(mAdminSecondaryLockScreenController);
         when(mSecurityViewFlipper.getWindowInsetsController()).thenReturn(mWindowInsetsController);
@@ -131,7 +140,7 @@
                 mView, mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils,
                 mKeyguardUpdateMonitor, mKeyguardSecurityModel, mMetricsLogger, mUiEventLogger,
                 mKeyguardStateController, mKeyguardSecurityViewFlipperController,
-                mConfigurationController)
+                mConfigurationController, mFalsingCollector)
                 .create(mSecurityCallback);
     }
 
@@ -169,18 +178,156 @@
     public void onResourcesUpdate_callsThroughOnRotationChange() {
         // Rotation is the same, shouldn't cause an update
         mKeyguardSecurityContainerController.updateResources();
-        verify(mView, times(0)).updateLayoutForSecurityMode(any());
+        verify(mView, times(0)).setOneHandedMode(anyBoolean());
 
         // Update rotation. Should trigger update
         mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE;
 
         mKeyguardSecurityContainerController.updateResources();
-        verify(mView, times(1)).updateLayoutForSecurityMode(any());
+        verify(mView, times(1)).setOneHandedMode(anyBoolean());
     }
 
     @Test
-    public void updateKeyguardPosition_callsThroughToView() {
+    public void updateKeyguardPosition_callsThroughToViewInOneHandedMode() {
+        when(mView.isOneHandedMode()).thenReturn(true);
+        mKeyguardSecurityContainerController.updateKeyguardPosition(VIEW_WIDTH / 3f);
+        verify(mView).setOneHandedModeLeftAligned(true, false);
+
+        mKeyguardSecurityContainerController.updateKeyguardPosition((VIEW_WIDTH / 3f) * 2);
+        verify(mView).setOneHandedModeLeftAligned(false, false);
+    }
+
+    @Test
+    public void updateKeyguardPosition_ignoredInTwoHandedMode() {
+        when(mView.isOneHandedMode()).thenReturn(false);
         mKeyguardSecurityContainerController.updateKeyguardPosition(1.0f);
-        verify(mView).updateKeyguardPosition(1.0f);
+        verify(mView, never()).setOneHandedModeLeftAligned(anyBoolean(), anyBoolean());
+    }
+
+    private void touchDownLeftSide() {
+        mKeyguardSecurityContainerController.mGlobalTouchListener.onTouchEvent(
+                MotionEvent.obtain(
+                        /* downTime= */0,
+                        /* eventTime= */0,
+                        MotionEvent.ACTION_DOWN,
+                        /* x= */VIEW_WIDTH / 3f,
+                        /* y= */0,
+                        /* metaState= */0));
+    }
+
+    private void touchDownRightSide() {
+        mKeyguardSecurityContainerController.mGlobalTouchListener.onTouchEvent(
+                MotionEvent.obtain(
+                        /* downTime= */0,
+                        /* eventTime= */0,
+                        MotionEvent.ACTION_DOWN,
+                        /* x= */(VIEW_WIDTH / 3f) * 2,
+                        /* y= */0,
+                        /* metaState= */0));
+    }
+
+    @Test
+    public void onInterceptTap_inhibitsFalsingInOneHandedMode() {
+        when(mView.isOneHandedMode()).thenReturn(true);
+        when(mView.isOneHandedModeLeftAligned()).thenReturn(true);
+
+        touchDownLeftSide();
+        verify(mFalsingCollector, never()).avoidGesture();
+
+        // Now on the right.
+        touchDownRightSide();
+        verify(mFalsingCollector).avoidGesture();
+
+        // Move and re-test
+        reset(mFalsingCollector);
+        when(mView.isOneHandedModeLeftAligned()).thenReturn(false);
+
+        // On the right...
+        touchDownRightSide();
+        verify(mFalsingCollector, never()).avoidGesture();
+
+        touchDownLeftSide();
+        verify(mFalsingCollector).avoidGesture();
+    }
+
+    @Test
+    public void showSecurityScreen_oneHandedMode_bothFlagsDisabled_noOneHandedMode() {
+        setUpKeyguardFlags(
+                /* deviceConfigCanUseOneHandedKeyguard= */false,
+                /* sysuiResourceCanUseOneHandedKeyguard= */false);
+
+        when(mKeyguardSecurityViewFlipperController.getSecurityView(
+                eq(SecurityMode.Pattern), any(KeyguardSecurityCallback.class)))
+                .thenReturn((KeyguardInputViewController) mKeyguardPasswordViewController);
+
+        mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Pattern);
+        verify(mView).setOneHandedMode(false);
+    }
+
+    @Test
+    public void showSecurityScreen_oneHandedMode_deviceFlagDisabled_noOneHandedMode() {
+        setUpKeyguardFlags(
+                /* deviceConfigCanUseOneHandedKeyguard= */false,
+                /* sysuiResourceCanUseOneHandedKeyguard= */true);
+
+        when(mKeyguardSecurityViewFlipperController.getSecurityView(
+                eq(SecurityMode.Pattern), any(KeyguardSecurityCallback.class)))
+                .thenReturn((KeyguardInputViewController) mKeyguardPasswordViewController);
+
+        mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Pattern);
+        verify(mView).setOneHandedMode(false);
+    }
+
+    @Test
+    public void showSecurityScreen_oneHandedMode_sysUiFlagDisabled_noOneHandedMode() {
+        setUpKeyguardFlags(
+                /* deviceConfigCanUseOneHandedKeyguard= */true,
+                /* sysuiResourceCanUseOneHandedKeyguard= */false);
+
+        when(mKeyguardSecurityViewFlipperController.getSecurityView(
+                eq(SecurityMode.Pattern), any(KeyguardSecurityCallback.class)))
+                .thenReturn((KeyguardInputViewController) mKeyguardPasswordViewController);
+
+        mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Pattern);
+        verify(mView).setOneHandedMode(false);
+    }
+
+    @Test
+    public void showSecurityScreen_oneHandedMode_bothFlagsEnabled_oneHandedMode() {
+        setUpKeyguardFlags(
+                /* deviceConfigCanUseOneHandedKeyguard= */true,
+                /* sysuiResourceCanUseOneHandedKeyguard= */true);
+
+        when(mKeyguardSecurityViewFlipperController.getSecurityView(
+                eq(SecurityMode.Pattern), any(KeyguardSecurityCallback.class)))
+                .thenReturn((KeyguardInputViewController) mKeyguardPasswordViewController);
+
+        mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Pattern);
+        verify(mView).setOneHandedMode(true);
+    }
+
+    @Test
+    public void showSecurityScreen_twoHandedMode_bothFlagsEnabled_noOneHandedMode() {
+        setUpKeyguardFlags(
+                /* deviceConfigCanUseOneHandedKeyguard= */true,
+                /* sysuiResourceCanUseOneHandedKeyguard= */true);
+
+        when(mKeyguardSecurityViewFlipperController.getSecurityView(
+                eq(SecurityMode.Password), any(KeyguardSecurityCallback.class)))
+                .thenReturn((KeyguardInputViewController) mKeyguardPasswordViewController);
+
+        mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Password);
+        verify(mView).setOneHandedMode(false);
+    }
+
+    private void setUpKeyguardFlags(
+            boolean deviceConfigCanUseOneHandedKeyguard,
+            boolean sysuiResourceCanUseOneHandedKeyguard) {
+        when(mResources.getBoolean(
+                com.android.internal.R.bool.config_enableDynamicKeyguardPositioning))
+                .thenReturn(deviceConfigCanUseOneHandedKeyguard);
+        when(mResources.getBoolean(
+                R.bool.can_use_one_handed_bouncer))
+                .thenReturn(sysuiResourceCanUseOneHandedKeyguard);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index f5916e7..0276323 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -28,7 +28,6 @@
 import android.graphics.Insets;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
-import android.testing.TestableResources;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowInsets;
@@ -37,8 +36,6 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 
 import org.junit.Before;
@@ -57,11 +54,6 @@
     private static final int FAKE_MEASURE_SPEC =
             View.MeasureSpec.makeMeasureSpec(SCREEN_WIDTH, View.MeasureSpec.EXACTLY);
 
-    private static final SecurityMode ONE_HANDED_SECURITY_MODE = SecurityMode.PIN;
-    private static final SecurityMode TWO_HANDED_SECURITY_MODE = SecurityMode.Password;
-
-
-
     @Rule
     public MockitoRule mRule = MockitoJUnit.rule();
 
@@ -90,45 +82,8 @@
     }
 
     @Test
-    public void onMeasure_usesFullWidthWithoutOneHandedMode() {
-        setUpKeyguard(
-                /* deviceConfigCanUseOneHandedKeyguard= */false,
-                /* sysuiResourceCanUseOneHandedKeyguard= */ false,
-                ONE_HANDED_SECURITY_MODE);
-
-        mKeyguardSecurityContainer.measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC);
-
-        verify(mSecurityViewFlipper).measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC);
-    }
-
-    @Test
-    public void onMeasure_usesFullWidthWithDeviceFlagDisabled() {
-        setUpKeyguard(
-                /* deviceConfigCanUseOneHandedKeyguard= */false,
-                /* sysuiResourceCanUseOneHandedKeyguard= */ true,
-                ONE_HANDED_SECURITY_MODE);
-
-        mKeyguardSecurityContainer.measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC);
-        verify(mSecurityViewFlipper).measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC);
-    }
-
-    @Test
-    public void onMeasure_usesFullWidthWithSysUIFlagDisabled() {
-        setUpKeyguard(
-                /* deviceConfigCanUseOneHandedKeyguard= */true,
-                /* sysuiResourceCanUseOneHandedKeyguard= */ false,
-                ONE_HANDED_SECURITY_MODE);
-
-        mKeyguardSecurityContainer.measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC);
-        verify(mSecurityViewFlipper).measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC);
-    }
-
-    @Test
-    public void onMeasure_usesHalfWidthWithFlagsEnabled() {
-        setUpKeyguard(
-                /* deviceConfigCanUseOneHandedKeyguard= */true,
-                /* sysuiResourceCanUseOneHandedKeyguard= */ true,
-                ONE_HANDED_SECURITY_MODE);
+    public void onMeasure_usesHalfWidthWithOneHandedModeEnabled() {
+        mKeyguardSecurityContainer.setOneHandedMode(/* oneHandedMode= */true);
 
         int halfWidthMeasureSpec =
                 View.MeasureSpec.makeMeasureSpec(SCREEN_WIDTH / 2, View.MeasureSpec.EXACTLY);
@@ -138,11 +93,8 @@
     }
 
     @Test
-    public void onMeasure_usesFullWidthForFullScreenIme() {
-        setUpKeyguard(
-                /* deviceConfigCanUseOneHandedKeyguard= */true,
-                /* sysuiResourceCanUseOneHandedKeyguard= */ true,
-                TWO_HANDED_SECURITY_MODE);
+    public void onMeasure_usesFullWidthWithOneHandedModeDisabled() {
+        mKeyguardSecurityContainer.setOneHandedMode(/* oneHandedMode= */false);
 
         mKeyguardSecurityContainer.measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC);
         verify(mSecurityViewFlipper).measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC);
@@ -153,10 +105,7 @@
         int imeInsetAmount = 100;
         int systemBarInsetAmount = 10;
 
-        setUpKeyguard(
-                /* deviceConfigCanUseOneHandedKeyguard= */false,
-                /* sysuiResourceCanUseOneHandedKeyguard= */ false,
-                ONE_HANDED_SECURITY_MODE);
+        mKeyguardSecurityContainer.setOneHandedMode(/* oneHandedMode= */false);
 
         Insets imeInset = Insets.of(0, 0, 0, imeInsetAmount);
         Insets systemBarInset = Insets.of(0, 0, 0, systemBarInsetAmount);
@@ -180,10 +129,7 @@
         int imeInsetAmount = 0;
         int systemBarInsetAmount = 10;
 
-        setUpKeyguard(
-                /* deviceConfigCanUseOneHandedKeyguard= */false,
-                /* sysuiResourceCanUseOneHandedKeyguard= */ false,
-                ONE_HANDED_SECURITY_MODE);
+        mKeyguardSecurityContainer.setOneHandedMode(/* oneHandedMode= */false);
 
         Insets imeInset = Insets.of(0, 0, 0, imeInsetAmount);
         Insets systemBarInset = Insets.of(0, 0, 0, systemBarInsetAmount);
@@ -201,56 +147,41 @@
         verify(mSecurityViewFlipper).measure(FAKE_MEASURE_SPEC, expectedHeightMeasureSpec);
     }
 
-    private void setupForUpdateKeyguardPosition(SecurityMode securityMode) {
-        setUpKeyguard(
-                /* deviceConfigCanUseOneHandedKeyguard= */true,
-                /* sysuiResourceCanUseOneHandedKeyguard= */ true,
-                securityMode);
+    private void setupForUpdateKeyguardPosition(boolean oneHandedMode) {
+        mKeyguardSecurityContainer.setOneHandedMode(oneHandedMode);
+        mKeyguardSecurityContainer.setOneHandedModeLeftAligned(true, false);
 
         mKeyguardSecurityContainer.measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC);
         mKeyguardSecurityContainer.layout(0, 0, SCREEN_WIDTH, SCREEN_WIDTH);
 
-        // Start off left-aligned. This should happen anyway, but just do this to ensure
-        // definitely move to the left.
-        mKeyguardSecurityContainer.updateKeyguardPosition(0.0f);
-
         // Clear any interactions with the mock so we know the interactions definitely come from the
         // below testing.
         reset(mSecurityViewFlipper);
     }
 
     @Test
-    public void updateKeyguardPosition_movesKeyguard() {
-        setupForUpdateKeyguardPosition(ONE_HANDED_SECURITY_MODE);
+    public void setIsLeftAligned_movesKeyguard() {
+        setupForUpdateKeyguardPosition(/* oneHandedMode= */ true);
 
-        mKeyguardSecurityContainer.updateKeyguardPosition((SCREEN_WIDTH / 4f) * 3f);
+        mKeyguardSecurityContainer.setOneHandedModeLeftAligned(
+                /* leftAligned= */false, /* animate= */false);
         verify(mSecurityViewFlipper).setTranslationX(SCREEN_WIDTH / 2.0f);
 
-        mKeyguardSecurityContainer.updateKeyguardPosition(0.0f);
+        mKeyguardSecurityContainer.setOneHandedModeLeftAligned(
+                /* leftAligned= */true, /* animate= */false);
         verify(mSecurityViewFlipper).setTranslationX(0.0f);
     }
 
     @Test
-    public void updateKeyguardPosition_doesntMoveTwoHandedKeyguard() {
-        setupForUpdateKeyguardPosition(TWO_HANDED_SECURITY_MODE);
+    public void setIsLeftAligned_doesntMoveTwoHandedKeyguard() {
+        setupForUpdateKeyguardPosition(/* oneHandedMode= */ false);
 
-        mKeyguardSecurityContainer.updateKeyguardPosition((SCREEN_WIDTH / 4f) * 3f);
+        mKeyguardSecurityContainer.setOneHandedModeLeftAligned(
+                /* leftAligned= */false, /* animate= */false);
         verify(mSecurityViewFlipper, never()).setTranslationX(anyInt());
 
-        mKeyguardSecurityContainer.updateKeyguardPosition(0.0f);
+        mKeyguardSecurityContainer.setOneHandedModeLeftAligned(
+                /* leftAligned= */true, /* animate= */false);
         verify(mSecurityViewFlipper, never()).setTranslationX(anyInt());
     }
-
-    private void setUpKeyguard(
-            boolean deviceConfigCanUseOneHandedKeyguard,
-            boolean sysuiResourceCanUseOneHandedKeyguard,
-            SecurityMode securityMode) {
-        TestableResources testableResources = mContext.getOrCreateTestableResources();
-        testableResources.addOverride(
-                com.android.internal.R.bool.config_enableDynamicKeyguardPositioning,
-                deviceConfigCanUseOneHandedKeyguard);
-        testableResources.addOverride(R.bool.can_use_one_handed_bouncer,
-                sysuiResourceCanUseOneHandedKeyguard);
-        mKeyguardSecurityContainer.updateLayoutForSecurityMode(securityMode);
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java
index 9621bed..8bb9d42 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java
@@ -16,9 +16,6 @@
 
 package com.android.systemui.accessibility;
 
-import static android.view.WindowInsets.Type.systemGestures;
-
-import android.graphics.Insets;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.view.Display;
@@ -79,14 +76,11 @@
 
     @Override
     public WindowMetrics getCurrentWindowMetrics() {
-        final Insets systemGesturesInsets = Insets.of(0, 0, 0, 10);
-        final WindowInsets insets = new WindowInsets.Builder()
-                .setInsets(systemGestures(), systemGesturesInsets)
-                .build();
+        final WindowMetrics realMetrics = mWindowManager.getCurrentWindowMetrics();
         final WindowMetrics windowMetrics = new WindowMetrics(
-                mWindowBounds == null ? mWindowManager.getCurrentWindowMetrics().getBounds()
+                mWindowBounds == null ? realMetrics.getBounds()
                         : mWindowBounds,
-                mWindowInsets == null ? insets : mWindowInsets);
+                mWindowInsets == null ?  realMetrics.getWindowInsets() : mWindowInsets);
         return windowMetrics;
     }
 
@@ -106,10 +100,20 @@
         return (WindowManager.LayoutParams) mView.getLayoutParams();
     }
 
+    /**
+     * Sets the given window bounds to current window metrics.
+     *
+     * @param bounds the window bounds
+     */
     public void setWindowBounds(Rect bounds) {
         mWindowBounds = bounds;
     }
 
+    /**
+     * Sets the given window insets to the current window metics.
+     *
+     * @param insets the window insets.
+     */
     public void setWindowInsets(WindowInsets insets) {
         mWindowInsets = insets;
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index f62069d..76cc7a0e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -17,6 +17,7 @@
 package com.android.systemui.accessibility;
 
 import static android.view.Choreographer.FrameCallback;
+import static android.view.WindowInsets.Type.systemGestures;
 import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP;
@@ -45,6 +46,8 @@
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.res.Resources;
+import android.graphics.Insets;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.Handler;
 import android.os.SystemClock;
@@ -55,6 +58,7 @@
 import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.View;
+import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityNodeInfo;
 
@@ -239,11 +243,13 @@
         mInstrumentation.runOnMainSync(() -> {
             mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_DENSITY);
             mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_ORIENTATION);
+            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_LOCALE);
+            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE);
         });
     }
 
     @Test
-    public void onOrientationChanged_enabled_updateDisplayRotationAndLayout() {
+    public void onOrientationChanged_enabled_updateDisplayRotationAndCenterStayAtSamePosition() {
         final Display display = Mockito.spy(mContext.getDisplay());
         when(display.getRotation()).thenReturn(Surface.ROTATION_90);
         when(mContext.getDisplay()).thenReturn(display);
@@ -251,13 +257,22 @@
             mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
                     Float.NaN);
         });
+        final PointF expectedCenter = new PointF(mWindowMagnificationController.getCenterY(),
+                mWindowMagnificationController.getCenterX());
+        final Rect windowBounds = new Rect(mWindowManager.getCurrentWindowMetrics().getBounds());
+        // Rotate the window 90 degrees.
+        windowBounds.set(windowBounds.top, windowBounds.left, windowBounds.bottom,
+                windowBounds.right);
+        mWindowManager.setWindowBounds(windowBounds);
 
         mInstrumentation.runOnMainSync(() -> {
             mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_ORIENTATION);
         });
 
         assertEquals(Surface.ROTATION_90, mWindowMagnificationController.mRotation);
-        // The first invocation is called when the surface is created.
+        final PointF actualCenter = new PointF(mWindowMagnificationController.getCenterX(),
+                mWindowMagnificationController.getCenterY());
+        assertEquals(expectedCenter, actualCenter);
         verify(mWindowManager, times(2)).updateViewLayout(any(), any());
     }
 
@@ -275,6 +290,33 @@
     }
 
     @Test
+    public void onScreenSizeChanged_enabledAtTheCenterOfScreen_keepSameWindowSizeRatio() {
+        // The default position is at the center of the screen.
+        final float expectedRatio = 0.5f;
+        final Rect testWindowBounds = new Rect(
+                mWindowManager.getCurrentWindowMetrics().getBounds());
+        testWindowBounds.set(testWindowBounds.left, testWindowBounds.top,
+                testWindowBounds.right + 100, testWindowBounds.bottom + 100);
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
+                    Float.NaN);
+        });
+        mWindowManager.setWindowBounds(testWindowBounds);
+
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE);
+        });
+
+        // The ratio of center to window size should be the same.
+        assertEquals(expectedRatio,
+                mWindowMagnificationController.getCenterX() / testWindowBounds.width(),
+                0);
+        assertEquals(expectedRatio,
+                mWindowMagnificationController.getCenterY() / testWindowBounds.height(),
+                0);
+    }
+
+    @Test
     public void onDensityChanged_enabled_updateDimensionsAndResetWindowMagnification() {
         mInstrumentation.runOnMainSync(() -> {
             mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
@@ -419,9 +461,12 @@
     }
 
     @Test
-    public void moveWindowMagnificationToTheBottom_enabled_overlapFlagIsTrue() {
-        final WindowManager wm = mContext.getSystemService(WindowManager.class);
-        final Rect bounds = wm.getCurrentWindowMetrics().getBounds();
+    public void moveWindowMagnificationToTheBottom_enabledWithGestureInset_overlapFlagIsTrue() {
+        final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+        final WindowInsets testInsets = new WindowInsets.Builder()
+                .setInsets(systemGestures(), Insets.of(0, 0, 0, 10))
+                .build();
+        mWindowManager.setWindowInsets(testInsets);
         mInstrumentation.runOnMainSync(() -> {
             mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
                     Float.NaN);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index 578c2d9..32abbc7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -104,7 +104,6 @@
     @Mock private IWindowManager mWindowManager;
     @Mock private Executor mBackgroundExecutor;
     @Mock private UiEventLogger mUiEventLogger;
-    @Mock private GlobalActionsInfoProvider mInfoProvider;
     @Mock private RingerModeTracker mRingerModeTracker;
     @Mock private RingerModeLiveData mRingerModeLiveData;
     @Mock private SysUiState mSysUiState;
@@ -151,7 +150,6 @@
                 mWindowManager,
                 mBackgroundExecutor,
                 mUiEventLogger,
-                mInfoProvider,
                 mRingerModeTracker,
                 mSysUiState,
                 mHandler,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsInfoProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsInfoProviderTest.kt
deleted file mode 100644
index 302a8d3..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsInfoProviderTest.kt
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.android.systemui.globalactions
-
-import android.content.Context
-import android.content.SharedPreferences
-import android.content.res.Configuration
-import android.content.res.Resources
-import android.service.quickaccesswallet.QuickAccessWalletClient
-import android.testing.AndroidTestingRunner
-import android.view.ViewGroup
-import androidx.test.filters.SmallTest
-import com.android.systemui.R
-import com.android.systemui.controls.controller.ControlsController
-import com.android.systemui.globalactions.GlobalActionsInfoProvider
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.SysuiTestCase
-import junit.framework.Assert.assertFalse
-import junit.framework.Assert.assertTrue
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.anyBoolean
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.ArgumentMatchers.anyObject
-import org.mockito.ArgumentMatchers.anyString
-import org.mockito.Mock
-import org.mockito.Mockito
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.spy
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-import org.mockito.Mockito.`when` as whenever
-
-private const val PREFERENCE = "global_actions_info_prefs"
-private const val KEY_VIEW_COUNT = "view_count"
-
-private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-class GlobalActionsInfoProviderTest : SysuiTestCase() {
-
-    @Mock private lateinit var walletClient: QuickAccessWalletClient
-    @Mock private lateinit var controlsController: ControlsController
-    @Mock private lateinit var activityStarter: ActivityStarter
-    @Mock private lateinit var mockContext: Context
-    @Mock private lateinit var mockResources: Resources
-    @Mock private lateinit var sharedPrefs: SharedPreferences
-    @Mock private lateinit var sharedPrefsEditor: SharedPreferences.Editor
-
-    private lateinit var infoProvider: GlobalActionsInfoProvider
-
-    @Before
-    fun setup() {
-        MockitoAnnotations.initMocks(this)
-        mockContext = spy(context)
-        mockResources = spy(context.resources)
-        whenever(mockContext.resources).thenReturn(mockResources)
-        whenever(mockResources.getBoolean(R.bool.global_actions_show_change_info))
-                .thenReturn(true)
-        whenever(mockContext.getSharedPreferences(eq(PREFERENCE), anyInt()))
-                .thenReturn(sharedPrefs)
-        whenever(sharedPrefs.edit()).thenReturn(sharedPrefsEditor)
-        whenever(sharedPrefsEditor.putInt(anyString(), anyInt())).thenReturn(sharedPrefsEditor)
-        whenever(sharedPrefsEditor.putBoolean(anyString(), anyBoolean()))
-                .thenReturn(sharedPrefsEditor)
-
-        infoProvider = GlobalActionsInfoProvider(
-                mockContext,
-                walletClient,
-                controlsController,
-                activityStarter
-        )
-    }
-
-    @Test
-    fun testIsEligible_noCards() {
-        whenever(sharedPrefs.contains(eq(KEY_VIEW_COUNT))).thenReturn(false)
-        whenever(walletClient.isWalletFeatureAvailable).thenReturn(false)
-
-        assertFalse(infoProvider.shouldShowMessage())
-    }
-
-    @Test
-    fun testIsEligible_hasCards() {
-        whenever(sharedPrefs.contains(eq(KEY_VIEW_COUNT))).thenReturn(false)
-        whenever(walletClient.isWalletFeatureAvailable).thenReturn(true)
-
-        assertTrue(infoProvider.shouldShowMessage())
-    }
-
-    @Test
-    fun testNotEligible_shouldNotShow() {
-        whenever(mockResources.getBoolean(R.bool.global_actions_show_change_info))
-                .thenReturn(false)
-
-        assertFalse(infoProvider.shouldShowMessage())
-    }
-
-    @Test
-    fun testTooManyButtons_doesNotAdd() {
-        val configuration = Configuration()
-        configuration.orientation = Configuration.ORIENTATION_LANDSCAPE
-        whenever(mockResources.configuration).thenReturn(configuration)
-
-        val parent = mock(ViewGroup::class.java)
-        infoProvider.addPanel(mockContext, parent, 5, { })
-
-        verify(parent, never()).addView(anyObject(), anyInt())
-    }
-
-    @Test
-    fun testLimitTimesShown() {
-        whenever(sharedPrefs.getInt(eq(KEY_VIEW_COUNT), anyInt())).thenReturn(4)
-
-        assertFalse(infoProvider.shouldShowMessage())
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
index d2527c6..e9a7c73 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
@@ -81,6 +81,8 @@
     private NavigationBar mDefaultNavBar;
     private NavigationBar mSecondaryNavBar;
 
+    private CommandQueue mCommandQueue = mock(CommandQueue.class);
+
     private static final int SECONDARY_DISPLAY = 1;
 
     @Before
@@ -99,7 +101,7 @@
                         mock(StatusBarStateController.class),
                         mock(SysUiState.class),
                         mock(BroadcastDispatcher.class),
-                        mock(CommandQueue.class),
+                        mCommandQueue,
                         Optional.of(mock(Pip.class)),
                         Optional.of(mock(LegacySplitScreen.class)),
                         Optional.of(mock(Recents.class)),
@@ -112,6 +114,8 @@
                         mock(UiEventLogger.class),
                         mock(NavigationBarOverlayController.class),
                         mock(ConfigurationController.class),
+                        mock(NavigationBarA11yHelper.class),
+                        mock(TaskbarDelegate.class),
                         mock(UserTracker.class)));
         initializeNavigationBars();
     }
@@ -275,4 +279,9 @@
 
         verify(mSecondaryNavBar).disableAnimationsDuringHide(eq(500L));
     }
+
+    @Test
+    public void test3ButtonTaskbarFlagDisabledNoRegister() {
+        verify(mCommandQueue, never()).addCallback(any(TaskbarDelegate.class));
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java
index eac68f6..b991976 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java
@@ -31,9 +31,6 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.SysuiTestableContext;
-import com.android.systemui.navigationbar.buttons.KeyButtonDrawable;
-import com.android.systemui.navigationbar.RotationButton;
-import com.android.systemui.navigationbar.RotationButtonController;
 import com.android.systemui.statusbar.policy.RotationLockController;
 
 import org.junit.Before;
@@ -46,7 +43,6 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class NavigationBarRotationContextTest extends SysuiTestCase {
-    static final int RES_UNDEF = 0;
     static final int DEFAULT_ROTATE = 0;
 
     @Rule
@@ -66,7 +62,6 @@
         mRotationButtonController.setRotationButton(mRotationButton, (visibility) -> {});
         // Due to a mockito issue, only spy the object after setting the initial state
         mRotationButtonController = spy(mRotationButtonController);
-        final KeyButtonDrawable kbd = mock(KeyButtonDrawable.class);
         doReturn(view).when(mRotationButton).getCurrentView();
         doReturn(true).when(mRotationButton).acceptRotationProposal();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index a570675..c606a43 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -280,6 +280,7 @@
                 mHandler,
                 mock(NavigationBarOverlayController.class),
                 mUiEventLogger,
+                mock(NavigationBarA11yHelper.class),
                 mock(UserTracker.class)));
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index 21c6292..d5a2919 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -33,6 +33,7 @@
 import android.hardware.biometrics.PromptInfo;
 import android.hardware.fingerprint.IUdfpsHbmListener;
 import android.os.Bundle;
+import android.view.InsetsState;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
 
@@ -124,24 +125,25 @@
     public void testOnSystemBarAttributesChanged() {
         doTestOnSystemBarAttributesChanged(DEFAULT_DISPLAY, 1,
                 new AppearanceRegion[]{new AppearanceRegion(2, new Rect())}, false,
-                BEHAVIOR_DEFAULT, false);
+                BEHAVIOR_DEFAULT, new InsetsState(), "test");
     }
 
     @Test
     public void testOnSystemBarAttributesChangedForSecondaryDisplay() {
         doTestOnSystemBarAttributesChanged(SECONDARY_DISPLAY, 1,
                 new AppearanceRegion[]{new AppearanceRegion(2, new Rect())}, false,
-                BEHAVIOR_DEFAULT, false);
+                BEHAVIOR_DEFAULT, new InsetsState(), "test");
     }
 
     private void doTestOnSystemBarAttributesChanged(int displayId, @Appearance int appearance,
             AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-            @Behavior int behavior, boolean isFullscreen) {
+            @Behavior int behavior, InsetsState requestedState, String packageName) {
         mCommandQueue.onSystemBarAttributesChanged(displayId, appearance, appearanceRegions,
-                navbarColorManagedByIme, behavior, isFullscreen);
+                navbarColorManagedByIme, behavior, requestedState, packageName);
         waitForIdleSync();
         verify(mCallbacks).onSystemBarAttributesChanged(eq(displayId), eq(appearance),
-                eq(appearanceRegions), eq(navbarColorManagedByIme), eq(behavior), eq(isFullscreen));
+                eq(appearanceRegions), eq(navbarColorManagedByIme), eq(behavior),
+                eq(requestedState), eq(packageName));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
similarity index 91%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollerControllerTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index f376e88..6ee2f20 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -92,7 +92,7 @@
  */
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
-public class NotificationStackScrollerControllerTest extends SysuiTestCase {
+public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
 
     @Mock private NotificationGutsManager mNotificationGutsManager;
     @Mock private HeadsUpManagerPhone mHeadsUpManager;
@@ -232,16 +232,15 @@
         reset(mNotificationStackScrollLayout);
         mController.updateShowEmptyShadeView();
         verify(mNotificationStackScrollLayout).updateEmptyShadeView(
-                true /* visible */,
-
-                true /* notifVisibleInShade */);
+                /* visible= */ true,
+                /* notifVisibleInShade= */ true);
 
         setupShowEmptyShadeViewState(stateListener, false);
         reset(mNotificationStackScrollLayout);
         mController.updateShowEmptyShadeView();
         verify(mNotificationStackScrollLayout).updateEmptyShadeView(
-                false /* visible */,
-                true /* notifVisibleInShade */);
+                /* visible= */ false,
+                /* notifVisibleInShade= */ true);
     }
 
     @Test
@@ -257,15 +256,42 @@
         reset(mNotificationStackScrollLayout);
         mController.updateShowEmptyShadeView();
         verify(mNotificationStackScrollLayout).updateEmptyShadeView(
-                true /* visible */,
-                false /* notifVisibleInShade */);
+                /* visible= */ true,
+                /* notifVisibleInShade= */ false);
 
         setupShowEmptyShadeViewState(stateListener, false);
         reset(mNotificationStackScrollLayout);
         mController.updateShowEmptyShadeView();
         verify(mNotificationStackScrollLayout).updateEmptyShadeView(
-                false /* visible */,
-                false /* notifVisibleInShade */);
+                /* visible= */ false,
+                /* notifVisibleInShade= */ false);
+    }
+
+    @Test
+    public void testUpdateEmptyShadeView_splitShadeMode_alwaysShowEmptyView() {
+        when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
+        mController.attach(mNotificationStackScrollLayout);
+        verify(mSysuiStatusBarStateController).addCallback(
+                mStateListenerArgumentCaptor.capture(), anyInt());
+        StatusBarStateController.StateListener stateListener =
+                mStateListenerArgumentCaptor.getValue();
+        when(mNotificationStackScrollLayout.isUsingSplitNotificationShade()).thenReturn(true);
+        stateListener.onStateChanged(SHADE);
+        mController.getView().removeAllViews();
+
+        mController.setQsExpanded(false);
+        reset(mNotificationStackScrollLayout);
+        mController.updateShowEmptyShadeView();
+        verify(mNotificationStackScrollLayout).updateEmptyShadeView(
+                /* visible= */ true,
+                /* notifVisibleInShade= */ false);
+
+        mController.setQsExpanded(true);
+        reset(mNotificationStackScrollLayout);
+        mController.updateShowEmptyShadeView();
+        verify(mNotificationStackScrollLayout).updateEmptyShadeView(
+                /* visible= */ true,
+                /* notifVisibleInShade= */ false);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java
index cdfab1e..c3adee9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java
@@ -102,7 +102,8 @@
                 null /* appearanceRegions */,
                 false /* navbarColorManagedByIme */,
                 BEHAVIOR_DEFAULT,
-                false /* isFullscreen */);
+                null /* requestedState */,
+                null /* packageName */);
         assertTrue(mLightsOutNotifController.areLightsOut());
     }
 
@@ -114,7 +115,8 @@
                 null /* appearanceRegions */,
                 false /* navbarColorManagedByIme */,
                 BEHAVIOR_DEFAULT,
-                false /* isFullscreen */);
+                null /* requestedState */,
+                null /* packageName */);
         assertFalse(mLightsOutNotifController.areLightsOut());
     }
 
@@ -144,7 +146,8 @@
                 null /* appearanceRegions */,
                 false /* navbarColorManagedByIme */,
                 BEHAVIOR_DEFAULT,
-                false /* isFullscreen */);
+                null /* requestedState */,
+                null /* packageName */);
 
         // THEN we should show dot
         assertTrue(mLightsOutNotifController.shouldShowDot());
@@ -163,7 +166,8 @@
                 null /* appearanceRegions */,
                 false /* navbarColorManagedByIme */,
                 BEHAVIOR_DEFAULT,
-                false /* isFullscreen */);
+                null /* requestedState */,
+                null /* packageName */);
 
         // THEN we shouldn't show the dot
         assertFalse(mLightsOutNotifController.shouldShowDot());
@@ -182,7 +186,8 @@
                 null /* appearanceRegions */,
                 false /* navbarColorManagedByIme */,
                 BEHAVIOR_DEFAULT,
-                false /* isFullscreen */);
+                null /* requestedState */,
+                null /* packageName */);
 
         // THEN we shouldn't show the dot
         assertFalse(mLightsOutNotifController.shouldShowDot());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index a6fc02d..1540b14 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -18,6 +18,8 @@
 
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 
+import static com.android.keyguard.KeyguardClockSwitch.LARGE;
+import static com.android.keyguard.KeyguardClockSwitch.SMALL;
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
 import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED;
@@ -35,6 +37,7 @@
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -573,17 +576,19 @@
     }
 
     @Test
-    public void testKeyguardStatusView_isAlignedToGuidelineInSplitShadeMode() {
-        mNotificationPanelViewController.updateResources();
-
-        assertThat(getConstraintSetLayout(R.id.keyguard_status_view).endToEnd)
-                .isEqualTo(ConstraintSet.PARENT_ID);
-
+    public void testKeyguardStatusViewInSplitShade_changesConstraintsDependingOnNotifications() {
+        mStatusBarStateController.setState(KEYGUARD);
         enableSplitShade();
-        mNotificationPanelViewController.updateResources();
 
+        when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
+        mNotificationPanelViewController.updateResources();
         assertThat(getConstraintSetLayout(R.id.keyguard_status_view).endToEnd)
                 .isEqualTo(R.id.qs_edge_guideline);
+
+        when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
+        mNotificationPanelViewController.updateResources();
+        assertThat(getConstraintSetLayout(R.id.keyguard_status_view).endToEnd)
+                .isEqualTo(ConstraintSet.PARENT_ID);
     }
 
     @Test
@@ -744,6 +749,38 @@
         verify(mTapAgainViewController).show();
     }
 
+    @Test
+    public void testSwitchesToCorrectClockInSinglePaneShade() {
+        mStatusBarStateController.setState(KEYGUARD);
+
+        when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
+        triggerPositionClockAndNotifications();
+        verify(mKeyguardStatusViewController).displayClock(LARGE);
+
+        when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
+        mNotificationPanelViewController.closeQs();
+        verify(mKeyguardStatusViewController).displayClock(SMALL);
+    }
+
+    @Test
+    public void testSwitchesToCorrectClockInSplitShade() {
+        mStatusBarStateController.setState(KEYGUARD);
+        enableSplitShade();
+
+        when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
+        triggerPositionClockAndNotifications();
+        verify(mKeyguardStatusViewController).displayClock(LARGE);
+
+        when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
+        triggerPositionClockAndNotifications();
+        verify(mKeyguardStatusViewController, times(2)).displayClock(LARGE);
+        verify(mKeyguardStatusViewController, never()).displayClock(SMALL);
+    }
+
+    private void triggerPositionClockAndNotifications() {
+        mNotificationPanelViewController.closeQs();
+    }
+
     private FalsingManager.FalsingTapListener getFalsingTapListener() {
         for (View.OnAttachStateChangeListener listener : mOnAttachStateChangeListeners) {
             listener.onViewAttachedToWindow(mView);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 678b193..2685b76 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -576,6 +576,49 @@
     }
 
     @Test
+    public void disableClipQsScrimWithoutStateTransition_updatesTintAndAlpha() {
+        mScrimController.setClipsQsScrim(true);
+        mScrimController.transitionTo(ScrimState.BOUNCER);
+
+        mScrimController.setClipsQsScrim(false);
+
+        finishAnimationsImmediately();
+        // Front scrim should be transparent
+        // Back scrim should be visible without tint
+        assertScrimAlpha(Map.of(
+                mScrimInFront, TRANSPARENT,
+                mNotificationsScrim, TRANSPARENT,
+                mScrimBehind, OPAQUE));
+        assertScrimTinted(Map.of(
+                mScrimInFront, false,
+                mScrimBehind, false,
+                mNotificationsScrim, false
+        ));
+    }
+
+    @Test
+    public void enableClipQsScrimWithoutStateTransition_updatesTintAndAlpha() {
+        mScrimController.setClipsQsScrim(false);
+        mScrimController.transitionTo(ScrimState.BOUNCER);
+
+        mScrimController.setClipsQsScrim(true);
+
+        finishAnimationsImmediately();
+        // Front scrim should be transparent
+        // Back scrim should be clipping QS
+        // Notif scrim should be visible without tint
+        assertScrimAlpha(Map.of(
+                mScrimInFront, TRANSPARENT,
+                mNotificationsScrim, OPAQUE,
+                mScrimBehind, OPAQUE));
+        assertScrimTinted(Map.of(
+                mScrimInFront, false,
+                mScrimBehind, true,
+                mNotificationsScrim, false
+        ));
+    }
+
+    @Test
     public void transitionToBouncer() {
         mScrimController.transitionTo(ScrimState.BOUNCER_SCRIMMED);
         finishAnimationsImmediately();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index f243077..d6492c6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -120,6 +120,7 @@
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TaskStackListenerImpl;
 
 import com.google.common.collect.ImmutableList;
@@ -331,7 +332,8 @@
                 mPositioner,
                 mock(DisplayController.class),
                 syncExecutor,
-                mock(Handler.class));
+                mock(Handler.class),
+                mock(SyncTransactionQueue.class));
         mBubbleController.setExpandListener(mBubbleExpandListener);
         spyOn(mBubbleController);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
index e4c7800..db8a08c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
@@ -100,6 +100,7 @@
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TaskStackListenerImpl;
 
 import org.junit.Before;
@@ -275,7 +276,8 @@
                 mPositioner,
                 mock(DisplayController.class),
                 syncExecutor,
-                mock(Handler.class));
+                mock(Handler.class),
+                mock(SyncTransactionQueue.class));
         mBubbleController.setExpandListener(mBubbleExpandListener);
         spyOn(mBubbleController);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
index cd5aa9a..7b77cb0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
@@ -32,6 +32,7 @@
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TaskStackListenerImpl;
 
 /**
@@ -54,11 +55,12 @@
             BubblePositioner positioner,
             DisplayController displayController,
             ShellExecutor shellMainExecutor,
-            Handler shellMainHandler) {
+            Handler shellMainHandler,
+            SyncTransactionQueue syncQueue) {
         super(context, data, Runnable::run, floatingContentCoordinator, dataRepository,
                 statusBarService, windowManager, windowManagerShellWrapper, launcherApps,
                 bubbleLogger, taskStackListener, shellTaskOrganizer, positioner, displayController,
-                shellMainExecutor, shellMainHandler);
+                shellMainExecutor, shellMainHandler, syncQueue);
         setInflateSynchronously(true);
         initialize();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
index 5691660..8480702 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
@@ -41,6 +41,7 @@
 import com.android.wm.shell.onehanded.OneHandedEventCallback;
 import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
 import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.splitscreen.SplitScreen;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -69,6 +70,7 @@
     @Mock SysUiState mSysUiState;
     @Mock Pip mPip;
     @Mock LegacySplitScreen mLegacySplitScreen;
+    @Mock SplitScreen mSplitScreen;
     @Mock OneHanded mOneHanded;
     @Mock HideDisplayCutout mHideDisplayCutout;
     @Mock WakefulnessLifecycle mWakefulnessLifecycle;
@@ -81,7 +83,7 @@
         MockitoAnnotations.initMocks(this);
 
         mWMShell = new WMShell(mContext, Optional.of(mPip), Optional.of(mLegacySplitScreen),
-                Optional.of(mOneHanded), Optional.of(mHideDisplayCutout),
+                Optional.of(mSplitScreen), Optional.of(mOneHanded), Optional.of(mHideDisplayCutout),
                 Optional.of(mShellCommandHandler), mCommandQueue, mConfigurationController,
                 mKeyguardUpdateMonitor, mNavigationModeController,
                 mScreenLifecycle, mSysUiState, mProtoTracer, mWakefulnessLifecycle,
@@ -96,8 +98,15 @@
     }
 
     @Test
+    public void initLegacySplitScreen_registersCallbacks() {
+        mWMShell.initLegacySplitScreen(mLegacySplitScreen);
+
+        verify(mKeyguardUpdateMonitor).registerCallback(any(KeyguardUpdateMonitorCallback.class));
+    }
+
+    @Test
     public void initSplitScreen_registersCallbacks() {
-        mWMShell.initSplitScreen(mLegacySplitScreen);
+        mWMShell.initSplitScreen(mSplitScreen);
 
         verify(mKeyguardUpdateMonitor).registerCallback(any(KeyguardUpdateMonitorCallback.class));
     }
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index e9c9899..ee80dae 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -21,8 +21,13 @@
 import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT_STATUS;
 import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP;
 import static android.accessibilityservice.AccessibilityServiceInfo.DEFAULT;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CLIENT;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CONNECTION;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MANAGER_INTERNAL;
 import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
 import static android.view.accessibility.AccessibilityInteractionClient.CALL_STACK;
+import static android.view.accessibility.AccessibilityInteractionClient.IGNORE_CALL_STACK;
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS;
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS;
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
@@ -31,6 +36,7 @@
 import android.accessibilityservice.AccessibilityGestureEvent;
 import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.AccessibilityTrace;
 import android.accessibilityservice.IAccessibilityServiceClient;
 import android.accessibilityservice.IAccessibilityServiceConnection;
 import android.annotation.NonNull;
@@ -103,10 +109,9 @@
         FingerprintGestureDispatcher.FingerprintGestureClient {
     private static final boolean DEBUG = false;
     private static final String LOG_TAG = "AbstractAccessibilityServiceConnection";
-    private static final String TRACE_A11Y_SERVICE_CONNECTION =
-            LOG_TAG + ".IAccessibilityServiceConnection";
-    private static final String TRACE_A11Y_SERVICE_CLIENT =
-            LOG_TAG + ".IAccessibilityServiceClient";
+    private static final String TRACE_SVC_CONN = LOG_TAG + ".IAccessibilityServiceConnection";
+    private static final String TRACE_SVC_CLIENT = LOG_TAG + ".IAccessibilityServiceClient";
+    private static final String TRACE_WM = "WindowManagerInternal";
     private static final int WAIT_WINDOWS_TIMEOUT_MILLIS = 5000;
 
     protected static final String TAKE_SCREENSHOT = "takeScreenshot";
@@ -298,9 +303,8 @@
             return false;
         }
         try {
-            if (mTrace.isA11yTracingEnabled()) {
-                mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onKeyEvent",
-                        keyEvent + ", " + sequenceNumber);
+            if (svcClientTracingEnabled()) {
+                logTraceSvcClient("onKeyEvent", keyEvent + ", " + sequenceNumber);
             }
             mServiceInterface.onKeyEvent(keyEvent, sequenceNumber);
         } catch (RemoteException e) {
@@ -365,17 +369,16 @@
 
     @Override
     public void setOnKeyEventResult(boolean handled, int sequence) {
-        if (mTrace.isA11yTracingEnabled()) {
-            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setOnKeyEventResult",
-                    "handled=" + handled + ";sequence=" + sequence);
+        if (svcConnTracingEnabled()) {
+            logTraceSvcConn("setOnKeyEventResult", "handled=" + handled + ";sequence=" + sequence);
         }
         mSystemSupport.getKeyEventDispatcher().setOnKeyEventResult(this, handled, sequence);
     }
 
     @Override
     public AccessibilityServiceInfo getServiceInfo() {
-        if (mTrace.isA11yTracingEnabled()) {
-            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getServiceInfo");
+        if (svcConnTracingEnabled()) {
+            logTraceSvcConn("getServiceInfo", "");
         }
         synchronized (mLock) {
             return mAccessibilityServiceInfo;
@@ -393,8 +396,8 @@
 
     @Override
     public void setServiceInfo(AccessibilityServiceInfo info) {
-        if (mTrace.isA11yTracingEnabled()) {
-            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setServiceInfo", "info=" + info);
+        if (svcConnTracingEnabled()) {
+            logTraceSvcConn("setServiceInfo", "info=" + info);
         }
         final long identity = Binder.clearCallingIdentity();
         try {
@@ -421,8 +424,8 @@
     @Nullable
     @Override
     public AccessibilityWindowInfo.WindowListSparseArray getWindows() {
-        if (mTrace.isA11yTracingEnabled()) {
-            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getWindows");
+        if (svcConnTracingEnabled()) {
+            logTraceSvcConn("getWindows", "");
         }
         synchronized (mLock) {
             if (!hasRightsToCurrentUserLocked()) {
@@ -458,8 +461,8 @@
 
     @Override
     public AccessibilityWindowInfo getWindow(int windowId) {
-        if (mTrace.isA11yTracingEnabled()) {
-            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getWindow", "windowId=" + windowId);
+        if (svcConnTracingEnabled()) {
+            logTraceSvcConn("getWindow", "windowId=" + windowId);
         }
         synchronized (mLock) {
             int displayId = Display.INVALID_DISPLAY;
@@ -496,8 +499,8 @@
             long accessibilityNodeId, String viewIdResName, int interactionId,
             IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
             throws RemoteException {
-        if (mTrace.isA11yTracingEnabled()) {
-            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".findAccessibilityNodeInfosByViewId",
+        if (svcConnTracingEnabled()) {
+            logTraceSvcConn("findAccessibilityNodeInfosByViewId",
                     "accessibilityWindowId=" + accessibilityWindowId + ";accessibilityNodeId="
                     + accessibilityNodeId + ";viewIdResName=" + viewIdResName + ";interactionId="
                     + interactionId + ";callback=" + callback + ";interrogatingTid="
@@ -539,6 +542,12 @@
         callback = replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
                 interrogatingPid, interrogatingTid);
         final long identityToken = Binder.clearCallingIdentity();
+        if (intConnTracingEnabled()) {
+            logTraceIntConn("findAccessibilityNodeInfosByViewId",
+                    accessibilityNodeId + ";" + viewIdResName + ";" + partialInteractiveRegion + ";"
+                    + interactionId + ";" + callback + ";" + mFetchFlags + ";" + interrogatingPid
+                    + ";" + interrogatingTid + ";" + spec);
+        }
         try {
             connection.getRemote().findAccessibilityNodeInfosByViewId(accessibilityNodeId,
                     viewIdResName, partialInteractiveRegion, interactionId, callback, mFetchFlags,
@@ -564,8 +573,8 @@
             long accessibilityNodeId, String text, int interactionId,
             IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
             throws RemoteException {
-        if (mTrace.isA11yTracingEnabled()) {
-            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".findAccessibilityNodeInfosByText",
+        if (svcConnTracingEnabled()) {
+            logTraceSvcConn("findAccessibilityNodeInfosByText",
                     "accessibilityWindowId=" + accessibilityWindowId + ";accessibilityNodeId="
                     + accessibilityNodeId + ";text=" + text + ";interactionId=" + interactionId
                     + ";callback=" + callback + ";interrogatingTid=" + interrogatingTid);
@@ -606,6 +615,12 @@
         callback = replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
                 interrogatingPid, interrogatingTid);
         final long identityToken = Binder.clearCallingIdentity();
+        if (intConnTracingEnabled()) {
+            logTraceIntConn("findAccessibilityNodeInfosByText",
+                    accessibilityNodeId + ";" + text + ";" + partialInteractiveRegion + ";"
+                    + interactionId + ";" + callback + ";" + mFetchFlags + ";" + interrogatingPid
+                    + ";" + interrogatingTid + ";" + spec);
+        }
         try {
             connection.getRemote().findAccessibilityNodeInfosByText(accessibilityNodeId,
                     text, partialInteractiveRegion, interactionId, callback, mFetchFlags,
@@ -631,13 +646,12 @@
             int accessibilityWindowId, long accessibilityNodeId, int interactionId,
             IAccessibilityInteractionConnectionCallback callback, int flags,
             long interrogatingTid, Bundle arguments) throws RemoteException {
-        if (mTrace.isA11yTracingEnabled()) {
-            mTrace.logTrace(
-                    TRACE_A11Y_SERVICE_CONNECTION + ".findAccessibilityNodeInfoByAccessibilityId",
+        if (svcConnTracingEnabled()) {
+            logTraceSvcConn("findAccessibilityNodeInfoByAccessibilityId",
                     "accessibilityWindowId=" + accessibilityWindowId + ";accessibilityNodeId="
-                            + accessibilityNodeId + ";interactionId=" + interactionId + ";callback="
-                            + callback + ";flags=" + flags + ";interrogatingTid=" + interrogatingTid
-                            + ";arguments=" + arguments);
+                    + accessibilityNodeId + ";interactionId=" + interactionId + ";callback="
+                    + callback + ";flags=" + flags + ";interrogatingTid=" + interrogatingTid
+                    + ";arguments=" + arguments);
         }
         final int resolvedWindowId;
         RemoteAccessibilityConnection connection;
@@ -675,6 +689,12 @@
         callback = replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
                 interrogatingPid, interrogatingTid);
         final long identityToken = Binder.clearCallingIdentity();
+        if (intConnTracingEnabled()) {
+            logTraceIntConn("findAccessibilityNodeInfoByAccessibilityId",
+                    accessibilityNodeId + ";" + partialInteractiveRegion + ";" + interactionId + ";"
+                    + callback + ";" + (mFetchFlags | flags) + ";" + interrogatingPid + ";"
+                    + interrogatingTid + ";" + spec + ";" + arguments);
+        }
         try {
             connection.getRemote().findAccessibilityNodeInfoByAccessibilityId(
                     accessibilityNodeId, partialInteractiveRegion, interactionId, callback,
@@ -700,12 +720,12 @@
             int focusType, int interactionId,
             IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
             throws RemoteException {
-        if (mTrace.isA11yTracingEnabled()) {
-            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".findFocus",
+        if (svcConnTracingEnabled()) {
+            logTraceSvcConn("findFocus",
                     "accessibilityWindowId=" + accessibilityWindowId + ";accessibilityNodeId="
-                            + accessibilityNodeId + ";focusType=" + focusType + ";interactionId="
-                            + interactionId + ";callback=" + callback + ";interrogatingTid="
-                            + interrogatingTid);
+                    + accessibilityNodeId + ";focusType=" + focusType + ";interactionId="
+                    + interactionId + ";callback=" + callback + ";interrogatingTid="
+                    + interrogatingTid);
         }
         final int resolvedWindowId;
         RemoteAccessibilityConnection connection;
@@ -743,6 +763,12 @@
         callback = replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
                 interrogatingPid, interrogatingTid);
         final long identityToken = Binder.clearCallingIdentity();
+        if (intConnTracingEnabled()) {
+            logTraceIntConn("findFocus",
+                    accessibilityNodeId + ";" + focusType + ";" + partialInteractiveRegion + ";"
+                    + interactionId + ";" + callback + ";" + mFetchFlags + ";" + interrogatingPid
+                    + ";" + interrogatingTid + ";" + spec);
+        }
         try {
             connection.getRemote().findFocus(accessibilityNodeId, focusType,
                     partialInteractiveRegion, interactionId, callback, mFetchFlags,
@@ -768,12 +794,12 @@
             int direction, int interactionId,
             IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
             throws RemoteException {
-        if (mTrace.isA11yTracingEnabled()) {
-            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".focusSearch",
+        if (svcConnTracingEnabled()) {
+            logTraceSvcConn("focusSearch",
                     "accessibilityWindowId=" + accessibilityWindowId + ";accessibilityNodeId="
-                            + accessibilityNodeId + ";direction=" + direction + ";interactionId="
-                            + interactionId + ";callback=" + callback + ";interrogatingTid="
-                            + interrogatingTid);
+                    + accessibilityNodeId + ";direction=" + direction + ";interactionId="
+                    + interactionId + ";callback=" + callback + ";interrogatingTid="
+                    + interrogatingTid);
         }
         final int resolvedWindowId;
         RemoteAccessibilityConnection connection;
@@ -810,6 +836,12 @@
         callback = replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
                 interrogatingPid, interrogatingTid);
         final long identityToken = Binder.clearCallingIdentity();
+        if (intConnTracingEnabled()) {
+            logTraceIntConn("focusSearch",
+                    accessibilityNodeId + ";" + direction + ";" + partialInteractiveRegion
+                    + ";" + interactionId + ";" + callback + ";" + mFetchFlags + ";"
+                    + interrogatingPid + ";" + interrogatingTid + ";" + spec);
+        }
         try {
             connection.getRemote().focusSearch(accessibilityNodeId, direction,
                     partialInteractiveRegion, interactionId, callback, mFetchFlags,
@@ -832,17 +864,17 @@
 
     @Override
     public void sendGesture(int sequence, ParceledListSlice gestureSteps) {
-        if (mTrace.isA11yTracingEnabled()) {
-            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".sendGesture",
-                    "sequence=" + sequence + ";gestureSteps=" + gestureSteps);
+        if (svcConnTracingEnabled()) {
+            logTraceSvcConn(
+                    "sendGesture", "sequence=" + sequence + ";gestureSteps=" + gestureSteps);
         }
     }
 
     @Override
     public void dispatchGesture(int sequence, ParceledListSlice gestureSteps, int displayId) {
-        if (mTrace.isA11yTracingEnabled()) {
-            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".dispatchGesture", "sequence="
-                    + sequence + ";gestureSteps=" + gestureSteps + ";displayId=" + displayId);
+        if (svcConnTracingEnabled()) {
+            logTraceSvcConn("dispatchGesture", "sequence=" + sequence + ";gestureSteps="
+                    + gestureSteps + ";displayId=" + displayId);
         }
     }
 
@@ -851,12 +883,12 @@
             long accessibilityNodeId, int action, Bundle arguments, int interactionId,
             IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
             throws RemoteException {
-        if (mTrace.isA11yTracingEnabled()) {
-            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".performAccessibilityAction",
+        if (svcConnTracingEnabled()) {
+            logTraceSvcConn("performAccessibilityAction",
                     "accessibilityWindowId=" + accessibilityWindowId + ";accessibilityNodeId="
-                            + accessibilityNodeId + ";action=" + action + ";arguments=" + arguments
-                            + ";interactionId=" + interactionId + ";callback=" + callback
-                            + ";interrogatingTid=" + interrogatingTid);
+                    + accessibilityNodeId + ";action=" + action + ";arguments=" + arguments
+                    + ";interactionId=" + interactionId + ";callback=" + callback
+                    + ";interrogatingTid=" + interrogatingTid);
         }
         final int resolvedWindowId;
         synchronized (mLock) {
@@ -879,9 +911,8 @@
 
     @Override
     public boolean performGlobalAction(int action) {
-        if (mTrace.isA11yTracingEnabled()) {
-            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".performGlobalAction",
-                    "action=" + action);
+        if (svcConnTracingEnabled()) {
+            logTraceSvcConn("performGlobalAction", "action=" + action);
         }
         synchronized (mLock) {
             if (!hasRightsToCurrentUserLocked()) {
@@ -893,8 +924,8 @@
 
     @Override
     public @NonNull List<AccessibilityNodeInfo.AccessibilityAction> getSystemActions() {
-        if (mTrace.isA11yTracingEnabled()) {
-            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getSystemActions");
+        if (svcConnTracingEnabled()) {
+            logTraceSvcConn("getSystemActions", "");
         }
         synchronized (mLock) {
             if (!hasRightsToCurrentUserLocked()) {
@@ -906,9 +937,8 @@
 
     @Override
     public boolean isFingerprintGestureDetectionAvailable() {
-        if (mTrace.isA11yTracingEnabled()) {
-            mTrace.logTrace(
-                    TRACE_A11Y_SERVICE_CONNECTION + ".isFingerprintGestureDetectionAvailable");
+        if (svcConnTracingEnabled()) {
+            logTraceSvcConn("isFingerprintGestureDetectionAvailable", "");
         }
         if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
             return false;
@@ -923,9 +953,8 @@
 
     @Override
     public float getMagnificationScale(int displayId) {
-        if (mTrace.isA11yTracingEnabled()) {
-            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getMagnificationScale",
-                    "displayId=" + displayId);
+        if (svcConnTracingEnabled()) {
+            logTraceSvcConn("getMagnificationScale", "displayId=" + displayId);
         }
         synchronized (mLock) {
             if (!hasRightsToCurrentUserLocked()) {
@@ -942,9 +971,8 @@
 
     @Override
     public Region getMagnificationRegion(int displayId) {
-        if (mTrace.isA11yTracingEnabled()) {
-            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getMagnificationRegion",
-                    "displayId=" + displayId);
+        if (svcConnTracingEnabled()) {
+            logTraceSvcConn("getMagnificationRegion", "displayId=" + displayId);
         }
         synchronized (mLock) {
             final Region region = Region.obtain();
@@ -970,9 +998,8 @@
 
     @Override
     public float getMagnificationCenterX(int displayId) {
-        if (mTrace.isA11yTracingEnabled()) {
-            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getMagnificationCenterX",
-                    "displayId=" + displayId);
+        if (svcConnTracingEnabled()) {
+            logTraceSvcConn("getMagnificationCenterX", "displayId=" + displayId);
         }
         synchronized (mLock) {
             if (!hasRightsToCurrentUserLocked()) {
@@ -996,9 +1023,8 @@
 
     @Override
     public float getMagnificationCenterY(int displayId) {
-        if (mTrace.isA11yTracingEnabled()) {
-            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getMagnificationCenterY",
-                    "displayId=" + displayId);
+        if (svcConnTracingEnabled()) {
+            logTraceSvcConn("getMagnificationCenterY", "displayId=" + displayId);
         }
         synchronized (mLock) {
             if (!hasRightsToCurrentUserLocked()) {
@@ -1032,9 +1058,8 @@
 
     @Override
     public boolean resetMagnification(int displayId, boolean animate) {
-        if (mTrace.isA11yTracingEnabled()) {
-            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".resetMagnification",
-                    "displayId=" + displayId + ";animate=" + animate);
+        if (svcConnTracingEnabled()) {
+            logTraceSvcConn("resetMagnification", "displayId=" + displayId + ";animate=" + animate);
         }
         synchronized (mLock) {
             if (!hasRightsToCurrentUserLocked()) {
@@ -1058,10 +1083,10 @@
     @Override
     public boolean setMagnificationScaleAndCenter(int displayId, float scale, float centerX,
             float centerY, boolean animate) {
-        if (mTrace.isA11yTracingEnabled()) {
-            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setMagnificationScaleAndCenter",
+        if (svcConnTracingEnabled()) {
+            logTraceSvcConn("setMagnificationScaleAndCenter",
                     "displayId=" + displayId + ";scale=" + scale + ";centerX=" + centerX
-                            + ";centerY=" + centerY + ";animate=" + animate);
+                    + ";centerY=" + centerY + ";animate=" + animate);
         }
         synchronized (mLock) {
             if (!hasRightsToCurrentUserLocked()) {
@@ -1087,8 +1112,8 @@
 
     @Override
     public void setMagnificationCallbackEnabled(int displayId, boolean enabled) {
-        if (mTrace.isA11yTracingEnabled()) {
-            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setMagnificationCallbackEnabled",
+        if (svcConnTracingEnabled()) {
+            logTraceSvcConn("setMagnificationCallbackEnabled",
                     "displayId=" + displayId + ";enabled=" + enabled);
         }
         mInvocationHandler.setMagnificationCallbackEnabled(displayId, enabled);
@@ -1100,18 +1125,16 @@
 
     @Override
     public void setSoftKeyboardCallbackEnabled(boolean enabled) {
-        if (mTrace.isA11yTracingEnabled()) {
-            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setSoftKeyboardCallbackEnabled",
-                    "enabled=" + enabled);
+        if (svcConnTracingEnabled()) {
+            logTraceSvcConn("setSoftKeyboardCallbackEnabled", "enabled=" + enabled);
         }
         mInvocationHandler.setSoftKeyboardCallbackEnabled(enabled);
     }
 
     @Override
     public void takeScreenshot(int displayId, RemoteCallback callback) {
-        if (mTrace.isA11yTracingEnabled()) {
-            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".takeScreenshot",
-                    "displayId=" + displayId + ";callback=" + callback);
+        if (svcConnTracingEnabled()) {
+            logTraceSvcConn("takeScreenshot", "displayId=" + displayId + ";callback=" + callback);
         }
         final long currentTimestamp = SystemClock.uptimeMillis();
         if (mRequestTakeScreenshotTimestampMs != 0
@@ -1237,6 +1260,10 @@
         final long identity = Binder.clearCallingIdentity();
         try {
             final IBinder overlayWindowToken = new Binder();
+            if (wmTracingEnabled()) {
+                logTraceWM("addWindowToken",
+                        overlayWindowToken + ";TYPE_ACCESSIBILITY_OVERLAY;" + displayId + ";null");
+            }
             mWindowManagerService.addWindowToken(overlayWindowToken, TYPE_ACCESSIBILITY_OVERLAY,
                     displayId, null /* options */);
             synchronized (mLock) {
@@ -1263,6 +1290,10 @@
      */
     public void onDisplayRemoved(int displayId) {
         final long identity = Binder.clearCallingIdentity();
+        if (wmTracingEnabled()) {
+            logTraceWM(
+                    "addWindowToken", mOverlayWindowTokens.get(displayId) + ";true;" + displayId);
+        }
         try {
             mWindowManagerService.removeWindowToken(mOverlayWindowTokens.get(displayId), true,
                     displayId);
@@ -1282,9 +1313,8 @@
      */
     @Override
     public IBinder getOverlayWindowToken(int displayId) {
-        if (mTrace.isA11yTracingEnabled()) {
-            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getOverlayWindowToken",
-                    "displayId=" + displayId);
+        if (svcConnTracingEnabled()) {
+            logTraceSvcConn("getOverlayWindowToken", "displayId=" + displayId);
         }
         synchronized (mLock) {
             return mOverlayWindowTokens.get(displayId);
@@ -1299,9 +1329,8 @@
      */
     @Override
     public int getWindowIdForLeashToken(@NonNull IBinder token) {
-        if (mTrace.isA11yTracingEnabled()) {
-            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getWindowIdForLeashToken",
-                    "token=" + token);
+        if (svcConnTracingEnabled()) {
+            logTraceSvcConn("getWindowIdForLeashToken", "token=" + token);
         }
         synchronized (mLock) {
             return mA11yWindowManager.getWindowIdLocked(token);
@@ -1314,8 +1343,8 @@
             // Clear the proxy in the other process so this
             // IAccessibilityServiceConnection can be garbage collected.
             if (mServiceInterface != null) {
-                if (mTrace.isA11yTracingEnabled()) {
-                    mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".init", "null, " + mId + ", null");
+                if (svcClientTracingEnabled()) {
+                    logTraceSvcClient("init", "null, " + mId + ", null");
                 }
                 mServiceInterface.init(null, mId, null);
             }
@@ -1465,9 +1494,8 @@
         }
 
         try {
-            if (mTrace.isA11yTracingEnabled()) {
-                mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onAccessibilityEvent",
-                        event + ";" + serviceWantsEvent);
+            if (svcClientTracingEnabled()) {
+                logTraceSvcClient("onAccessibilityEvent", event + ";" + serviceWantsEvent);
             }
             listener.onAccessibilityEvent(event, serviceWantsEvent);
             if (DEBUG) {
@@ -1522,9 +1550,9 @@
         final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
         if (listener != null) {
             try {
-                if (mTrace.isA11yTracingEnabled()) {
-                    mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onMagnificationChanged", displayId
-                            + ", " + region + ", " + scale + ", " + centerX + ", " + centerY);
+                if (svcClientTracingEnabled()) {
+                    logTraceSvcClient("onMagnificationChanged", displayId + ", " + region + ", "
+                            + scale + ", " + centerX + ", " + centerY);
                 }
                 listener.onMagnificationChanged(displayId, region, scale, centerX, centerY);
             } catch (RemoteException re) {
@@ -1541,9 +1569,8 @@
         final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
         if (listener != null) {
             try {
-                if (mTrace.isA11yTracingEnabled()) {
-                    mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onSoftKeyboardShowModeChanged",
-                            String.valueOf(showState));
+                if (svcClientTracingEnabled()) {
+                    logTraceSvcClient("onSoftKeyboardShowModeChanged", String.valueOf(showState));
                 }
                 listener.onSoftKeyboardShowModeChanged(showState);
             } catch (RemoteException re) {
@@ -1557,9 +1584,8 @@
         final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
         if (listener != null) {
             try {
-                if (mTrace.isA11yTracingEnabled()) {
-                    mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onAccessibilityButtonClicked",
-                            String.valueOf(displayId));
+                if (svcClientTracingEnabled()) {
+                    logTraceSvcClient("onAccessibilityButtonClicked", String.valueOf(displayId));
                 }
                 listener.onAccessibilityButtonClicked(displayId);
             } catch (RemoteException re) {
@@ -1579,9 +1605,8 @@
         final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
         if (listener != null) {
             try {
-                if (mTrace.isA11yTracingEnabled()) {
-                    mTrace.logTrace(
-                            TRACE_A11Y_SERVICE_CLIENT + ".onAccessibilityButtonAvailabilityChanged",
+                if (svcClientTracingEnabled()) {
+                    logTraceSvcClient("onAccessibilityButtonAvailabilityChanged",
                             String.valueOf(available));
                 }
                 listener.onAccessibilityButtonAvailabilityChanged(available);
@@ -1597,9 +1622,8 @@
         final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
         if (listener != null) {
             try {
-                if (mTrace.isA11yTracingEnabled()) {
-                    mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onGesture",
-                            gestureInfo.toString());
+                if (svcClientTracingEnabled()) {
+                    logTraceSvcClient("onGesture", gestureInfo.toString());
                 }
                 listener.onGesture(gestureInfo);
             } catch (RemoteException re) {
@@ -1613,8 +1637,8 @@
         final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
         if (listener != null) {
             try {
-                if (mTrace.isA11yTracingEnabled()) {
-                    mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onSystemActionsChanged");
+                if (svcClientTracingEnabled()) {
+                    logTraceSvcClient("onSystemActionsChanged", "");
                 }
                 listener.onSystemActionsChanged();
             } catch (RemoteException re) {
@@ -1628,8 +1652,8 @@
         final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
         if (listener != null) {
             try {
-                if (mTrace.isA11yTracingEnabled()) {
-                    mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".clearAccessibilityCache");
+                if (svcClientTracingEnabled()) {
+                    logTraceSvcClient("clearAccessibilityCache", "");
                 }
                 listener.clearAccessibilityCache();
             } catch (RemoteException re) {
@@ -1747,6 +1771,12 @@
                 LocalServices.getService(ActivityTaskManagerInternal.class)
                         .setFocusedActivity(activityToken);
             }
+            if (intConnTracingEnabled()) {
+                logTraceIntConn("performAccessibilityAction",
+                        accessibilityNodeId + ";" + action + ";" + arguments + ";" + interactionId
+                        + ";" + callback + ";" + mFetchFlags + ";" + interrogatingPid + ";"
+                        + interrogatingTid);
+            }
             connection.getRemote().performAccessibilityAction(accessibilityNodeId, action,
                     arguments, interactionId, callback, fetchFlags, interrogatingPid,
                     interrogatingTid);
@@ -1957,8 +1987,8 @@
 
     @Override
     public void setGestureDetectionPassthroughRegion(int displayId, Region region) {
-        if (mTrace.isA11yTracingEnabled()) {
-            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setGestureDetectionPassthroughRegion",
+        if (svcConnTracingEnabled()) {
+            logTraceSvcConn("setGestureDetectionPassthroughRegion",
                     "displayId=" + displayId + ";region=" + region);
         }
         mSystemSupport.setGestureDetectionPassthroughRegion(displayId, region);
@@ -1966,8 +1996,8 @@
 
     @Override
     public void setTouchExplorationPassthroughRegion(int displayId, Region region) {
-        if (mTrace.isA11yTracingEnabled()) {
-            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setTouchExplorationPassthroughRegion",
+        if (svcConnTracingEnabled()) {
+            logTraceSvcConn("setTouchExplorationPassthroughRegion",
                     "displayId=" + displayId + ";region=" + region);
         }
         mSystemSupport.setTouchExplorationPassthroughRegion(displayId, region);
@@ -1975,20 +2005,56 @@
 
     @Override
     public void setFocusAppearance(int strokeWidth, int color) {
-        if (mTrace.isA11yTracingEnabled()) {
-            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setFocusAppearance",
-                    "strokeWidth=" + strokeWidth + ";color=" + color);
+        if (svcConnTracingEnabled()) {
+            logTraceSvcConn("setFocusAppearance", "strokeWidth=" + strokeWidth + ";color=" + color);
         }
     }
 
     @Override
-    public void logTrace(long timestamp, String where, String callingParams, int processId,
-            long threadId, int callingUid, Bundle callingStack) {
-        if (mTrace.isA11yTracingEnabled()) {
+    public void logTrace(long timestamp, String where, long loggingTypes, String callingParams,
+            int processId, long threadId, int callingUid, Bundle callingStack) {
+        if (mTrace.isA11yTracingEnabledForTypes(loggingTypes)) {
             ArrayList<StackTraceElement> list =
                     (ArrayList<StackTraceElement>) callingStack.getSerializable(CALL_STACK);
-            mTrace.logTrace(timestamp, where, callingParams, processId, threadId, callingUid,
-                    list.toArray(new StackTraceElement[list.size()]));
+            HashSet<String> ignoreList =
+                    (HashSet<String>) callingStack.getSerializable(IGNORE_CALL_STACK);
+            mTrace.logTrace(timestamp, where, loggingTypes, callingParams, processId, threadId,
+                    callingUid, list.toArray(new StackTraceElement[list.size()]), ignoreList);
         }
     }
+
+    protected boolean svcClientTracingEnabled() {
+        return mTrace.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_SERVICE_CLIENT);
+    }
+
+    protected void logTraceSvcClient(String methodName, String params) {
+        mTrace.logTrace(TRACE_SVC_CLIENT + "." + methodName,
+                    FLAGS_ACCESSIBILITY_SERVICE_CLIENT, params);
+    }
+
+    protected boolean svcConnTracingEnabled() {
+        return mTrace.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_SERVICE_CONNECTION);
+    }
+
+    protected void logTraceSvcConn(String methodName, String params) {
+        mTrace.logTrace(TRACE_SVC_CONN + "." + methodName,
+                FLAGS_ACCESSIBILITY_SERVICE_CONNECTION, params);
+    }
+
+    protected boolean intConnTracingEnabled() {
+        return mTrace.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION);
+    }
+
+    protected void logTraceIntConn(String methodName, String params) {
+        mTrace.logTrace(LOG_TAG + "." + methodName,
+                FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION, params);
+    }
+
+    protected boolean wmTracingEnabled() {
+        return mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MANAGER_INTERNAL);
+    }
+
+    protected void logTraceWM(String methodName, String params) {
+        mTrace.logTrace(TRACE_WM + "." + methodName, FLAGS_WINDOW_MANAGER_INTERNAL, params);
+    }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index 7403af7..7d2b71f 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -19,7 +19,9 @@
 import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
 
+import android.accessibilityservice.AccessibilityTrace;
 import android.annotation.MainThread;
+import android.annotation.NonNull;
 import android.content.Context;
 import android.graphics.Region;
 import android.os.PowerManager;
@@ -43,7 +45,10 @@
 import com.android.server.accessibility.magnification.WindowMagnificationPromptController;
 import com.android.server.policy.WindowManagerPolicy;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.StringJoiner;
 
 /**
  * This class is an input filter for implementing accessibility features such
@@ -171,9 +176,9 @@
 
     private int mEnabledFeatures;
 
-    private EventStreamState mMouseStreamState;
+    private final SparseArray<EventStreamState> mMouseStreamStates = new SparseArray<>(0);
 
-    private EventStreamState mTouchScreenStreamState;
+    private final SparseArray<EventStreamState> mTouchScreenStreamStates = new SparseArray<>(0);
 
     private EventStreamState mKeyboardStreamState;
 
@@ -211,10 +216,17 @@
         super.onUninstalled();
     }
 
-    void onDisplayChanged() {
+    void onDisplayAdded(@NonNull Display display) {
         if (mInstalled) {
-            disableFeatures();
-            enableFeatures();
+            resetStreamStateForDisplay(display.getDisplayId());
+            enableFeaturesForDisplay(display);
+        }
+    }
+
+    void onDisplayRemoved(int displayId) {
+        if (mInstalled) {
+            disableFeaturesForDisplay(displayId);
+            resetStreamStateForDisplay(displayId);
         }
     }
 
@@ -224,7 +236,12 @@
             Slog.d(TAG, "Received event: " + event + ", policyFlags=0x"
                     + Integer.toHexString(policyFlags));
         }
-
+        if (mAms.getTraceManager().isA11yTracingEnabledForTypes(
+                AccessibilityTrace.FLAGS_INPUT_FILTER)) {
+            mAms.getTraceManager().logTrace(TAG + ".onInputEvent",
+                    AccessibilityTrace.FLAGS_INPUT_FILTER,
+                    "event=" + event + ";policyFlags=" + policyFlags);
+        }
         if (mEventHandler.size() == 0) {
             if (DEBUG) Slog.d(TAG, "No mEventHandler for event " + event);
             super.onInputEvent(event, policyFlags);
@@ -237,16 +254,17 @@
             return;
         }
 
-        int eventSource = event.getSource();
+        final int eventSource = event.getSource();
+        final int displayId = event.getDisplayId();
         if ((policyFlags & WindowManagerPolicy.FLAG_PASS_TO_USER) == 0) {
             state.reset();
-            clearEventsForAllEventHandlers(eventSource);
+            clearEventStreamHandler(displayId, eventSource);
             super.onInputEvent(event, policyFlags);
             return;
         }
 
         if (state.updateInputSource(event.getSource())) {
-            clearEventsForAllEventHandlers(eventSource);
+            clearEventStreamHandler(displayId, eventSource);
         }
 
         if (!state.inputSourceValid()) {
@@ -275,35 +293,39 @@
      */
     private EventStreamState getEventStreamState(InputEvent event) {
         if (event instanceof MotionEvent) {
-          if (event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) {
-              if (mTouchScreenStreamState == null) {
-                  mTouchScreenStreamState = new TouchScreenEventStreamState();
-              }
-              return mTouchScreenStreamState;
-          }
-          if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
-              if (mMouseStreamState == null) {
-                  mMouseStreamState = new MouseEventStreamState();
-              }
-              return mMouseStreamState;
-          }
+            final int displayId = event.getDisplayId();
+
+            if (event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) {
+                EventStreamState touchScreenStreamState = mTouchScreenStreamStates.get(displayId);
+                if (touchScreenStreamState == null) {
+                    touchScreenStreamState = new TouchScreenEventStreamState();
+                    mTouchScreenStreamStates.put(displayId, touchScreenStreamState);
+                }
+                return touchScreenStreamState;
+            }
+            if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
+                EventStreamState mouseStreamState = mMouseStreamStates.get(displayId);
+                if (mouseStreamState == null) {
+                    mouseStreamState = new MouseEventStreamState();
+                    mMouseStreamStates.put(displayId, mouseStreamState);
+                }
+                return mouseStreamState;
+            }
         } else if (event instanceof KeyEvent) {
-          if (event.isFromSource(InputDevice.SOURCE_KEYBOARD)) {
-              if (mKeyboardStreamState == null) {
-                  mKeyboardStreamState = new KeyboardEventStreamState();
-              }
-              return mKeyboardStreamState;
-          }
+            if (event.isFromSource(InputDevice.SOURCE_KEYBOARD)) {
+                if (mKeyboardStreamState == null) {
+                    mKeyboardStreamState = new KeyboardEventStreamState();
+                }
+                return mKeyboardStreamState;
+            }
         }
         return null;
     }
 
-    private void clearEventsForAllEventHandlers(int eventSource) {
-        for (int i = 0; i < mEventHandler.size(); i++) {
-            final EventStreamTransformation eventHandler = mEventHandler.valueAt(i);
-            if (eventHandler != null) {
-                eventHandler.clearEvents(eventSource);
-            }
+    private void clearEventStreamHandler(int displayId, int eventSource) {
+        final EventStreamTransformation eventHandler = mEventHandler.get(displayId);
+        if (eventHandler != null) {
+            eventHandler.clearEvents(eventSource);
         }
     }
 
@@ -419,55 +441,69 @@
     private void enableFeatures() {
         if (DEBUG) Slog.i(TAG, "enableFeatures()");
 
-        resetStreamState();
+        resetAllStreamState();
 
         final ArrayList<Display> displaysList = mAms.getValidDisplayList();
 
-        if ((mEnabledFeatures & FLAG_FEATURE_AUTOCLICK) != 0) {
-            mAutoclickController = new AutoclickController(mContext, mUserId);
-            addFirstEventHandlerForAllDisplays(displaysList, mAutoclickController);
-        }
-
         for (int i = displaysList.size() - 1; i >= 0; i--) {
-            final int displayId = displaysList.get(i).getDisplayId();
-            final Context displayContext = mContext.createDisplayContext(displaysList.get(i));
+            enableFeaturesForDisplay(displaysList.get(i));
+        }
+        enableDisplayIndependentFeatures();
+    }
 
-            if ((mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) != 0) {
-                TouchExplorer explorer = new TouchExplorer(displayContext, mAms);
-                if ((mEnabledFeatures & FLAG_SERVICE_HANDLES_DOUBLE_TAP) != 0) {
-                    explorer.setServiceHandlesDoubleTap(true);
-                }
-                if ((mEnabledFeatures & FLAG_REQUEST_MULTI_FINGER_GESTURES) != 0) {
-                    explorer.setMultiFingerGesturesEnabled(true);
-                }
-                if ((mEnabledFeatures & FLAG_REQUEST_2_FINGER_PASSTHROUGH) != 0) {
-                    explorer.setTwoFingerPassthroughEnabled(true);
-                }
-                if ((mEnabledFeatures & FLAG_SEND_MOTION_EVENTS) != 0) {
-                    explorer.setSendMotionEventsEnabled(true);
-                }
-                addFirstEventHandler(displayId, explorer);
-                mTouchExplorer.put(displayId, explorer);
-            }
-
-            if ((mEnabledFeatures & FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER) != 0
-                    || ((mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0)
-                    || ((mEnabledFeatures & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0)) {
-                final MagnificationGestureHandler magnificationGestureHandler =
-                        createMagnificationGestureHandler(displayId,
-                                displayContext);
-                addFirstEventHandler(displayId, magnificationGestureHandler);
-                mMagnificationGestureHandler.put(displayId, magnificationGestureHandler);
-            }
-
-            if ((mEnabledFeatures & FLAG_FEATURE_INJECT_MOTION_EVENTS) != 0) {
-                MotionEventInjector injector = new MotionEventInjector(
-                        mContext.getMainLooper());
-                addFirstEventHandler(displayId, injector);
-                mMotionEventInjectors.put(displayId, injector);
-            }
+    private void enableFeaturesForDisplay(Display display) {
+        if (DEBUG) {
+            Slog.i(TAG, "enableFeaturesForDisplay() : display Id = " + display.getDisplayId());
         }
 
+        final Context displayContext = mContext.createDisplayContext(display);
+        final int displayId = display.getDisplayId();
+
+        if ((mEnabledFeatures & FLAG_FEATURE_AUTOCLICK) != 0) {
+            if (mAutoclickController == null) {
+                mAutoclickController = new AutoclickController(
+                        mContext, mUserId, mAms.getTraceManager());
+            }
+            addFirstEventHandler(displayId, mAutoclickController);
+        }
+
+        if ((mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) != 0) {
+            TouchExplorer explorer = new TouchExplorer(displayContext, mAms);
+            if ((mEnabledFeatures & FLAG_SERVICE_HANDLES_DOUBLE_TAP) != 0) {
+                explorer.setServiceHandlesDoubleTap(true);
+            }
+            if ((mEnabledFeatures & FLAG_REQUEST_MULTI_FINGER_GESTURES) != 0) {
+                explorer.setMultiFingerGesturesEnabled(true);
+            }
+            if ((mEnabledFeatures & FLAG_REQUEST_2_FINGER_PASSTHROUGH) != 0) {
+                explorer.setTwoFingerPassthroughEnabled(true);
+            }
+            if ((mEnabledFeatures & FLAG_SEND_MOTION_EVENTS) != 0) {
+                explorer.setSendMotionEventsEnabled(true);
+            }
+            addFirstEventHandler(displayId, explorer);
+            mTouchExplorer.put(displayId, explorer);
+        }
+
+        if ((mEnabledFeatures & FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER) != 0
+                || ((mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0)
+                || ((mEnabledFeatures & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0)) {
+            final MagnificationGestureHandler magnificationGestureHandler =
+                    createMagnificationGestureHandler(displayId,
+                            displayContext);
+            addFirstEventHandler(displayId, magnificationGestureHandler);
+            mMagnificationGestureHandler.put(displayId, magnificationGestureHandler);
+        }
+
+        if ((mEnabledFeatures & FLAG_FEATURE_INJECT_MOTION_EVENTS) != 0) {
+            MotionEventInjector injector = new MotionEventInjector(
+                    mContext.getMainLooper(), mAms.getTraceManager());
+            addFirstEventHandler(displayId, injector);
+            mMotionEventInjectors.put(displayId, injector);
+        }
+    }
+
+    private void enableDisplayIndependentFeatures() {
         if ((mEnabledFeatures & FLAG_FEATURE_INJECT_MOTION_EVENTS) != 0) {
             mAms.setMotionEventInjectors(mMotionEventInjectors);
         }
@@ -500,55 +536,57 @@
         mEventHandler.put(displayId, eventHandler);
     }
 
-    /**
-     * Adds an event handler to the event handler chain for all displays. The handler is added at
-     * the beginning of the chain.
-     *
-     * @param displayList The list of displays
-     * @param handler The handler to be added to the event handlers list.
-     */
-    private void addFirstEventHandlerForAllDisplays(ArrayList<Display> displayList,
-            EventStreamTransformation handler) {
-        for (int i = 0; i < displayList.size(); i++) {
-            final int displayId = displayList.get(i).getDisplayId();
-            addFirstEventHandler(displayId, handler);
+    private void disableFeatures() {
+        final ArrayList<Display> displaysList = mAms.getValidDisplayList();
+
+        for (int i = displaysList.size() - 1; i >= 0; i--) {
+            disableFeaturesForDisplay(displaysList.get(i).getDisplayId());
+        }
+        mAms.setMotionEventInjectors(null);
+        disableDisplayIndependentFeatures();
+
+        resetAllStreamState();
+    }
+
+    private void disableFeaturesForDisplay(int displayId) {
+        if (DEBUG) {
+            Slog.i(TAG, "disableFeaturesForDisplay() : display Id = " + displayId);
+        }
+
+        final MotionEventInjector injector = mMotionEventInjectors.get(displayId);
+        if (injector != null) {
+            injector.onDestroy();
+            mMotionEventInjectors.remove(displayId);
+        }
+
+        final TouchExplorer explorer = mTouchExplorer.get(displayId);
+        if (explorer != null) {
+            explorer.onDestroy();
+            mTouchExplorer.remove(displayId);
+        }
+
+        final MagnificationGestureHandler handler = mMagnificationGestureHandler.get(displayId);
+        if (handler != null) {
+            handler.onDestroy();
+            mMagnificationGestureHandler.remove(displayId);
+        }
+
+        final EventStreamTransformation eventStreamTransformation = mEventHandler.get(displayId);
+        if (eventStreamTransformation != null) {
+            mEventHandler.remove(displayId);
         }
     }
 
-    private void disableFeatures() {
-        for (int i = mMotionEventInjectors.size() - 1; i >= 0; i--) {
-            final MotionEventInjector injector = mMotionEventInjectors.valueAt(i);
-            if (injector != null) {
-                injector.onDestroy();
-            }
-        }
-        mAms.setMotionEventInjectors(null);
-        mMotionEventInjectors.clear();
+    private void disableDisplayIndependentFeatures() {
         if (mAutoclickController != null) {
             mAutoclickController.onDestroy();
             mAutoclickController = null;
         }
-        for (int i = mTouchExplorer.size() - 1; i >= 0; i--) {
-            final TouchExplorer explorer = mTouchExplorer.valueAt(i);
-            if (explorer != null) {
-                explorer.onDestroy();
-            }
-        }
-        mTouchExplorer.clear();
-        for (int i = mMagnificationGestureHandler.size() - 1; i >= 0; i--) {
-            final MagnificationGestureHandler handler = mMagnificationGestureHandler.valueAt(i);
-            if (handler != null) {
-                handler.onDestroy();
-            }
-        }
-        mMagnificationGestureHandler.clear();
+
         if (mKeyboardInterceptor != null) {
             mKeyboardInterceptor.onDestroy();
             mKeyboardInterceptor = null;
         }
-
-        mEventHandler.clear();
-        resetStreamState();
     }
 
     private MagnificationGestureHandler createMagnificationGestureHandler(
@@ -563,32 +601,46 @@
             final Context uiContext = displayContext.createWindowContext(
                     TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY, null /* options */);
             magnificationGestureHandler = new WindowMagnificationGestureHandler(uiContext,
-                    mAms.getWindowMagnificationMgr(), mAms.getMagnificationController(),
-                    detectControlGestures, triggerable,
+                    mAms.getWindowMagnificationMgr(), mAms.getTraceManager(),
+                    mAms.getMagnificationController(), detectControlGestures, triggerable,
                     displayId);
         } else {
             final Context uiContext = displayContext.createWindowContext(
                     TYPE_MAGNIFICATION_OVERLAY, null /* options */);
             magnificationGestureHandler = new FullScreenMagnificationGestureHandler(uiContext,
-                    mAms.getFullScreenMagnificationController(), mAms.getMagnificationController(),
-                    detectControlGestures, triggerable,
+                    mAms.getFullScreenMagnificationController(), mAms.getTraceManager(),
+                    mAms.getMagnificationController(), detectControlGestures, triggerable,
                     new WindowMagnificationPromptController(displayContext, mUserId), displayId);
         }
         return magnificationGestureHandler;
     }
 
-    void resetStreamState() {
-        if (mTouchScreenStreamState != null) {
-            mTouchScreenStreamState.reset();
+    void resetAllStreamState() {
+        final ArrayList<Display> displaysList = mAms.getValidDisplayList();
+
+        for (int i = displaysList.size() - 1; i >= 0; i--) {
+            resetStreamStateForDisplay(displaysList.get(i).getDisplayId());
         }
-        if (mMouseStreamState != null) {
-            mMouseStreamState.reset();
-        }
+
         if (mKeyboardStreamState != null) {
             mKeyboardStreamState.reset();
         }
     }
 
+    void resetStreamStateForDisplay(int displayId) {
+        final EventStreamState touchScreenStreamState = mTouchScreenStreamStates.get(displayId);
+        if (touchScreenStreamState != null) {
+            touchScreenStreamState.reset();
+            mTouchScreenStreamStates.remove(displayId);
+        }
+
+        final EventStreamState mouseStreamState = mMouseStreamStates.get(displayId);
+        if (mouseStreamState != null) {
+            mouseStreamState.reset();
+            mMouseStreamStates.remove(displayId);
+        }
+    }
+
     @Override
     public void onDestroy() {
         /* ignore */
@@ -839,4 +891,45 @@
             mTouchExplorer.get(displayId).setTouchExplorationPassthroughRegion(region);
         }
     }
+
+    /**
+     * Dumps all {@link AccessibilityInputFilter}s here.
+     */
+    public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
+        if (mEventHandler == null) {
+            return;
+        }
+        pw.append("A11yInputFilter Info : ");
+        pw.println();
+
+        final ArrayList<Display> displaysList = mAms.getValidDisplayList();
+        for (int i = 0; i < displaysList.size(); i++) {
+            final int displayId = displaysList.get(i).getDisplayId();
+            EventStreamTransformation next = mEventHandler.get(displayId);
+            if (next != null) {
+                pw.append("Enabled features of Display [");
+                pw.append(Integer.toString(displayId));
+                pw.append("] = ");
+
+                final StringJoiner joiner = new StringJoiner(",", "[", "]");
+
+                while (next != null) {
+                    if (next instanceof MagnificationGestureHandler) {
+                        joiner.add("MagnificationGesture");
+                    } else if (next instanceof KeyboardInterceptor) {
+                        joiner.add("KeyboardInterceptor");
+                    } else if (next instanceof TouchExplorer) {
+                        joiner.add("TouchExplorer");
+                    } else if (next instanceof AutoclickController) {
+                        joiner.add("AutoclickController");
+                    } else if (next instanceof MotionEventInjector) {
+                        joiner.add("MotionEventInjector");
+                    }
+                    next = next.getNext();
+                }
+                pw.append(joiner.toString());
+            }
+            pw.println();
+        }
+    }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index f631988..04ef101 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -16,6 +16,15 @@
 
 package com.android.server.accessibility;
 
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_MANAGER;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_MANAGER_CLIENT;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CLIENT;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_FINGERPRINT;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_INPUT_FILTER;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_PACKAGE_BROADCAST_RECEIVER;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_USER_BROADCAST_RECEIVER;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MAGNIFICATION_CONNECTION;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MANAGER_INTERNAL;
 import static android.provider.Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED;
 import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
 import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
@@ -289,8 +298,8 @@
         mContext = context;
         mPowerManager =  (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
         mWindowManagerService = LocalServices.getService(WindowManagerInternal.class);
-        mTraceManager = new AccessibilityTraceManager(
-                mWindowManagerService.getAccessibilityController(), this);
+        mTraceManager = AccessibilityTraceManager.getInstance(
+                mWindowManagerService.getAccessibilityController(), this, mLock);
         mMainHandler = new MainHandler(mContext.getMainLooper());
         mActivityTaskManagerService = LocalServices.getService(ActivityTaskManagerInternal.class);
         mPackageManager = packageManager;
@@ -311,8 +320,8 @@
         mContext = context;
         mPowerManager = context.getSystemService(PowerManager.class);
         mWindowManagerService = LocalServices.getService(WindowManagerInternal.class);
-        mTraceManager = new AccessibilityTraceManager(
-                mWindowManagerService.getAccessibilityController(), this);
+        mTraceManager = AccessibilityTraceManager.getInstance(
+                mWindowManagerService.getAccessibilityController(), this, mLock);
         mMainHandler = new MainHandler(mContext.getMainLooper());
         mActivityTaskManagerService = LocalServices.getService(ActivityTaskManagerInternal.class);
         mPackageManager = mContext.getPackageManager();
@@ -324,7 +333,7 @@
         mSecurityPolicy = new AccessibilitySecurityPolicy(policyWarningUIController, mContext,
                 this);
         mA11yWindowManager = new AccessibilityWindowManager(mLock, mMainHandler,
-                mWindowManagerService, this, mSecurityPolicy, this);
+                mWindowManagerService, this, mSecurityPolicy, this, mTraceManager);
         mA11yDisplayListener = new AccessibilityDisplayListener(mContext, mMainHandler);
         mMagnificationController = new MagnificationController(this, mLock, mContext);
         init();
@@ -339,26 +348,16 @@
 
     @Override
     public int getCurrentUserIdLocked() {
-        if (mTraceManager.isA11yTracingEnabled()) {
-            mTraceManager.logTrace(LOG_TAG + ".getCurrentUserIdLocked");
-        }
         return mCurrentUserId;
     }
 
     @Override
     public boolean isAccessibilityButtonShown() {
-        if (mTraceManager.isA11yTracingEnabled()) {
-            mTraceManager.logTrace(LOG_TAG + ".isAccessibilityButtonShown");
-        }
         return mIsAccessibilityButtonShown;
     }
 
     @Override
     public void onServiceInfoChangedLocked(AccessibilityUserState userState) {
-        if (mTraceManager.isA11yTracingEnabled()) {
-            mTraceManager.logTrace(
-                    LOG_TAG + ".onServiceInfoChangedLocked", "userState=" + userState);
-        }
         mSecurityPolicy.onBoundServicesChangedLocked(userState.mUserId,
                 userState.mBoundServices);
         scheduleNotifyClientsOfServicesStateChangeLocked(userState);
@@ -424,8 +423,9 @@
         PackageMonitor monitor = new PackageMonitor() {
             @Override
             public void onSomePackagesChanged() {
-                if (mTraceManager.isA11yTracingEnabled()) {
-                    mTraceManager.logTrace(LOG_TAG + ".PM.onSomePackagesChanged");
+                if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_PACKAGE_BROADCAST_RECEIVER)) {
+                    mTraceManager.logTrace(LOG_TAG + ".PM.onSomePackagesChanged",
+                            FLAGS_PACKAGE_BROADCAST_RECEIVER);
                 }
 
                 synchronized (mLock) {
@@ -452,8 +452,9 @@
                 // mBindingServices in binderDied() during updating. Remove services from  this
                 // package from mBindingServices, and then update the user state to re-bind new
                 // versions of them.
-                if (mTraceManager.isA11yTracingEnabled()) {
+                if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_PACKAGE_BROADCAST_RECEIVER)) {
                     mTraceManager.logTrace(LOG_TAG + ".PM.onPackageUpdateFinished",
+                            FLAGS_PACKAGE_BROADCAST_RECEIVER,
                             "packageName=" + packageName + ";uid=" + uid);
                 }
                 synchronized (mLock) {
@@ -485,8 +486,9 @@
 
             @Override
             public void onPackageRemoved(String packageName, int uid) {
-                if (mTraceManager.isA11yTracingEnabled()) {
+                if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_PACKAGE_BROADCAST_RECEIVER)) {
                     mTraceManager.logTrace(LOG_TAG + ".PM.onPackageRemoved",
+                            FLAGS_PACKAGE_BROADCAST_RECEIVER,
                             "packageName=" + packageName + ";uid=" + uid);
                 }
 
@@ -529,8 +531,9 @@
             @Override
             public boolean onHandleForceStop(Intent intent, String[] packages,
                     int uid, boolean doit) {
-                if (mTraceManager.isA11yTracingEnabled()) {
+                if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_PACKAGE_BROADCAST_RECEIVER)) {
                     mTraceManager.logTrace(LOG_TAG + ".PM.onHandleForceStop",
+                            FLAGS_PACKAGE_BROADCAST_RECEIVER,
                             "intent=" + intent + ";packages=" + packages + ";uid=" + uid
                             + ";doit=" + doit);
                 }
@@ -580,8 +583,8 @@
         mContext.registerReceiverAsUser(new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
-                if (mTraceManager.isA11yTracingEnabled()) {
-                    mTraceManager.logTrace(LOG_TAG + ".BR.onReceive",
+                if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_USER_BROADCAST_RECEIVER)) {
+                    mTraceManager.logTrace(LOG_TAG + ".BR.onReceive", FLAGS_USER_BROADCAST_RECEIVER,
                             "context=" + context + ";intent=" + intent);
                 }
 
@@ -668,8 +671,8 @@
 
     @Override
     public long addClient(IAccessibilityManagerClient callback, int userId) {
-        if (mTraceManager.isA11yTracingEnabled()) {
-            mTraceManager.logTrace(LOG_TAG + ".addClient",
+        if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
+            mTraceManager.logTrace(LOG_TAG + ".addClient", FLAGS_ACCESSIBILITY_MANAGER,
                     "callback=" + callback + ";userId=" + userId);
         }
 
@@ -739,8 +742,8 @@
 
     @Override
     public void sendAccessibilityEvent(AccessibilityEvent event, int userId) {
-        if (mTraceManager.isA11yTracingEnabled()) {
-            mTraceManager.logTrace(LOG_TAG + ".sendAccessibilityEvent",
+        if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
+            mTraceManager.logTrace(LOG_TAG + ".sendAccessibilityEvent", FLAGS_ACCESSIBILITY_MANAGER,
                     "event=" + event + ";userId=" + userId);
         }
         boolean dispatchEvent = false;
@@ -803,6 +806,10 @@
                 }
             }
             if (shouldComputeWindows) {
+                if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MANAGER_INTERNAL)) {
+                    mTraceManager.logTrace("WindowManagerInternal.computeWindowsForAccessibility",
+                            FLAGS_WINDOW_MANAGER_INTERNAL, "display=" + displayId);
+                }
                 final WindowManagerInternal wm = LocalServices.getService(
                         WindowManagerInternal.class);
                 wm.computeWindowsForAccessibility(displayId);
@@ -835,9 +842,9 @@
      */
     @Override
     public void registerSystemAction(RemoteAction action, int actionId) {
-        if (mTraceManager.isA11yTracingEnabled()) {
+        if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
             mTraceManager.logTrace(LOG_TAG + ".registerSystemAction",
-                    "action=" + action + ";actionId=" + actionId);
+                    FLAGS_ACCESSIBILITY_MANAGER, "action=" + action + ";actionId=" + actionId);
         }
         mSecurityPolicy.enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY);
         getSystemActionPerformer().registerSystemAction(actionId, action);
@@ -850,8 +857,9 @@
      */
     @Override
     public void unregisterSystemAction(int actionId) {
-        if (mTraceManager.isA11yTracingEnabled()) {
-            mTraceManager.logTrace(LOG_TAG + ".unregisterSystemAction", "actionId=" + actionId);
+        if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
+            mTraceManager.logTrace(LOG_TAG + ".unregisterSystemAction",
+                    FLAGS_ACCESSIBILITY_MANAGER, "actionId=" + actionId);
         }
         mSecurityPolicy.enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY);
         getSystemActionPerformer().unregisterSystemAction(actionId);
@@ -867,9 +875,9 @@
 
     @Override
     public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(int userId) {
-        if (mTraceManager.isA11yTracingEnabled()) {
+        if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
             mTraceManager.logTrace(LOG_TAG + ".getInstalledAccessibilityServiceList",
-                    "userId=" + userId);
+                    FLAGS_ACCESSIBILITY_MANAGER, "userId=" + userId);
         }
 
         final int resolvedUserId;
@@ -903,8 +911,9 @@
     @Override
     public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int feedbackType,
             int userId) {
-        if (mTraceManager.isA11yTracingEnabled()) {
+        if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
             mTraceManager.logTrace(LOG_TAG + ".getEnabledAccessibilityServiceList",
+                    FLAGS_ACCESSIBILITY_MANAGER,
                     "feedbackType=" + feedbackType + ";userId=" + userId);
         }
 
@@ -936,8 +945,9 @@
 
     @Override
     public void interrupt(int userId) {
-        if (mTraceManager.isA11yTracingEnabled()) {
-            mTraceManager.logTrace(LOG_TAG + ".interrupt", "userId=" + userId);
+        if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
+            mTraceManager.logTrace(LOG_TAG + ".interrupt",
+                    FLAGS_ACCESSIBILITY_MANAGER, "userId=" + userId);
         }
 
         List<IAccessibilityServiceClient> interfacesToInterrupt;
@@ -966,8 +976,10 @@
         }
         for (int i = 0, count = interfacesToInterrupt.size(); i < count; i++) {
             try {
-                if (mTraceManager.isA11yTracingEnabled()) {
-                    mTraceManager.logTrace(LOG_TAG + ".IAccessibilityServiceClient.onInterrupt");
+                if (mTraceManager.isA11yTracingEnabledForTypes(
+                        FLAGS_ACCESSIBILITY_SERVICE_CLIENT)) {
+                    mTraceManager.logTrace(LOG_TAG + ".IAccessibilityServiceClient.onInterrupt",
+                            FLAGS_ACCESSIBILITY_SERVICE_CLIENT);
                 }
                 interfacesToInterrupt.get(i).onInterrupt();
             } catch (RemoteException re) {
@@ -981,8 +993,9 @@
     public int addAccessibilityInteractionConnection(IWindow windowToken, IBinder leashToken,
             IAccessibilityInteractionConnection connection, String packageName,
             int userId) throws RemoteException {
-        if (mTraceManager.isA11yTracingEnabled()) {
+        if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
             mTraceManager.logTrace(LOG_TAG + ".addAccessibilityInteractionConnection",
+                    FLAGS_ACCESSIBILITY_MANAGER,
                     "windowToken=" + windowToken + "leashToken=" + leashToken + ";connection="
                             + connection + "; packageName=" + packageName + ";userId=" + userId);
         }
@@ -993,9 +1006,9 @@
 
     @Override
     public void removeAccessibilityInteractionConnection(IWindow window) {
-        if (mTraceManager.isA11yTracingEnabled()) {
+        if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
             mTraceManager.logTrace(LOG_TAG + ".removeAccessibilityInteractionConnection",
-                    "window=" + window);
+                    FLAGS_ACCESSIBILITY_MANAGER, "window=" + window);
         }
         mA11yWindowManager.removeAccessibilityInteractionConnection(window);
     }
@@ -1003,9 +1016,9 @@
     @Override
     public void setPictureInPictureActionReplacingConnection(
             IAccessibilityInteractionConnection connection) throws RemoteException {
-        if (mTraceManager.isA11yTracingEnabled()) {
+        if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
             mTraceManager.logTrace(LOG_TAG + ".setPictureInPictureActionReplacingConnection",
-                    "connection=" + connection);
+                    FLAGS_ACCESSIBILITY_MANAGER, "connection=" + connection);
         }
         mSecurityPolicy.enforceCallingPermission(Manifest.permission.MODIFY_ACCESSIBILITY_DATA,
                 SET_PIP_ACTION_REPLACEMENT);
@@ -1017,10 +1030,11 @@
             IAccessibilityServiceClient serviceClient,
             AccessibilityServiceInfo accessibilityServiceInfo,
             int flags) {
-        if (mTraceManager.isA11yTracingEnabled()) {
-            mTraceManager.logTrace(LOG_TAG + ".registerUiTestAutomationService", "owner=" + owner
-                    + ";serviceClient=" + serviceClient + ";accessibilityServiceInfo="
-                    + accessibilityServiceInfo + ";flags=" + flags);
+        if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
+            mTraceManager.logTrace(LOG_TAG + ".registerUiTestAutomationService",
+                    FLAGS_ACCESSIBILITY_MANAGER,
+                    "owner=" + owner + ";serviceClient=" + serviceClient
+                    + ";accessibilityServiceInfo=" + accessibilityServiceInfo + ";flags=" + flags);
         }
 
         mSecurityPolicy.enforceCallingPermission(Manifest.permission.RETRIEVE_WINDOW_CONTENT,
@@ -1037,9 +1051,9 @@
 
     @Override
     public void unregisterUiTestAutomationService(IAccessibilityServiceClient serviceClient) {
-        if (mTraceManager.isA11yTracingEnabled()) {
+        if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
             mTraceManager.logTrace(LOG_TAG + ".unregisterUiTestAutomationService",
-                    "serviceClient=" + serviceClient);
+                    FLAGS_ACCESSIBILITY_MANAGER, "serviceClient=" + serviceClient);
         }
         synchronized (mLock) {
             mUiAutomationManager.unregisterUiTestAutomationServiceLocked(serviceClient);
@@ -1049,15 +1063,20 @@
     @Override
     public void temporaryEnableAccessibilityStateUntilKeyguardRemoved(
             ComponentName service, boolean touchExplorationEnabled) {
-        if (mTraceManager.isA11yTracingEnabled()) {
+        if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
             mTraceManager.logTrace(
                     LOG_TAG + ".temporaryEnableAccessibilityStateUntilKeyguardRemoved",
+                    FLAGS_ACCESSIBILITY_MANAGER,
                     "service=" + service + ";touchExplorationEnabled=" + touchExplorationEnabled);
         }
 
         mSecurityPolicy.enforceCallingPermission(
                 Manifest.permission.TEMPORARY_ENABLE_ACCESSIBILITY,
                 TEMPORARY_ENABLE_ACCESSIBILITY_UNTIL_KEYGUARD_REMOVED);
+        if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MANAGER_INTERNAL)) {
+            mTraceManager.logTrace("WindowManagerInternal.isKeyguardLocked",
+                    FLAGS_WINDOW_MANAGER_INTERNAL);
+        }
         if (!mWindowManagerService.isKeyguardLocked()) {
             return;
         }
@@ -1083,9 +1102,9 @@
 
     @Override
     public IBinder getWindowToken(int windowId, int userId) {
-        if (mTraceManager.isA11yTracingEnabled()) {
+        if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
             mTraceManager.logTrace(LOG_TAG + ".getWindowToken",
-                    "windowId=" + windowId + ";userId=" + userId);
+                    FLAGS_ACCESSIBILITY_MANAGER, "windowId=" + windowId + ";userId=" + userId);
         }
 
         mSecurityPolicy.enforceCallingPermission(
@@ -1127,8 +1146,9 @@
      */
     @Override
     public void notifyAccessibilityButtonClicked(int displayId, String targetName) {
-        if (mTraceManager.isA11yTracingEnabled()) {
+        if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
             mTraceManager.logTrace(LOG_TAG + ".notifyAccessibilityButtonClicked",
+                    FLAGS_ACCESSIBILITY_MANAGER,
                     "displayId=" + displayId + ";targetName=" + targetName);
         }
 
@@ -1157,9 +1177,9 @@
      */
     @Override
     public void notifyAccessibilityButtonVisibilityChanged(boolean shown) {
-        if (mTraceManager.isA11yTracingEnabled()) {
+        if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
             mTraceManager.logTrace(LOG_TAG + ".notifyAccessibilityButtonVisibilityChanged",
-                    "shown=" + shown);
+                    FLAGS_ACCESSIBILITY_MANAGER, "shown=" + shown);
         }
 
         mSecurityPolicy.enforceCallingOrSelfPermission(
@@ -1190,10 +1210,6 @@
      */
     @Override
     public void onSystemActionsChanged() {
-        if (mTraceManager.isA11yTracingEnabled()) {
-            mTraceManager.logTrace(LOG_TAG + ".onSystemActionsChanged");
-        }
-
         synchronized (mLock) {
             AccessibilityUserState state = getCurrentUserStateLocked();
             notifySystemActionsChangedLocked(state);
@@ -1256,11 +1272,6 @@
 
     @Override
     public @Nullable MotionEventInjector getMotionEventInjectorForDisplayLocked(int displayId) {
-        if (mTraceManager.isA11yTracingEnabled()) {
-            mTraceManager.logTrace(LOG_TAG + ".getMotionEventInjectorForDisplayLocked",
-                    "displayId=" + displayId);
-        }
-
         final long endMillis = SystemClock.uptimeMillis() + WAIT_MOTION_INJECTOR_TIMEOUT_MILLIS;
         MotionEventInjector motionEventInjector = null;
         while ((mMotionEventInjectors == null) && (SystemClock.uptimeMillis() < endMillis)) {
@@ -1323,6 +1334,10 @@
         synchronized (mLock) {
             token = getWindowToken(windowId, mCurrentUserId);
         }
+        if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MANAGER_INTERNAL)) {
+            mTraceManager.logTrace("WindowManagerInternal.getWindowFrame",
+                    FLAGS_WINDOW_MANAGER_INTERNAL, "token=" + token + ";outBounds=" + outBounds);
+        }
         mWindowManagerService.getWindowFrame(token, outBounds);
         if (!outBounds.isEmpty()) {
             return true;
@@ -1471,7 +1486,7 @@
     private int getClientStateLocked(AccessibilityUserState userState) {
         return userState.getClientStateLocked(
             mUiAutomationManager.isUiAutomationRunningLocked(),
-            mTraceManager.isA11yTracingEnabled());
+            mTraceManager.getTraceStateForAccessibilityManagerClientState());
     }
 
     private InteractionBridge getInteractionBridge() {
@@ -1681,6 +1696,10 @@
     }
 
     private void updateRelevantEventsLocked(AccessibilityUserState userState) {
+        if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_SERVICE_CLIENT)) {
+            mTraceManager.logTrace(LOG_TAG + ".updateRelevantEventsLocked",
+                    FLAGS_ACCESSIBILITY_SERVICE_CLIENT, "userState=" + userState);
+        }
         mMainHandler.post(() -> {
             broadcastToClients(userState, ignoreRemoteException(client -> {
                 int relevantEventTypes;
@@ -1830,12 +1849,6 @@
     @Override
     public void persistComponentNamesToSettingLocked(String settingName,
             Set<ComponentName> componentNames, int userId) {
-        if (mTraceManager.isA11yTracingEnabled()) {
-            mTraceManager.logTrace(LOG_TAG + ".persistComponentNamesToSettingLocked",
-                    "settingName=" + settingName + ";componentNames=" + componentNames + ";userId="
-                            + userId);
-        }
-
         persistColonDelimitedSetToSettingLocked(settingName, userId, componentNames,
                 componentName -> componentName.flattenToShortString());
     }
@@ -1960,7 +1973,7 @@
         }
     }
 
-    private void scheduleUpdateClientsIfNeededLocked(AccessibilityUserState userState) {
+    void scheduleUpdateClientsIfNeededLocked(AccessibilityUserState userState) {
         final int clientState = getClientStateLocked(userState);
         if (userState.getLastSentClientStateLocked() != clientState
                 && (mGlobalClients.getRegisteredCallbackCount() > 0
@@ -1983,6 +1996,10 @@
 
     private void sendStateToClients(int clientState,
             RemoteCallbackList<IAccessibilityManagerClient> clients) {
+        if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER_CLIENT)) {
+            mTraceManager.logTrace(LOG_TAG + ".sendStateToClients",
+                    FLAGS_ACCESSIBILITY_MANAGER_CLIENT, "clientState=" + clientState);
+        }
         clients.broadcast(ignoreRemoteException(
                 client -> client.setState(clientState)));
     }
@@ -2003,6 +2020,10 @@
 
     private void notifyClientsOfServicesStateChange(
             RemoteCallbackList<IAccessibilityManagerClient> clients, long uiTimeout) {
+        if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER_CLIENT)) {
+            mTraceManager.logTrace(LOG_TAG + ".notifyClientsOfServicesStateChange",
+                    FLAGS_ACCESSIBILITY_MANAGER_CLIENT, "uiTimeout=" + uiTimeout);
+        }
         clients.broadcast(ignoreRemoteException(
                 client -> client.notifyServicesStateChanged(uiTimeout)));
     }
@@ -2082,6 +2103,12 @@
             }
         }
         if (setInputFilter) {
+            if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MANAGER_INTERNAL
+                    | FLAGS_INPUT_FILTER)) {
+                mTraceManager.logTrace("WindowManagerInternal.setInputFilter",
+                        FLAGS_WINDOW_MANAGER_INTERNAL | FLAGS_INPUT_FILTER,
+                        "inputFilter=" + inputFilter);
+            }
             mWindowManagerService.setInputFilter(inputFilter);
         }
     }
@@ -2805,26 +2832,21 @@
     @GuardedBy("mLock")
     @Override
     public MagnificationSpec getCompatibleMagnificationSpecLocked(int windowId) {
-        if (mTraceManager.isA11yTracingEnabled()) {
-            mTraceManager.logTrace(LOG_TAG + ".getCompatibleMagnificationSpecLocked",
-                    "windowId=" + windowId);
-        }
-
         IBinder windowToken = mA11yWindowManager.getWindowTokenForUserAndWindowIdLocked(
                 mCurrentUserId, windowId);
         if (windowToken != null) {
-            return mWindowManagerService.getCompatibleMagnificationSpecForWindow(
-                    windowToken);
+            if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MANAGER_INTERNAL)) {
+                mTraceManager.logTrace(LOG_TAG + ".getCompatibleMagnificationSpecForWindow",
+                        FLAGS_WINDOW_MANAGER_INTERNAL, "windowToken=" + windowToken);
+            }
+
+            return mWindowManagerService.getCompatibleMagnificationSpecForWindow(windowToken);
         }
         return null;
     }
 
     @Override
     public KeyEventDispatcher getKeyEventDispatcher() {
-        if (mTraceManager.isA11yTracingEnabled()) {
-            mTraceManager.logTrace(LOG_TAG + ".getKeyEventDispatcher");
-        }
-
         if (mKeyEventDispatcher == null) {
             mKeyEventDispatcher = new KeyEventDispatcher(
                     mMainHandler, MainHandler.MSG_SEND_KEY_EVENT_TO_INPUT_FILTER, mLock,
@@ -2837,13 +2859,6 @@
     @SuppressWarnings("AndroidFrameworkPendingIntentMutability")
     public PendingIntent getPendingIntentActivity(Context context, int requestCode, Intent intent,
             int flags) {
-        if (mTraceManager.isA11yTracingEnabled()) {
-            mTraceManager.logTrace(LOG_TAG + ".getPendingIntentActivity",
-                    "context=" + context + ";requestCode=" + requestCode + ";intent=" + intent
-                            + ";flags=" + flags);
-        }
-
-
         return PendingIntent.getActivity(context, requestCode, intent, flags);
     }
 
@@ -2858,9 +2873,9 @@
      */
     @Override
     public void performAccessibilityShortcut(String targetName) {
-        if (mTraceManager.isA11yTracingEnabled()) {
+        if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
             mTraceManager.logTrace(LOG_TAG + ".performAccessibilityShortcut",
-                    "targetName=" + targetName);
+                    FLAGS_ACCESSIBILITY_MANAGER, "targetName=" + targetName);
         }
 
         if ((UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID)
@@ -3048,9 +3063,9 @@
 
     @Override
     public List<String> getAccessibilityShortcutTargets(@ShortcutType int shortcutType) {
-        if (mTraceManager.isA11yTracingEnabled()) {
+        if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
             mTraceManager.logTrace(LOG_TAG + ".getAccessibilityShortcutTargets",
-                    "shortcutType=" + shortcutType);
+                    FLAGS_ACCESSIBILITY_MANAGER, "shortcutType=" + shortcutType);
         }
 
         if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
@@ -3122,11 +3137,6 @@
 
     @Override
     public void sendAccessibilityEventForCurrentUserLocked(AccessibilityEvent event) {
-        if (mTraceManager.isA11yTracingEnabled()) {
-            mTraceManager.logTrace(LOG_TAG + ".sendAccessibilityEventForCurrentUserLocked",
-                    "event=" + event);
-        }
-
         sendAccessibilityEventLocked(event, mCurrentUserId);
     }
 
@@ -3148,8 +3158,10 @@
      */
     @Override
     public boolean sendFingerprintGesture(int gestureKeyCode) {
-        if (mTraceManager.isA11yTracingEnabled()) {
+        if (mTraceManager.isA11yTracingEnabledForTypes(
+                FLAGS_ACCESSIBILITY_MANAGER | FLAGS_FINGERPRINT)) {
             mTraceManager.logTrace(LOG_TAG + ".sendFingerprintGesture",
+                    FLAGS_ACCESSIBILITY_MANAGER | FLAGS_FINGERPRINT,
                     "gestureKeyCode=" + gestureKeyCode);
         }
 
@@ -3174,9 +3186,9 @@
      */
     @Override
     public int getAccessibilityWindowId(@Nullable IBinder windowToken) {
-        if (mTraceManager.isA11yTracingEnabled()) {
+        if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
             mTraceManager.logTrace(LOG_TAG + ".getAccessibilityWindowId",
-                    "windowToken=" + windowToken);
+                    FLAGS_ACCESSIBILITY_MANAGER, "windowToken=" + windowToken);
         }
 
         synchronized (mLock) {
@@ -3196,8 +3208,9 @@
      */
     @Override
     public long getRecommendedTimeoutMillis() {
-        if (mTraceManager.isA11yTracingEnabled()) {
-            mTraceManager.logTrace(LOG_TAG + ".getRecommendedTimeoutMillis");
+        if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
+            mTraceManager.logTrace(
+                    LOG_TAG + ".getRecommendedTimeoutMillis", FLAGS_ACCESSIBILITY_MANAGER);
         }
 
         synchronized(mLock) {
@@ -3214,8 +3227,10 @@
     @Override
     public void setWindowMagnificationConnection(
             IWindowMagnificationConnection connection) throws RemoteException {
-        if (mTraceManager.isA11yTracingEnabled()) {
+        if (mTraceManager.isA11yTracingEnabledForTypes(
+                FLAGS_ACCESSIBILITY_MANAGER | FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) {
             mTraceManager.logTrace(LOG_TAG + ".setWindowMagnificationConnection",
+                    FLAGS_ACCESSIBILITY_MANAGER | FLAGS_WINDOW_MAGNIFICATION_CONNECTION,
                     "connection=" + connection);
         }
 
@@ -3249,9 +3264,9 @@
 
     @Override
     public void associateEmbeddedHierarchy(@NonNull IBinder host, @NonNull IBinder embedded) {
-        if (mTraceManager.isA11yTracingEnabled()) {
+        if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
             mTraceManager.logTrace(LOG_TAG + ".associateEmbeddedHierarchy",
-                    "host=" + host + ";embedded=" + embedded);
+                    FLAGS_ACCESSIBILITY_MANAGER, "host=" + host + ";embedded=" + embedded);
         }
 
         synchronized (mLock) {
@@ -3261,8 +3276,9 @@
 
     @Override
     public void disassociateEmbeddedHierarchy(@NonNull IBinder token) {
-        if (mTraceManager.isA11yTracingEnabled()) {
-            mTraceManager.logTrace(LOG_TAG + ".disassociateEmbeddedHierarchy", "token=" + token);
+        if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
+            mTraceManager.logTrace(LOG_TAG + ".disassociateEmbeddedHierarchy",
+                    FLAGS_ACCESSIBILITY_MANAGER, "token=" + token);
         }
 
         synchronized (mLock) {
@@ -3274,7 +3290,11 @@
      * Gets the stroke width of the focus rectangle.
      * @return The stroke width.
      */
+    @Override
     public int getFocusStrokeWidth() {
+        if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
+            mTraceManager.logTrace(LOG_TAG + ".getFocusStrokeWidth", FLAGS_ACCESSIBILITY_MANAGER);
+        }
         synchronized (mLock) {
             final AccessibilityUserState userState = getCurrentUserStateLocked();
 
@@ -3286,7 +3306,11 @@
      * Gets the color of the focus rectangle.
      * @return The color.
      */
+    @Override
     public int getFocusColor() {
+        if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
+            mTraceManager.logTrace(LOG_TAG + ".getFocusColor", FLAGS_ACCESSIBILITY_MANAGER);
+        }
         synchronized (mLock) {
             final AccessibilityUserState userState = getCurrentUserStateLocked();
 
@@ -3314,6 +3338,9 @@
                 pw.println();
             }
             mA11yWindowManager.dump(fd, pw, args);
+            if (mHasInputFilter && mInputFilter != null) {
+                mInputFilter.dump(fd, pw, args);
+            }
             pw.println("Global client list info:{");
             mGlobalClients.dump(pw, "    Client list ");
             pw.println("    Registered clients:{");
@@ -3350,9 +3377,6 @@
 
     @Override
     public FullScreenMagnificationController getFullScreenMagnificationController() {
-        if (mTraceManager.isA11yTracingEnabled()) {
-            mTraceManager.logTrace(LOG_TAG + ".getFullScreenMagnificationController");
-        }
         synchronized (mLock) {
             return mMagnificationController.getFullScreenMagnificationController();
         }
@@ -3360,11 +3384,6 @@
 
     @Override
     public void onClientChangeLocked(boolean serviceInfoChanged) {
-        if (mTraceManager.isA11yTracingEnabled()) {
-            mTraceManager.logTrace(LOG_TAG + ".onClientChangeLocked",
-                    "serviceInfoChanged=" + serviceInfoChanged);
-        }
-
         AccessibilityUserState userState = getUserStateLocked(mCurrentUserId);
         onUserStateChangedLocked(userState);
         if (serviceInfoChanged) {
@@ -3569,7 +3588,7 @@
             synchronized (mLock) {
                 mDisplaysList.add(display);
                 if (mInputFilter != null) {
-                    mInputFilter.onDisplayChanged();
+                    mInputFilter.onDisplayAdded(display);
                 }
                 AccessibilityUserState userState = getCurrentUserStateLocked();
                 if (displayId != Display.DEFAULT_DISPLAY) {
@@ -3591,7 +3610,7 @@
                     return;
                 }
                 if (mInputFilter != null) {
-                    mInputFilter.onDisplayChanged();
+                    mInputFilter.onDisplayRemoved(displayId);
                 }
                 AccessibilityUserState userState = getCurrentUserStateLocked();
                 if (displayId != Display.DEFAULT_DISPLAY) {
@@ -3891,11 +3910,6 @@
 
     @Override
     public void setGestureDetectionPassthroughRegion(int displayId, Region region) {
-        if (mTraceManager.isA11yTracingEnabled()) {
-            mTraceManager.logTrace(LOG_TAG + ".setGestureDetectionPassthroughRegion",
-                    "displayId=" + displayId + ";region=" + region);
-        }
-
         mMainHandler.sendMessage(
                 obtainMessage(
                         AccessibilityManagerService::setGestureDetectionPassthroughRegionInternal,
@@ -3906,11 +3920,6 @@
 
     @Override
     public void setTouchExplorationPassthroughRegion(int displayId, Region region) {
-        if (mTraceManager.isA11yTracingEnabled()) {
-            mTraceManager.logTrace(LOG_TAG + ".setTouchExplorationPassthroughRegion",
-                    "displayId=" + displayId + ";region=" + region);
-        }
-
         mMainHandler.sendMessage(
                 obtainMessage(
                         AccessibilityManagerService::setTouchExplorationPassthroughRegionInternal,
@@ -3939,7 +3948,10 @@
         if (userState.mUserId != mCurrentUserId) {
             return;
         }
-
+        if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_SERVICE_CLIENT)) {
+            mTraceManager.logTrace(LOG_TAG + ".updateFocusAppearanceDataLocked",
+                    FLAGS_ACCESSIBILITY_SERVICE_CLIENT, "userState=" + userState);
+        }
         mMainHandler.post(() -> {
             broadcastToClients(userState, ignoreRemoteException(client -> {
                 client.mCallback.setFocusAppearance(userState.getFocusStrokeWidthLocked(),
@@ -3949,7 +3961,7 @@
 
     }
 
-    AccessibilityTraceManager getTraceManager() {
+    public AccessibilityTraceManager getTraceManager() {
         return mTraceManager;
     }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index 7d75b73..467cab5 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -20,6 +20,7 @@
 
 import android.Manifest;
 import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.AccessibilityTrace;
 import android.accessibilityservice.IAccessibilityServiceClient;
 import android.app.PendingIntent;
 import android.content.ComponentName;
@@ -53,10 +54,7 @@
  */
 class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnection {
     private static final String LOG_TAG = "AccessibilityServiceConnection";
-    private static final String TRACE_A11Y_SERVICE_CONNECTION =
-            LOG_TAG + ".IAccessibilityServiceConnection";
-    private static final String TRACE_A11Y_SERVICE_CLIENT =
-            LOG_TAG + ".IAccessibilityServiceClient";
+
     /*
      Holding a weak reference so there isn't a loop of references. AccessibilityUserState keeps
      lists of bound and binding services. These are freed on user changes, but just in case it
@@ -137,8 +135,8 @@
 
     @Override
     public void disableSelf() {
-        if (mTrace.isA11yTracingEnabled()) {
-            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".disableSelf");
+        if (svcConnTracingEnabled()) {
+            logTraceSvcConn("disableSelf", "");
         }
         synchronized (mLock) {
             AccessibilityUserState userState = mUserStateWeakReference.get();
@@ -218,9 +216,9 @@
             return;
         }
         try {
-            if (mTrace.isA11yTracingEnabled()) {
-                mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".init", this + ", " + mId + ", "
-                        + mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY));
+            if (svcClientTracingEnabled()) {
+                logTraceSvcClient("init",
+                        this + "," + mId + "," + mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY));
             }
             serviceInterface.init(this, mId, mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY));
         } catch (RemoteException re) {
@@ -264,9 +262,8 @@
 
     @Override
     public boolean setSoftKeyboardShowMode(int showMode) {
-        if (mTrace.isA11yTracingEnabled()) {
-            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setSoftKeyboardShowMode",
-                    "showMode=" + showMode);
+        if (svcConnTracingEnabled()) {
+            logTraceSvcConn("setSoftKeyboardShowMode", "showMode=" + showMode);
         }
         synchronized (mLock) {
             if (!hasRightsToCurrentUserLocked()) {
@@ -280,8 +277,8 @@
 
     @Override
     public int getSoftKeyboardShowMode() {
-        if (mTrace.isA11yTracingEnabled()) {
-            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getSoftKeyboardShowMode");
+        if (svcConnTracingEnabled()) {
+            logTraceSvcConn("getSoftKeyboardShowMode", "");
         }
         final AccessibilityUserState userState = mUserStateWeakReference.get();
         return (userState != null) ? userState.getSoftKeyboardShowModeLocked() : 0;
@@ -289,9 +286,8 @@
 
     @Override
     public boolean switchToInputMethod(String imeId) {
-        if (mTrace.isA11yTracingEnabled()) {
-            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".switchToInputMethod",
-                    "imeId=" + imeId);
+        if (svcConnTracingEnabled()) {
+            logTraceSvcConn("switchToInputMethod", "imeId=" + imeId);
         }
         synchronized (mLock) {
             if (!hasRightsToCurrentUserLocked()) {
@@ -311,8 +307,8 @@
 
     @Override
     public boolean isAccessibilityButtonAvailable() {
-        if (mTrace.isA11yTracingEnabled()) {
-            mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".isAccessibilityButtonAvailable");
+        if (svcConnTracingEnabled()) {
+            logTraceSvcConn("isAccessibilityButtonAvailable", "");
         }
         synchronized (mLock) {
             if (!hasRightsToCurrentUserLocked()) {
@@ -373,9 +369,9 @@
         }
         if (serviceInterface != null) {
             try {
-                if (mTrace.isA11yTracingEnabled()) {
-                    mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT
-                            + ".onFingerprintCapturingGesturesChanged", String.valueOf(active));
+                if (svcClientTracingEnabled()) {
+                    logTraceSvcClient(
+                            "onFingerprintCapturingGesturesChanged", String.valueOf(active));
                 }
                 mServiceInterface.onFingerprintCapturingGesturesChanged(active);
             } catch (RemoteException e) {
@@ -394,9 +390,8 @@
         }
         if (serviceInterface != null) {
             try {
-                if (mTrace.isA11yTracingEnabled()) {
-                    mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onFingerprintGesture",
-                            String.valueOf(gesture));
+                if (svcClientTracingEnabled()) {
+                    logTraceSvcClient("onFingerprintGesture", String.valueOf(gesture));
                 }
                 mServiceInterface.onFingerprintGesture(gesture);
             } catch (RemoteException e) {
@@ -410,15 +405,17 @@
             if (mSecurityPolicy.canPerformGestures(this)) {
                 MotionEventInjector motionEventInjector =
                         mSystemSupport.getMotionEventInjectorForDisplayLocked(displayId);
+                if (wmTracingEnabled()) {
+                    logTraceWM("isTouchOrFaketouchDevice", "");
+                }
                 if (motionEventInjector != null
                         && mWindowManagerService.isTouchOrFaketouchDevice()) {
                     motionEventInjector.injectEvents(
                             gestureSteps.getList(), mServiceInterface, sequence, displayId);
                 } else {
                     try {
-                        if (mTrace.isA11yTracingEnabled()) {
-                            mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onPerformGestureResult",
-                                    sequence + ", false");
+                        if (svcClientTracingEnabled()) {
+                            logTraceSvcClient("onPerformGestureResult", sequence + ", false");
                         }
                         mServiceInterface.onPerformGestureResult(sequence, false);
                     } catch (RemoteException re) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java
index 6396960..8cf5547 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java
@@ -60,7 +60,7 @@
             }
             case "start-trace":
             case "stop-trace":
-                return mService.getTraceManager().onShellCommand(cmd);
+                return mService.getTraceManager().onShellCommand(cmd, this);
         }
         return -1;
     }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityTrace.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityTrace.java
deleted file mode 100644
index 0391413..0000000
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityTrace.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/**
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server.accessibility;
-
-/**
- * Interface to log accessibility trace.
- */
-public interface AccessibilityTrace {
-    /**
-     * Whether the trace is enabled.
-     */
-    boolean isA11yTracingEnabled();
-
-    /**
-     * Start tracing.
-     */
-    void startTrace();
-
-    /**
-     * Stop tracing.
-     */
-    void stopTrace();
-
-    /**
-     * Log one trace entry.
-     * @param where A string to identify this log entry, which can be used to filter/search
-     *        through the tracing file.
-     */
-    void logTrace(String where);
-
-    /**
-     * Log one trace entry.
-     * @param where A string to identify this log entry, which can be used to filter/search
-     *        through the tracing file.
-     * @param callingParams The parameters for the method to be logged.
-     */
-    void logTrace(String where, String callingParams);
-
-    /**
-     * Log one trace entry. Accessibility services using AccessibilityInteractionClient to
-     * make screen content related requests use this API to log entry when receive callback.
-     * @param timestamp The timestamp when a callback is received.
-     * @param where A string to identify this log entry, which can be used to filter/search
-     *        through the tracing file.
-     * @param callingParams The parameters for the callback.
-     * @param processId The process id of the calling component.
-     * @param threadId The threadId of the calling component.
-     * @param callingUid The calling uid of the callback.
-     * @param callStack The call stack of the callback.
-     */
-    void logTrace(long timestamp, String where, String callingParams, int processId,
-            long threadId, int callingUid, StackTraceElement[] callStack);
-}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityTraceManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityTraceManager.java
index 6105e8a..51e01ea 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityTraceManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityTraceManager.java
@@ -15,72 +15,197 @@
  */
 package com.android.server.accessibility;
 
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_INTERACTION_CLIENT;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_MANAGER_CLIENT_STATES;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_LOGGING_ALL;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_LOGGING_NONE;
+import static android.view.accessibility.AccessibilityManager.STATE_FLAG_TRACE_A11Y_INTERACTION_CLIENT_ENABLED;
+import static android.view.accessibility.AccessibilityManager.STATE_FLAG_TRACE_A11Y_INTERACTION_CONNECTION_CB_ENABLED;
+import static android.view.accessibility.AccessibilityManager.STATE_FLAG_TRACE_A11Y_INTERACTION_CONNECTION_ENABLED;
+import static android.view.accessibility.AccessibilityManager.STATE_FLAG_TRACE_A11Y_SERVICE_ENABLED;
+
+import android.accessibilityservice.AccessibilityTrace;
+import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.os.Binder;
+import android.os.ShellCommand;
 
 import com.android.server.wm.WindowManagerInternal;
 
 import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
 
 /**
  * Manager of accessibility trace.
  */
-class AccessibilityTraceManager implements AccessibilityTrace {
+public class AccessibilityTraceManager implements AccessibilityTrace {
     private final WindowManagerInternal.AccessibilityControllerInternal mA11yController;
     private final AccessibilityManagerService mService;
+    private final Object mA11yMSLock;
 
-    AccessibilityTraceManager(
+    private long mEnabledLoggingFlags;
+
+    private static AccessibilityTraceManager sInstance = null;
+
+    @MainThread
+    static AccessibilityTraceManager getInstance(
             @NonNull WindowManagerInternal.AccessibilityControllerInternal a11yController,
-            @NonNull AccessibilityManagerService service) {
+            @NonNull AccessibilityManagerService service,
+            @NonNull Object lock) {
+        if (sInstance == null) {
+            sInstance = new AccessibilityTraceManager(a11yController, service, lock);
+        }
+        return sInstance;
+    }
+
+    private AccessibilityTraceManager(
+            @NonNull WindowManagerInternal.AccessibilityControllerInternal a11yController,
+            @NonNull AccessibilityManagerService service,
+            @NonNull Object lock) {
         mA11yController = a11yController;
         mService = service;
+        mA11yMSLock = lock;
+        mEnabledLoggingFlags = FLAGS_LOGGING_NONE;
     }
 
     @Override
     public boolean isA11yTracingEnabled() {
-        return mA11yController.isAccessibilityTracingEnabled();
+        synchronized (mA11yMSLock) {
+            return mEnabledLoggingFlags != FLAGS_LOGGING_NONE;
+        }
     }
 
     @Override
-    public void startTrace() {
-        if (!mA11yController.isAccessibilityTracingEnabled()) {
-            mA11yController.startTrace();
-            mService.scheduleUpdateClientsIfNeeded(mService.getCurrentUserState());
+    public boolean isA11yTracingEnabledForTypes(long typeIdFlags) {
+        synchronized (mA11yMSLock) {
+            return ((typeIdFlags & mEnabledLoggingFlags) != FLAGS_LOGGING_NONE);
         }
     }
 
     @Override
+    public int getTraceStateForAccessibilityManagerClientState() {
+        int state = 0x0;
+        synchronized (mA11yMSLock) {
+            if (isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION)) {
+                state |= STATE_FLAG_TRACE_A11Y_INTERACTION_CONNECTION_ENABLED;
+            }
+            if (isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK)) {
+                state |= STATE_FLAG_TRACE_A11Y_INTERACTION_CONNECTION_CB_ENABLED;
+            }
+            if (isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_INTERACTION_CLIENT)) {
+                state |= STATE_FLAG_TRACE_A11Y_INTERACTION_CLIENT_ENABLED;
+            }
+            if (isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_SERVICE)) {
+                state |= STATE_FLAG_TRACE_A11Y_SERVICE_ENABLED;
+            }
+        }
+        return state;
+    }
+
+    @Override
+    public void startTrace(long loggingTypes) {
+        if (loggingTypes == FLAGS_LOGGING_NONE) {
+            // Ignore start none request
+            return;
+        }
+
+        synchronized (mA11yMSLock) {
+            long oldEnabled = mEnabledLoggingFlags;
+            mEnabledLoggingFlags = loggingTypes;
+
+            if (needToNotifyClients(oldEnabled)) {
+                mService.scheduleUpdateClientsIfNeededLocked(mService.getCurrentUserState());
+            }
+        }
+
+        mA11yController.startTrace(loggingTypes);
+    }
+
+    @Override
     public void stopTrace() {
-        if (mA11yController.isAccessibilityTracingEnabled()) {
+        boolean stop = false;
+        synchronized (mA11yMSLock) {
+            stop = isA11yTracingEnabled();
+
+            long oldEnabled = mEnabledLoggingFlags;
+            mEnabledLoggingFlags = FLAGS_LOGGING_NONE;
+
+            if (needToNotifyClients(oldEnabled)) {
+                mService.scheduleUpdateClientsIfNeededLocked(mService.getCurrentUserState());
+            }
+        }
+        if (stop) {
             mA11yController.stopTrace();
-            mService.scheduleUpdateClientsIfNeeded(mService.getCurrentUserState());
         }
     }
 
     @Override
-    public void logTrace(String where) {
-        logTrace(where, "");
+    public void logTrace(String where, long loggingTypes) {
+        logTrace(where, loggingTypes, "");
     }
 
     @Override
-    public void logTrace(String where, String callingParams) {
-        mA11yController.logTrace(where, callingParams, "".getBytes(),
-                Binder.getCallingUid(), Thread.currentThread().getStackTrace());
-    }
-
-    @Override
-    public void logTrace(long timestamp, String where, String callingParams, int processId,
-            long threadId, int callingUid, StackTraceElement[] callStack) {
-        if (mA11yController.isAccessibilityTracingEnabled()) {
-            mA11yController.logTrace(where, callingParams, "".getBytes(), callingUid, callStack,
-                    timestamp, processId, threadId);
+    public void logTrace(String where, long loggingTypes, String callingParams) {
+        if (isA11yTracingEnabledForTypes(loggingTypes)) {
+            mA11yController.logTrace(where, loggingTypes, callingParams, "".getBytes(),
+                    Binder.getCallingUid(), Thread.currentThread().getStackTrace(),
+                    new HashSet<String>(Arrays.asList("logTrace")));
         }
     }
 
-    int onShellCommand(String cmd) {
+    @Override
+    public void logTrace(long timestamp, String where, long loggingTypes, String callingParams,
+            int processId, long threadId, int callingUid, StackTraceElement[] callStack,
+            Set<String> ignoreElementList) {
+        if (isA11yTracingEnabledForTypes(loggingTypes)) {
+            mA11yController.logTrace(where, loggingTypes, callingParams, "".getBytes(), callingUid,
+                    callStack, timestamp, processId, threadId,
+                    ((ignoreElementList == null) ? new HashSet<String>() : ignoreElementList));
+        }
+    }
+
+    private boolean needToNotifyClients(long otherTypesEnabled) {
+        return (mEnabledLoggingFlags & FLAGS_ACCESSIBILITY_MANAGER_CLIENT_STATES)
+                != (otherTypesEnabled & FLAGS_ACCESSIBILITY_MANAGER_CLIENT_STATES);
+    }
+
+    int onShellCommand(String cmd, ShellCommand shell) {
         switch (cmd) {
             case "start-trace": {
-                startTrace();
+                String opt = shell.getNextOption();
+                if (opt == null) {
+                    startTrace(FLAGS_LOGGING_ALL);
+                    return 0;
+                }
+                List<String> types = new ArrayList<String>();
+                while (opt != null) {
+                    switch (opt) {
+                        case "-t": {
+                            String type = shell.getNextArg();
+                            while (type != null) {
+                                types.add(type);
+                                type = shell.getNextArg();
+                            }
+                            break;
+                        }
+                        default: {
+                            shell.getErrPrintWriter().println(
+                                    "Error: option not recognized " + opt);
+                            stopTrace();
+                            return -1;
+                        }
+                    }
+                    opt = shell.getNextOption();
+                }
+                long enabledTypes = AccessibilityTrace.getLoggingFlagsFromNames(types);
+                startTrace(enabledTypes);
                 return 0;
             }
             case "stop-trace": {
@@ -92,8 +217,29 @@
     }
 
     void onHelp(PrintWriter pw) {
-        pw.println("  start-trace");
-        pw.println("    Start the debug tracing.");
+        pw.println("  start-trace [-t LOGGING_TYPE [LOGGING_TYPE...]]");
+        pw.println("    Start the debug tracing. If no option is present, full trace will be");
+        pw.println("    generated. Options are:");
+        pw.println("      -t: Only generate tracing for the logging type(s) specified here.");
+        pw.println("          LOGGING_TYPE can be any one of below:");
+        pw.println("            IAccessibilityServiceConnection");
+        pw.println("            IAccessibilityServiceClient");
+        pw.println("            IAccessibilityManager");
+        pw.println("            IAccessibilityManagerClient");
+        pw.println("            IAccessibilityInteractionConnection");
+        pw.println("            IAccessibilityInteractionConnectionCallback");
+        pw.println("            IRemoteMagnificationAnimationCallback");
+        pw.println("            IWindowMagnificationConnection");
+        pw.println("            IWindowMagnificationConnectionCallback");
+        pw.println("            WindowManagerInternal");
+        pw.println("            WindowsForAccessibilityCallback");
+        pw.println("            MagnificationCallbacks");
+        pw.println("            InputFilter");
+        pw.println("            Gesture");
+        pw.println("            AccessibilityService");
+        pw.println("            PMBroadcastReceiver");
+        pw.println("            UserBroadcastReceiver");
+        pw.println("            FingerprintGesture");
         pw.println("  stop-trace");
         pw.println("    Stop the debug tracing.");
     }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index 0fde0de..c70bf73 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -392,7 +392,7 @@
         return mBoundServices;
     }
 
-    int getClientStateLocked(boolean isUiAutomationRunning, boolean isTracingEnabled) {
+    int getClientStateLocked(boolean isUiAutomationRunning, int traceClientState) {
         int clientState = 0;
         final boolean a11yEnabled = isUiAutomationRunning
                 || isHandlingAccessibilityEventsLocked();
@@ -408,9 +408,9 @@
         if (mIsTextHighContrastEnabled) {
             clientState |= AccessibilityManager.STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED;
         }
-        if (isTracingEnabled) {
-            clientState |= AccessibilityManager.STATE_FLAG_ACCESSIBILITY_TRACING_ENABLED;
-        }
+
+        clientState |= traceClientState;
+
         return clientState;
     }
 
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index ff79469..b05dffe 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -16,6 +16,8 @@
 
 package com.android.server.accessibility;
 
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MANAGER_INTERNAL;
 import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
 import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED;
 
@@ -69,6 +71,7 @@
     private final AccessibilityEventSender mAccessibilityEventSender;
     private final AccessibilitySecurityPolicy mSecurityPolicy;
     private final AccessibilityUserManager mAccessibilityUserManager;
+    private final AccessibilityTraceManager mTraceManager;
 
     // Connections and window tokens for cross-user windows
     private final SparseArray<RemoteAccessibilityConnection>
@@ -151,6 +154,10 @@
                 // In some cases, onWindowsForAccessibilityChanged will be called immediately in
                 // setWindowsForAccessibilityCallback. We'll lost windows if flag is false.
                 mTrackingWindows = true;
+                if (traceWMEnabled()) {
+                    logTraceWM("setWindowsForAccessibilityCallback",
+                            "displayId=" + mDisplayId + ";callback=" + this);
+                }
                 result = mWindowManagerInternal.setWindowsForAccessibilityCallback(
                         mDisplayId, this);
                 if (!result) {
@@ -167,6 +174,10 @@
          */
         void stopTrackingWindowsLocked() {
             if (mTrackingWindows) {
+                if (traceWMEnabled()) {
+                    logTraceWM("setWindowsForAccessibilityCallback",
+                            "displayId=" + mDisplayId + ";callback=null");
+                }
                 mWindowManagerInternal.setWindowsForAccessibilityCallback(
                         mDisplayId, null);
                 mTrackingWindows = false;
@@ -373,6 +384,20 @@
             }
         }
 
+        /**
+         * Called when the display is reparented and becomes an embedded
+         * display.
+         *
+         * @param embeddedDisplayId The embedded display Id.
+         */
+        @Override
+        public void onDisplayReparented(int embeddedDisplayId) {
+            // Removes the un-used window observer for the embedded display.
+            synchronized (mLock) {
+                mDisplayWindowsObservers.remove(embeddedDisplayId);
+            }
+        }
+
         private boolean shouldUpdateWindowsLocked(boolean forceSend,
                 @NonNull List<WindowInfo> windows) {
             if (forceSend) {
@@ -844,19 +869,21 @@
     }
 
     /**
-     * Constructor for AccessibilityManagerService.
+     * Constructor for AccessibilityWindowManager.
      */
     public AccessibilityWindowManager(@NonNull Object lock, @NonNull Handler handler,
             @NonNull WindowManagerInternal windowManagerInternal,
             @NonNull AccessibilityEventSender accessibilityEventSender,
             @NonNull AccessibilitySecurityPolicy securityPolicy,
-            @NonNull AccessibilityUserManager accessibilityUserManager) {
+            @NonNull AccessibilityUserManager accessibilityUserManager,
+            @NonNull AccessibilityTraceManager traceManager) {
         mLock = lock;
         mHandler = handler;
         mWindowManagerInternal = windowManagerInternal;
         mAccessibilityEventSender = accessibilityEventSender;
         mSecurityPolicy = securityPolicy;
         mAccessibilityUserManager = accessibilityUserManager;
+        mTraceManager = traceManager;
     }
 
     /**
@@ -957,6 +984,9 @@
         final int windowId;
         boolean shouldComputeWindows = false;
         final IBinder token = window.asBinder();
+        if (traceWMEnabled()) {
+            logTraceWM("getDisplayIdForWindow", "token=" + token);
+        }
         final int displayId = mWindowManagerInternal.getDisplayIdForWindow(token);
         synchronized (mLock) {
             // We treat calls from a profile as if made by its parent as profiles
@@ -1003,9 +1033,15 @@
             registerIdLocked(leashToken, windowId);
         }
         if (shouldComputeWindows) {
+            if (traceWMEnabled()) {
+                logTraceWM("computeWindowsForAccessibility", "displayId=" + displayId);
+            }
             mWindowManagerInternal.computeWindowsForAccessibility(displayId);
         }
-
+        if (traceWMEnabled()) {
+            logTraceWM("setAccessibilityIdToSurfaceMetadata",
+                    "token=" + token + ";windowId=" + windowId);
+        }
         mWindowManagerInternal.setAccessibilityIdToSurfaceMetadata(token, windowId);
         return windowId;
     }
@@ -1139,6 +1175,10 @@
             mActiveWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
         }
         if (binder != null) {
+            if (traceWMEnabled()) {
+                logTraceWM("setAccessibilityIdToSurfaceMetadata", "token=" + binder
+                        + ";windowId=AccessibilityWindowInfo.UNDEFINED_WINDOW_ID");
+            }
             mWindowManagerInternal.setAccessibilityIdToSurfaceMetadata(
                     binder, AccessibilityWindowInfo.UNDEFINED_WINDOW_ID);
         }
@@ -1169,6 +1209,9 @@
      * @return The userId
      */
     public int getWindowOwnerUserId(@NonNull IBinder windowToken) {
+        if (traceWMEnabled()) {
+            logTraceWM("getWindowOwnerUserId", "token=" + windowToken);
+        }
         return mWindowManagerInternal.getWindowOwnerUserId(windowToken);
     }
 
@@ -1547,6 +1590,10 @@
         for (int i = 0; i < connectionList.size(); i++) {
             final RemoteAccessibilityConnection connection = connectionList.get(i);
             if (connection != null) {
+                if (traceIntConnEnabled()) {
+                    logTraceIntConn("notifyOutsideTouch");
+                }
+
                 try {
                     connection.getRemote().notifyOutsideTouch();
                 } catch (RemoteException re) {
@@ -1567,6 +1614,9 @@
      */
     public int getDisplayIdByUserIdAndWindowIdLocked(int userId, int windowId) {
         final IBinder windowToken = getWindowTokenForUserAndWindowIdLocked(userId, windowId);
+        if (traceWMEnabled()) {
+            logTraceWM("getDisplayIdForWindow", "token=" + windowToken);
+        }
         final int displayId = mWindowManagerInternal.getDisplayIdForWindow(windowToken);
         return displayId;
     }
@@ -1595,6 +1645,9 @@
      * @return The input focused windowId, or -1 if not found
      */
     private int findFocusedWindowId(int userId) {
+        if (traceWMEnabled()) {
+            logTraceWM("getFocusedWindowToken", "");
+        }
         final IBinder token = mWindowManagerInternal.getFocusedWindowToken();
         synchronized (mLock) {
             return findWindowIdLocked(userId, token);
@@ -1644,6 +1697,9 @@
                 return;
             }
         }
+        if (traceIntConnEnabled()) {
+            logTraceIntConn("notifyOutsideTouch");
+        }
         try {
             connection.getRemote().clearAccessibilityFocus();
         } catch (RemoteException re) {
@@ -1666,6 +1722,25 @@
         return null;
     }
 
+    private boolean traceWMEnabled() {
+        return mTraceManager.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MANAGER_INTERNAL);
+    }
+
+    private void logTraceWM(String methodName, String params) {
+        mTraceManager.logTrace("WindowManagerInternal." + methodName,
+                    FLAGS_WINDOW_MANAGER_INTERNAL, params);
+    }
+
+    private boolean traceIntConnEnabled() {
+        return mTraceManager.isA11yTracingEnabledForTypes(
+                FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION);
+    }
+
+    private void logTraceIntConn(String methodName) {
+        mTraceManager.logTrace(
+                    LOG_TAG + "." + methodName, FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION);
+    }
+
     /**
      * Associate the token of the embedded view hierarchy to the host view hierarchy.
      *
diff --git a/services/accessibility/java/com/android/server/accessibility/AutoclickController.java b/services/accessibility/java/com/android/server/accessibility/AutoclickController.java
index f5b0eb1..95f3560 100644
--- a/services/accessibility/java/com/android/server/accessibility/AutoclickController.java
+++ b/services/accessibility/java/com/android/server/accessibility/AutoclickController.java
@@ -16,6 +16,7 @@
 
 package com.android.server.accessibility;
 
+import android.accessibilityservice.AccessibilityTrace;
 import android.annotation.NonNull;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -56,6 +57,7 @@
 
     private static final String LOG_TAG = AutoclickController.class.getSimpleName();
 
+    private final AccessibilityTraceManager mTrace;
     private final Context mContext;
     private final int mUserId;
 
@@ -63,13 +65,18 @@
     private ClickScheduler mClickScheduler;
     private ClickDelayObserver mClickDelayObserver;
 
-    public AutoclickController(Context context, int userId) {
+    public AutoclickController(Context context, int userId, AccessibilityTraceManager trace) {
+        mTrace = trace;
         mContext = context;
         mUserId = userId;
     }
 
     @Override
     public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        if (mTrace.isA11yTracingEnabledForTypes(AccessibilityTrace.FLAGS_INPUT_FILTER)) {
+            mTrace.logTrace(LOG_TAG + ".onMotionEvent", AccessibilityTrace.FLAGS_INPUT_FILTER,
+                    "event=" + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags);
+        }
         if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
             if (mClickScheduler == null) {
                 Handler handler = new Handler(mContext.getMainLooper());
@@ -89,6 +96,10 @@
 
     @Override
     public void onKeyEvent(KeyEvent event, int policyFlags) {
+        if (mTrace.isA11yTracingEnabledForTypes(AccessibilityTrace.FLAGS_INPUT_FILTER)) {
+            mTrace.logTrace(LOG_TAG + ".onKeyEvent", AccessibilityTrace.FLAGS_INPUT_FILTER,
+                    "event=" + event + ";policyFlags=" + policyFlags);
+        }
         if (mClickScheduler != null) {
             if (KeyEvent.isModifierKey(event.getKeyCode())) {
                 mClickScheduler.updateMetaState(event.getMetaState());
diff --git a/services/accessibility/java/com/android/server/accessibility/KeyboardInterceptor.java b/services/accessibility/java/com/android/server/accessibility/KeyboardInterceptor.java
index bc379c2..b8250c0 100644
--- a/services/accessibility/java/com/android/server/accessibility/KeyboardInterceptor.java
+++ b/services/accessibility/java/com/android/server/accessibility/KeyboardInterceptor.java
@@ -16,6 +16,8 @@
 
 package com.android.server.accessibility;
 
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_INPUT_FILTER;
+
 import android.os.Handler;
 import android.os.Message;
 import android.os.SystemClock;
@@ -64,6 +66,10 @@
 
     @Override
     public void onKeyEvent(KeyEvent event, int policyFlags) {
+        if (mAms.getTraceManager().isA11yTracingEnabledForTypes(FLAGS_INPUT_FILTER)) {
+            mAms.getTraceManager().logTrace(LOG_TAG + ".onKeyEvent",
+                    FLAGS_INPUT_FILTER, "event=" + event + ";policyFlags=" + policyFlags);
+        }
         /*
          * Certain keys have system-level behavior that affects accessibility services.
          * Let that behavior settle before handling the keys
diff --git a/services/accessibility/java/com/android/server/accessibility/MotionEventInjector.java b/services/accessibility/java/com/android/server/accessibility/MotionEventInjector.java
index 2673cd1..5cbd1a2 100644
--- a/services/accessibility/java/com/android/server/accessibility/MotionEventInjector.java
+++ b/services/accessibility/java/com/android/server/accessibility/MotionEventInjector.java
@@ -16,6 +16,7 @@
 
 package com.android.server.accessibility;
 
+import android.accessibilityservice.AccessibilityTrace;
 import android.accessibilityservice.GestureDescription;
 import android.accessibilityservice.GestureDescription.GestureStep;
 import android.accessibilityservice.GestureDescription.TouchPoint;
@@ -68,6 +69,7 @@
     private final Handler mHandler;
     private final SparseArray<Boolean> mOpenGesturesInProgress = new SparseArray<>();
 
+    private final AccessibilityTraceManager mTrace;
     private IAccessibilityServiceClient mServiceInterfaceForCurrentGesture;
     private IntArray mSequencesInProgress = new IntArray(5);
     private boolean mIsDestroyed = false;
@@ -80,15 +82,17 @@
     /**
      * @param looper A looper on the main thread to use for dispatching new events
      */
-    public MotionEventInjector(Looper looper) {
+    public MotionEventInjector(Looper looper, AccessibilityTraceManager trace) {
         mHandler = new Handler(looper, this);
+        mTrace = trace;
     }
 
     /**
      * @param handler A handler to post messages. Exposes internal state for testing only.
      */
-    public MotionEventInjector(Handler handler) {
+    public MotionEventInjector(Handler handler, AccessibilityTraceManager trace) {
         mHandler = handler;
+        mTrace = trace;
     }
 
     /**
@@ -112,6 +116,12 @@
 
     @Override
     public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        if (mTrace.isA11yTracingEnabledForTypes(
+                AccessibilityTrace.FLAGS_INPUT_FILTER | AccessibilityTrace.FLAGS_GESTURE)) {
+            mTrace.logTrace(LOG_TAG + ".onMotionEvent",
+                    AccessibilityTrace.FLAGS_INPUT_FILTER | AccessibilityTrace.FLAGS_GESTURE,
+                    "event=" + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags);
+        }
         // MotionEventInjector would cancel any injected gesture when any MotionEvent arrives.
         // For user using an external device to control the pointer movement, it's almost
         // impossible to perform the gestures. Any slightly unintended movement results in the
diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
index 9547280..6cd23fc 100644
--- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
@@ -17,6 +17,7 @@
 package com.android.server.accessibility;
 
 import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.AccessibilityTrace;
 import android.accessibilityservice.IAccessibilityServiceClient;
 import android.annotation.Nullable;
 import android.app.UiAutomation;
@@ -269,6 +270,14 @@
                     // If the serviceInterface is null, the UiAutomation has been shut down on
                     // another thread.
                     if (serviceInterface != null) {
+                        if (mTrace.isA11yTracingEnabledForTypes(
+                                AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CLIENT)) {
+                            mTrace.logTrace("UiAutomationService.connectServiceUnknownThread",
+                                    AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CLIENT,
+                                    "serviceConnection=" + this + ";connectionId=" + mId
+                                    + "windowToken="
+                                    + mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY));
+                        }
                         serviceInterface.init(this, mId,
                                 mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY));
                     }
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
index e1af2c4..f95de4e 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -16,6 +16,8 @@
 
 package com.android.server.accessibility.gestures;
 
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_GESTURE;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_INPUT_FILTER;
 import static android.view.MotionEvent.ACTION_CANCEL;
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_HOVER_ENTER;
@@ -82,6 +84,7 @@
         implements GestureManifold.Listener {
 
     static final boolean DEBUG = false;
+    private static final long LOGGING_FLAGS = FLAGS_GESTURE | FLAGS_INPUT_FILTER;
 
     // Tag for logging received events.
     private static final String LOG_TAG = "TouchExplorer";
@@ -254,6 +257,10 @@
 
     @Override
     public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) {
+            mAms.getTraceManager().logTrace(LOG_TAG + ".onMotionEvent", LOGGING_FLAGS,
+                    "event=" + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags);
+        }
         if (!event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) {
             super.onMotionEvent(event, rawEvent, policyFlags);
             return;
@@ -303,6 +310,10 @@
 
     @Override
     public void onAccessibilityEvent(AccessibilityEvent event) {
+        if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) {
+            mAms.getTraceManager().logTrace(LOG_TAG + ".onAccessibilityEvent",
+                    LOGGING_FLAGS, "event=" + event);
+        }
         final int eventType = event.getEventType();
 
         if (eventType == TYPE_VIEW_HOVER_EXIT) {
@@ -341,6 +352,10 @@
 
     @Override
     public void onDoubleTapAndHold(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) {
+            mAms.getTraceManager().logTrace(LOG_TAG + ".onDoubleTapAndHold", LOGGING_FLAGS,
+                    "event=" + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags);
+        }
         if (mDispatcher.longPressWithTouchEvents(event, policyFlags)) {
             sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
             if (isSendMotionEventsEnabled()) {
@@ -357,6 +372,10 @@
 
     @Override
     public boolean onDoubleTap(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) {
+            mAms.getTraceManager().logTrace(LOG_TAG + ".onDoubleTap", LOGGING_FLAGS,
+                    "event=" + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags);
+        }
         mAms.onTouchInteractionEnd();
         // Remove pending event deliveries.
         mSendHoverEnterAndMoveDelayed.cancel();
@@ -389,6 +408,9 @@
 
     @Override
     public boolean onGestureStarted() {
+        if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) {
+            mAms.getTraceManager().logTrace(LOG_TAG + ".onGestureStarted", LOGGING_FLAGS);
+        }
         // We have to perform gesture detection, so
         // clear the current state and try to detect.
         mSendHoverEnterAndMoveDelayed.cancel();
@@ -402,6 +424,10 @@
 
     @Override
     public boolean onGestureCompleted(AccessibilityGestureEvent gestureEvent) {
+        if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) {
+            mAms.getTraceManager().logTrace(LOG_TAG + ".onGestureCompleted",
+                    LOGGING_FLAGS, "event=" + gestureEvent);
+        }
         endGestureDetection(true);
         mSendTouchInteractionEndDelayed.cancel();
         dispatchGesture(gestureEvent);
@@ -410,6 +436,10 @@
 
     @Override
     public boolean onGestureCancelled(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) {
+            mAms.getTraceManager().logTrace(LOG_TAG + ".onGestureCancelled", LOGGING_FLAGS,
+                    "event=" + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags);
+        }
         if (mState.isGestureDetecting()) {
             endGestureDetection(event.getActionMasked() == ACTION_UP);
             return true;
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index 1f66bfd..718da9e 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -16,6 +16,8 @@
 
 package com.android.server.accessibility.magnification;
 
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MANAGER_INTERNAL;
+
 import android.animation.Animator;
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
@@ -46,6 +48,7 @@
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.LocalServices;
 import com.android.server.accessibility.AccessibilityManagerService;
+import com.android.server.accessibility.AccessibilityTraceManager;
 import com.android.server.wm.WindowManagerInternal;
 
 import java.util.Locale;
@@ -135,6 +138,10 @@
          */
         @GuardedBy("mLock")
         boolean register() {
+            if (traceEnabled()) {
+                logTrace("setMagnificationCallbacks",
+                        "displayID=" + mDisplayId + ";callback=" + this);
+            }
             mRegistered = mControllerCtx.getWindowManager().setMagnificationCallbacks(
                     mDisplayId, this);
             if (!mRegistered) {
@@ -142,6 +149,10 @@
                 return false;
             }
             mSpecAnimationBridge.setEnabled(true);
+            if (traceEnabled()) {
+                logTrace("getMagnificationRegion",
+                        "displayID=" + mDisplayId + ";region=" + mMagnificationRegion);
+            }
             // Obtain initial state.
             mControllerCtx.getWindowManager().getMagnificationRegion(
                     mDisplayId, mMagnificationRegion);
@@ -162,6 +173,10 @@
         void unregister(boolean delete) {
             if (mRegistered) {
                 mSpecAnimationBridge.setEnabled(false);
+                if (traceEnabled()) {
+                    logTrace("setMagnificationCallbacks",
+                            "displayID=" + mDisplayId + ";callback=null");
+                }
                 mControllerCtx.getWindowManager().setMagnificationCallbacks(
                         mDisplayId, null);
                 mMagnificationRegion.setEmpty();
@@ -268,7 +283,7 @@
         }
 
         @Override
-        public void onRotationChanged(int rotation) {
+        public void onDisplaySizeChanged() {
             // Treat as context change and reset
             final Message m = PooledLambda.obtainMessage(
                     FullScreenMagnificationController::resetIfNeeded,
@@ -431,6 +446,10 @@
         void setForceShowMagnifiableBounds(boolean show) {
             if (mRegistered) {
                 mForceShowMagnifiableBounds = show;
+                if (traceEnabled()) {
+                    logTrace("setForceShowMagnifiableBounds",
+                            "displayID=" + mDisplayId + ";show=" + show);
+                }
                 mControllerCtx.getWindowManager().setForceShowMagnifiableBounds(
                         mDisplayId, show);
             }
@@ -1255,6 +1274,16 @@
         }
     }
 
+    private boolean traceEnabled() {
+        return mControllerCtx.getTraceManager().isA11yTracingEnabledForTypes(
+                FLAGS_WINDOW_MANAGER_INTERNAL);
+    }
+
+    private void logTrace(String methodName, String params) {
+        mControllerCtx.getTraceManager().logTrace(
+                "WindowManagerInternal." + methodName, FLAGS_WINDOW_MANAGER_INTERNAL, params);
+    }
+
     @Override
     public String toString() {
         StringBuilder builder = new StringBuilder();
@@ -1320,6 +1349,13 @@
                     mEnabled = enabled;
                     if (!mEnabled) {
                         mSentMagnificationSpec.clear();
+                        if (mControllerCtx.getTraceManager().isA11yTracingEnabledForTypes(
+                                FLAGS_WINDOW_MANAGER_INTERNAL)) {
+                            mControllerCtx.getTraceManager().logTrace(
+                                    "WindowManagerInternal.setMagnificationSpec",
+                                    FLAGS_WINDOW_MANAGER_INTERNAL,
+                                    "displayID=" + mDisplayId + ";spec=" + mSentMagnificationSpec);
+                        }
                         mControllerCtx.getWindowManager().setMagnificationSpec(
                                 mDisplayId, mSentMagnificationSpec);
                     }
@@ -1367,6 +1403,13 @@
                 }
 
                 mSentMagnificationSpec.setTo(spec);
+                if (mControllerCtx.getTraceManager().isA11yTracingEnabledForTypes(
+                        FLAGS_WINDOW_MANAGER_INTERNAL)) {
+                    mControllerCtx.getTraceManager().logTrace(
+                            "WindowManagerInternal.setMagnificationSpec",
+                            FLAGS_WINDOW_MANAGER_INTERNAL,
+                            "displayID=" + mDisplayId + ";spec=" + mSentMagnificationSpec);
+                }
                 mControllerCtx.getWindowManager().setMagnificationSpec(
                         mDisplayId, mSentMagnificationSpec);
             }
@@ -1455,6 +1498,7 @@
     public static class ControllerContext {
         private final Context mContext;
         private final AccessibilityManagerService mAms;
+        private final AccessibilityTraceManager mTrace;
         private final WindowManagerInternal mWindowManager;
         private final Handler mHandler;
         private final Long mAnimationDuration;
@@ -1469,6 +1513,7 @@
                 long animationDuration) {
             mContext = context;
             mAms = ams;
+            mTrace = ams.getTraceManager();
             mWindowManager = windowManager;
             mHandler = handler;
             mAnimationDuration = animationDuration;
@@ -1491,6 +1536,14 @@
         }
 
         /**
+         * @return AccessibilityTraceManager
+         */
+        @NonNull
+        public AccessibilityTraceManager getTraceManager() {
+            return mTrace;
+        }
+
+        /**
          * @return WindowManagerInternal
          */
         @NonNull
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index f7d1b9a..c3d8d4c 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -61,6 +61,7 @@
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.accessibility.AccessibilityManagerService;
+import com.android.server.accessibility.AccessibilityTraceManager;
 import com.android.server.accessibility.gestures.GestureUtils;
 
 /**
@@ -142,12 +143,13 @@
 
     public FullScreenMagnificationGestureHandler(@UiContext Context context,
             FullScreenMagnificationController fullScreenMagnificationController,
+            AccessibilityTraceManager trace,
             Callback callback,
             boolean detectTripleTap,
             boolean detectShortcutTrigger,
             @NonNull WindowMagnificationPromptController promptController,
             int displayId) {
-        super(displayId, detectTripleTap, detectShortcutTrigger, callback);
+        super(displayId, detectTripleTap, detectShortcutTrigger, trace, callback);
         if (DEBUG_ALL) {
             Log.i(mLogTag,
                     "FullScreenMagnificationGestureHandler(detectTripleTap = " + detectTripleTap
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index f9aecd7..8aacafb 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -411,8 +411,7 @@
         synchronized (mLock) {
             if (mWindowMagnificationMgr == null) {
                 mWindowMagnificationMgr = new WindowMagnificationManager(mContext,
-                        mAms.getCurrentUserIdLocked(),
-                        this);
+                        mAms.getCurrentUserIdLocked(), this, mAms.getTraceManager());
             }
             return mWindowMagnificationMgr;
         }
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java
index bbe40b6..19b3396 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java
@@ -20,11 +20,13 @@
 import static android.view.MotionEvent.ACTION_CANCEL;
 import static android.view.MotionEvent.ACTION_UP;
 
+import android.accessibilityservice.AccessibilityTrace;
 import android.annotation.NonNull;
 import android.util.Log;
 import android.util.Slog;
 import android.view.MotionEvent;
 
+import com.android.server.accessibility.AccessibilityTraceManager;
 import com.android.server.accessibility.BaseEventStreamTransformation;
 
 import java.util.ArrayDeque;
@@ -99,14 +101,17 @@
         void onTripleTapped(int displayId, int mode);
     }
 
+    private final AccessibilityTraceManager mTrace;
     protected final Callback mCallback;
 
     protected MagnificationGestureHandler(int displayId, boolean detectTripleTap,
             boolean detectShortcutTrigger,
+            AccessibilityTraceManager trace,
             @NonNull Callback callback) {
         mDisplayId = displayId;
         mDetectTripleTap = detectTripleTap;
         mDetectShortcutTrigger = detectShortcutTrigger;
+        mTrace = trace;
         mCallback = callback;
 
         mDebugInputEventHistory = DEBUG_EVENT_STREAM ? new ArrayDeque<>() : null;
@@ -118,6 +123,12 @@
         if (DEBUG_ALL) {
             Slog.i(mLogTag, "onMotionEvent(" + event + ")");
         }
+        if (mTrace.isA11yTracingEnabledForTypes(
+                AccessibilityTrace.FLAGS_INPUT_FILTER | AccessibilityTrace.FLAGS_GESTURE)) {
+            mTrace.logTrace("MagnificationGestureHandler.onMotionEvent",
+                    AccessibilityTrace.FLAGS_INPUT_FILTER | AccessibilityTrace.FLAGS_GESTURE,
+                    "event=" + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags);
+        }
         if (DEBUG_EVENT_STREAM) {
             storeEventInto(mDebugInputEventHistory, event);
         }
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java
index 993027d..5277425 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java
@@ -16,6 +16,9 @@
 
 package com.android.server.accessibility.magnification;
 
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MAGNIFICATION_CONNECTION;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK;
 import static android.os.IBinder.DeathRecipient;
 
 import android.annotation.NonNull;
@@ -27,6 +30,8 @@
 import android.view.accessibility.IWindowMagnificationConnectionCallback;
 import android.view.accessibility.MagnificationAnimationCallback;
 
+import com.android.server.accessibility.AccessibilityTraceManager;
+
 /**
  * A wrapper of {@link IWindowMagnificationConnection}.
  */
@@ -36,9 +41,12 @@
     private static final String TAG = "WindowMagnificationConnectionWrapper";
 
     private final @NonNull IWindowMagnificationConnection mConnection;
+    private final @NonNull AccessibilityTraceManager mTrace;
 
-    WindowMagnificationConnectionWrapper(@NonNull IWindowMagnificationConnection connection) {
+    WindowMagnificationConnectionWrapper(@NonNull IWindowMagnificationConnection connection,
+            @NonNull AccessibilityTraceManager trace) {
         mConnection = connection;
+        mTrace = trace;
     }
 
     //Should not use this instance anymore after calling it.
@@ -52,9 +60,15 @@
 
     boolean enableWindowMagnification(int displayId, float scale, float centerX, float centerY,
             @Nullable MagnificationAnimationCallback callback) {
+        if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) {
+            mTrace.logTrace(TAG + ".enableWindowMagnification",
+                    FLAGS_WINDOW_MAGNIFICATION_CONNECTION,
+                    "displayId=" + displayId + ";scale=" + scale + ";centerX=" + centerX
+                    + ";centerY=" + centerY + ";callback=" + callback);
+        }
         try {
             mConnection.enableWindowMagnification(displayId, scale, centerX, centerY,
-                    transformToRemoteCallback(callback));
+                    transformToRemoteCallback(callback, mTrace));
         } catch (RemoteException e) {
             if (DBG) {
                 Slog.e(TAG, "Error calling enableWindowMagnification()", e);
@@ -65,6 +79,10 @@
     }
 
     boolean setScale(int displayId, float scale) {
+        if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) {
+            mTrace.logTrace(TAG + ".setScale", FLAGS_WINDOW_MAGNIFICATION_CONNECTION,
+                    "displayId=" + displayId + ";scale=" + scale);
+        }
         try {
             mConnection.setScale(displayId, scale);
         } catch (RemoteException e) {
@@ -78,8 +96,14 @@
 
     boolean disableWindowMagnification(int displayId,
             @Nullable MagnificationAnimationCallback callback) {
+        if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) {
+            mTrace.logTrace(TAG + ".disableWindowMagnification",
+                    FLAGS_WINDOW_MAGNIFICATION_CONNECTION,
+                    "displayId=" + displayId + ";callback=" + callback);
+        }
         try {
-            mConnection.disableWindowMagnification(displayId, transformToRemoteCallback(callback));
+            mConnection.disableWindowMagnification(displayId,
+                    transformToRemoteCallback(callback, mTrace));
         } catch (RemoteException e) {
             if (DBG) {
                 Slog.e(TAG, "Error calling disableWindowMagnification()", e);
@@ -90,6 +114,10 @@
     }
 
     boolean moveWindowMagnifier(int displayId, float offsetX, float offsetY) {
+        if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) {
+            mTrace.logTrace(TAG + ".moveWindowMagnifier", FLAGS_WINDOW_MAGNIFICATION_CONNECTION,
+                    "displayId=" + displayId + ";offsetX=" + offsetX + ";offsetY=" + offsetY);
+        }
         try {
             mConnection.moveWindowMagnifier(displayId, offsetX, offsetY);
         } catch (RemoteException e) {
@@ -102,6 +130,11 @@
     }
 
     boolean showMagnificationButton(int displayId, int magnificationMode) {
+        if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) {
+            mTrace.logTrace(TAG + ".showMagnificationButton",
+                    FLAGS_WINDOW_MAGNIFICATION_CONNECTION,
+                    "displayId=" + displayId + ";mode=" + magnificationMode);
+        }
         try {
             mConnection.showMagnificationButton(displayId, magnificationMode);
         } catch (RemoteException e) {
@@ -114,6 +147,10 @@
     }
 
     boolean removeMagnificationButton(int displayId) {
+        if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) {
+            mTrace.logTrace(TAG + ".removeMagnificationButton",
+                    FLAGS_WINDOW_MAGNIFICATION_CONNECTION, "displayId=" + displayId);
+        }
         try {
             mConnection.removeMagnificationButton(displayId);
         } catch (RemoteException e) {
@@ -126,6 +163,14 @@
     }
 
     boolean setConnectionCallback(IWindowMagnificationConnectionCallback connectionCallback) {
+        if (mTrace.isA11yTracingEnabledForTypes(
+                FLAGS_WINDOW_MAGNIFICATION_CONNECTION
+                | FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK)) {
+            mTrace.logTrace(TAG + ".setConnectionCallback",
+                    FLAGS_WINDOW_MAGNIFICATION_CONNECTION
+                    | FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK,
+                    "callback=" + connectionCallback);
+        }
         try {
             mConnection.setConnectionCallback(connectionCallback);
         } catch (RemoteException e) {
@@ -139,25 +184,38 @@
 
     private static @Nullable
             IRemoteMagnificationAnimationCallback transformToRemoteCallback(
-            MagnificationAnimationCallback callback) {
+            MagnificationAnimationCallback callback, AccessibilityTraceManager trace) {
         if (callback == null) {
             return null;
         }
-        return new RemoteAnimationCallback(callback);
+        return new RemoteAnimationCallback(callback, trace);
     }
 
     private static class RemoteAnimationCallback extends
             IRemoteMagnificationAnimationCallback.Stub {
-
         private final MagnificationAnimationCallback mCallback;
+        private final AccessibilityTraceManager mTrace;
 
-        RemoteAnimationCallback(@NonNull MagnificationAnimationCallback callback) {
+        RemoteAnimationCallback(@NonNull MagnificationAnimationCallback callback,
+                               @NonNull AccessibilityTraceManager trace) {
             mCallback = callback;
+            mTrace = trace;
+            if (mTrace.isA11yTracingEnabledForTypes(
+                    FLAGS_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK)) {
+                mTrace.logTrace("RemoteAnimationCallback.constructor",
+                        FLAGS_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK, "callback=" + callback);
+            }
         }
 
         @Override
         public void onResult(boolean success) throws RemoteException {
             mCallback.onResult(success);
+            if (mTrace.isA11yTracingEnabledForTypes(
+                    FLAGS_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK)) {
+                mTrace.logTrace("RemoteAnimationCallback.onResult",
+                        FLAGS_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK, "success=" + success);
+            }
+
         }
     }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
index 4fb9a03..b26d364 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
@@ -34,6 +34,7 @@
 import android.view.MotionEvent;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.accessibility.AccessibilityTraceManager;
 import com.android.server.accessibility.EventStreamTransformation;
 import com.android.server.accessibility.gestures.MultiTap;
 import com.android.server.accessibility.gestures.MultiTapAndHold;
@@ -89,9 +90,10 @@
 
     public WindowMagnificationGestureHandler(@UiContext Context context,
             WindowMagnificationManager windowMagnificationMgr,
+            AccessibilityTraceManager trace,
             Callback callback,
             boolean detectTripleTap, boolean detectShortcutTrigger, int displayId) {
-        super(displayId, detectTripleTap, detectShortcutTrigger, callback);
+        super(displayId, detectTripleTap, detectShortcutTrigger, trace, callback);
         if (DEBUG_ALL) {
             Slog.i(mLogTag,
                     "WindowMagnificationGestureHandler() , displayId = " + displayId + ")");
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
index 938cb73..7a111d8 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
@@ -16,6 +16,9 @@
 
 package com.android.server.accessibility.magnification;
 
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MAGNIFICATION_CONNECTION;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
@@ -39,6 +42,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BackgroundThread;
 import com.android.server.LocalServices;
+import com.android.server.accessibility.AccessibilityTraceManager;
 import com.android.server.statusbar.StatusBarManagerInternal;
 
 /**
@@ -111,11 +115,14 @@
     }
 
     private final Callback mCallback;
+    private final AccessibilityTraceManager mTrace;
 
-    public WindowMagnificationManager(Context context, int userId, @NonNull Callback callback) {
+    public WindowMagnificationManager(Context context, int userId, @NonNull Callback callback,
+            AccessibilityTraceManager trace) {
         mContext = context;
         mUserId = userId;
         mCallback = callback;
+        mTrace = trace;
     }
 
     /**
@@ -135,7 +142,7 @@
                 mConnectionWrapper = null;
             }
             if (connection != null) {
-                mConnectionWrapper = new WindowMagnificationConnectionWrapper(connection);
+                mConnectionWrapper = new WindowMagnificationConnectionWrapper(connection, mTrace);
             }
 
             if (mConnectionWrapper != null) {
@@ -197,7 +204,10 @@
                 }
             }
         }
-
+        if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) {
+            mTrace.logTrace(TAG + ".requestWindowMagnificationConnection",
+                    FLAGS_WINDOW_MAGNIFICATION_CONNECTION, "connect=" + connect);
+        }
         final long identity = Binder.clearCallingIdentity();
         try {
             final StatusBarManagerInternal service = LocalServices.getService(
@@ -511,6 +521,12 @@
 
         @Override
         public void onWindowMagnifierBoundsChanged(int displayId, Rect bounds) {
+            if (mTrace.isA11yTracingEnabledForTypes(
+                    FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK)) {
+                mTrace.logTrace(TAG + "ConnectionCallback.onWindowMagnifierBoundsChanged",
+                        FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK,
+                        "displayId=" + displayId + ";bounds=" + bounds);
+            }
             synchronized (mLock) {
                 WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
                 if (magnifier == null) {
@@ -527,11 +543,23 @@
         @Override
         public void onChangeMagnificationMode(int displayId, int magnificationMode)
                 throws RemoteException {
+            if (mTrace.isA11yTracingEnabledForTypes(
+                    FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK)) {
+                mTrace.logTrace(TAG + "ConnectionCallback.onChangeMagnificationMode",
+                        FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK,
+                        "displayId=" + displayId + ";mode=" + magnificationMode);
+            }
             //TODO: Uses this method to change the magnification mode on non-default display.
         }
 
         @Override
         public void onSourceBoundsChanged(int displayId, Rect sourceBounds) {
+            if (mTrace.isA11yTracingEnabledForTypes(
+                    FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK)) {
+                mTrace.logTrace(TAG + "ConnectionCallback.onSourceBoundsChanged",
+                        FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK,
+                        "displayId=" + displayId + ";source=" + sourceBounds);
+            }
             synchronized (mLock) {
                 WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
                 if (magnifier == null) {
@@ -543,11 +571,23 @@
 
         @Override
         public void onPerformScaleAction(int displayId, float scale) {
+            if (mTrace.isA11yTracingEnabledForTypes(
+                    FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK)) {
+                mTrace.logTrace(TAG + "ConnectionCallback.onPerformScaleAction",
+                        FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK,
+                        "displayId=" + displayId + ";scale=" + scale);
+            }
             mCallback.onPerformScaleAction(displayId, scale);
         }
 
         @Override
         public void onAccessibilityActionPerformed(int displayId) {
+            if (mTrace.isA11yTracingEnabledForTypes(
+                    FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK)) {
+                mTrace.logTrace(TAG + "ConnectionCallback.onAccessibilityActionPerformed",
+                        FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK,
+                        "displayId=" + displayId);
+            }
             mCallback.onAccessibilityActionPerformed(displayId);
         }
 
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 55b982b..cbc3238 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -107,6 +107,7 @@
         ":display-device-config",
         ":display-layout-config",
         ":device-state-config",
+        ":guiconstants_aidl",
         "java/com/android/server/EventLogTags.logtags",
         "java/com/android/server/am/EventLogTags.logtags",
         "java/com/android/server/wm/EventLogTags.logtags",
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index b2d35f4..ed7d185 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -35,11 +35,14 @@
 import android.hardware.ICameraService;
 import android.hardware.ICameraServiceProxy;
 import android.hardware.camera2.CameraMetadata;
+import android.hardware.devicestate.DeviceStateManager;
+import android.hardware.devicestate.DeviceStateManager.FoldStateListener;
 import android.hardware.display.DisplayManager;
 import android.media.AudioManager;
 import android.nfc.INfcAdapter;
 import android.os.Binder;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.IBinder;
 import android.os.Message;
 import android.os.Process;
@@ -57,8 +60,8 @@
 import android.view.Surface;
 import android.view.WindowManagerGlobal;
 
-import com.android.internal.annotations.GuardedBy;
 import com.android.framework.protobuf.nano.MessageNano;
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.LocalServices;
 import com.android.server.ServiceThread;
@@ -447,6 +450,8 @@
         }
     };
 
+    private final FoldStateListener mFoldStateListener;
+
     public CameraServiceProxy(Context context) {
         super(context);
         mContext = context;
@@ -459,6 +464,14 @@
         // Don't keep any extra logging threads if not needed
         mLogWriterService.setKeepAliveTime(1, TimeUnit.SECONDS);
         mLogWriterService.allowCoreThreadTimeOut(true);
+
+        mFoldStateListener = new FoldStateListener(mContext, folded -> {
+            if (folded) {
+                setDeviceStateFlags(ICameraService.DEVICE_STATE_FOLDED);
+            } else {
+                clearDeviceStateFlags(ICameraService.DEVICE_STATE_FOLDED);
+            }
+        });
     }
 
     /**
@@ -471,7 +484,7 @@
      *
      * @see #clearDeviceStateFlags(int)
      */
-    public void setDeviceStateFlags(@DeviceStateFlags int deviceStateFlags) {
+    private void setDeviceStateFlags(@DeviceStateFlags int deviceStateFlags) {
         synchronized (mLock) {
             mHandler.removeMessages(MSG_NOTIFY_DEVICE_STATE);
             mDeviceState |= deviceStateFlags;
@@ -491,7 +504,7 @@
      *
      * @see #setDeviceStateFlags(int)
      */
-    public void clearDeviceStateFlags(@DeviceStateFlags int deviceStateFlags) {
+    private void clearDeviceStateFlags(@DeviceStateFlags int deviceStateFlags) {
         synchronized (mLock) {
             mHandler.removeMessages(MSG_NOTIFY_DEVICE_STATE);
             mDeviceState &= ~deviceStateFlags;
@@ -555,6 +568,9 @@
             } catch (RemoteException e) {
                 Log.e(TAG, "Failed to register display window listener!");
             }
+
+            mContext.getSystemService(DeviceStateManager.class)
+                    .registerCallback(new HandlerExecutor(mHandler), mFoldStateListener);
         }
     }
 
diff --git a/services/core/java/com/android/server/compat/overrides/AppCompatOverridesParser.java b/services/core/java/com/android/server/compat/overrides/AppCompatOverridesParser.java
new file mode 100644
index 0000000..a81213d
--- /dev/null
+++ b/services/core/java/com/android/server/compat/overrides/AppCompatOverridesParser.java
@@ -0,0 +1,383 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.compat.overrides;
+
+import static android.content.pm.PackageManager.MATCH_ANY_USER;
+
+import static java.util.Collections.emptyMap;
+import static java.util.Collections.emptySet;
+
+import android.app.compat.PackageOverride;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.KeyValueListParser;
+import android.util.Slog;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+/**
+ * A utility class for parsing App Compat Overrides flags.
+ *
+ * @hide
+ */
+final class AppCompatOverridesParser {
+    /**
+     * Flag for specifying all compat change IDs owned by a namespace. See {@link
+     * #parseOwnedChangeIds} for information on how this flag is parsed.
+     */
+    static final String FLAG_OWNED_CHANGE_IDS = "owned_change_ids";
+
+    /**
+     * Flag for immediately removing overrides for certain packages and change IDs (from the compat
+     * platform), as well as stopping to apply them, in case of an emergency. See {@link
+     * #parseRemoveOverrides} for information on how this flag is parsed.
+     */
+    static final String FLAG_REMOVE_OVERRIDES = "remove_overrides";
+
+    private static final String TAG = "AppCompatOverridesParser";
+
+    private static final String WILDCARD_SYMBOL = "*";
+
+    private static final Pattern BOOLEAN_PATTERN =
+            Pattern.compile("true|false", Pattern.CASE_INSENSITIVE);
+
+    private static final String WILDCARD_NO_OWNED_CHANGE_IDS_WARNING =
+            "Wildcard can't be used in '" + FLAG_REMOVE_OVERRIDES + "' flag with an empty "
+                    + FLAG_OWNED_CHANGE_IDS + "' flag";
+
+    private final PackageManager mPackageManager;
+
+    AppCompatOverridesParser(PackageManager packageManager) {
+        mPackageManager = packageManager;
+    }
+
+    /**
+     * Parses the given {@code configStr} and returns a map from package name to a set of change
+     * IDs to remove for that package.
+     *
+     * <p>The given {@code configStr} is expected to either be:
+     *
+     * <ul>
+     *   <li>'*' (wildcard), to indicate that all owned overrides, specified in {@code
+     *   ownedChangeIds}, for all installed packages should be removed.
+     *   <li>A comma separated key value list, where the key is a package name and the value is
+     *       either:
+     *       <ul>
+     *         <li>'*' (wildcard), to indicate that all owned overrides, specified in {@code
+     *         ownedChangeIds} for that package should be removed.
+     *         <li>A colon separated list of change IDs to remove for that package.
+     *       </ul>
+     * </ul>
+     *
+     * <p>If the given {@code configStr} doesn't match the expected format, an empty map will be
+     * returned. If a specific change ID isn't a valid long, it will be ignored.
+     */
+    Map<String, Set<Long>> parseRemoveOverrides(String configStr, Set<Long> ownedChangeIds) {
+        if (configStr.isEmpty()) {
+            return emptyMap();
+        }
+
+        Map<String, Set<Long>> result = new ArrayMap<>();
+        if (configStr.equals(WILDCARD_SYMBOL)) {
+            if (ownedChangeIds.isEmpty()) {
+                Slog.w(TAG, WILDCARD_NO_OWNED_CHANGE_IDS_WARNING);
+                return emptyMap();
+            }
+            List<ApplicationInfo> installedApps = mPackageManager.getInstalledApplications(
+                    MATCH_ANY_USER);
+            for (ApplicationInfo appInfo : installedApps) {
+                result.put(appInfo.packageName, ownedChangeIds);
+            }
+            return result;
+        }
+
+        KeyValueListParser parser = new KeyValueListParser(',');
+        try {
+            parser.setString(configStr);
+        } catch (IllegalArgumentException e) {
+            Slog.w(
+                    TAG,
+                    "Invalid format in '" + FLAG_REMOVE_OVERRIDES + "' flag: " + configStr, e);
+            return emptyMap();
+        }
+        for (int i = 0; i < parser.size(); i++) {
+            String packageName = parser.keyAt(i);
+            String changeIdsStr = parser.getString(packageName, /* def= */ "");
+            if (changeIdsStr.equals(WILDCARD_SYMBOL)) {
+                if (ownedChangeIds.isEmpty()) {
+                    Slog.w(TAG, WILDCARD_NO_OWNED_CHANGE_IDS_WARNING);
+                    continue;
+                }
+                result.put(packageName, ownedChangeIds);
+            } else {
+                for (String changeIdStr : changeIdsStr.split(":")) {
+                    try {
+                        long changeId = Long.parseLong(changeIdStr);
+                        result.computeIfAbsent(packageName, k -> new ArraySet<>()).add(changeId);
+                    } catch (NumberFormatException e) {
+                        Slog.w(
+                                TAG,
+                                "Invalid change ID in '" + FLAG_REMOVE_OVERRIDES + "' flag: "
+                                        + changeIdStr, e);
+                    }
+                }
+            }
+        }
+
+        return result;
+    }
+
+
+    /**
+     * Parses the given {@code configStr}, that is expected to be a comma separated list of change
+     * IDs, into a set.
+     *
+     * <p>If any of the change IDs isn't a valid long, it will be ignored.
+     */
+    static Set<Long> parseOwnedChangeIds(String configStr) {
+        if (configStr.isEmpty()) {
+            return emptySet();
+        }
+
+        Set<Long> result = new ArraySet<>();
+        for (String changeIdStr : configStr.split(",")) {
+            try {
+                result.add(Long.parseLong(changeIdStr));
+            } catch (NumberFormatException e) {
+                Slog.w(TAG,
+                        "Invalid change ID in '" + FLAG_OWNED_CHANGE_IDS + "' flag: " + changeIdStr,
+                        e);
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Parses the given {@code configStr}, that is expected to be a comma separated list of changes
+     * overrides, and returns a {@link PackageOverrides}.
+     *
+     * <p>Each change override is in the following format:
+     * '<change-id>:<min-version-code?>:<max-version-code?>:<enabled?>'. If <enabled> is empty,
+     * this indicates that any override for the specified change ID should be removed.
+     *
+     * <p>If there are multiple overrides that should be added with the same change ID, the one
+     * that best fits the given {@code versionCode} is added.
+     *
+     * <p>Any overrides whose change ID is in {@code changeIdsToSkip} are ignored.
+     *
+     * <p>If a change override entry in {@code configStr} is invalid, it will be ignored. If the
+     * same change ID is both added and removed, i.e., has a change override entry with an empty
+     * enabled and another with a non-empty enabled, the change ID will only be removed.
+     */
+    static PackageOverrides parsePackageOverrides(
+            String configStr, long versionCode, Set<Long> changeIdsToSkip) {
+        if (configStr.isEmpty()) {
+            return new PackageOverrides();
+        }
+        PackageOverrideComparator comparator = new PackageOverrideComparator(versionCode);
+        Map<Long, PackageOverride> overridesToAdd = new ArrayMap<>();
+        Set<Long> overridesToRemove = new ArraySet<>();
+        for (String overrideEntryString : configStr.split(",")) {
+            List<String> changeIdAndVersions = Arrays.asList(overrideEntryString.split(":", 4));
+            if (changeIdAndVersions.size() != 4) {
+                Slog.w(TAG, "Invalid change override entry: " + overrideEntryString);
+                continue;
+            }
+            long changeId;
+            try {
+                changeId = Long.parseLong(changeIdAndVersions.get(0));
+            } catch (NumberFormatException e) {
+                Slog.w(TAG, "Invalid change ID in override entry: " + overrideEntryString, e);
+                continue;
+            }
+
+            if (changeIdsToSkip.contains(changeId)) {
+                continue;
+            }
+
+            String minVersionCodeStr = changeIdAndVersions.get(1);
+            String maxVersionCodeStr = changeIdAndVersions.get(2);
+
+            String enabledStr = changeIdAndVersions.get(3);
+            if (enabledStr.isEmpty()) {
+                if (!minVersionCodeStr.isEmpty() || !maxVersionCodeStr.isEmpty()) {
+                    Slog.w(
+                            TAG,
+                            "min/max version code should be empty if enabled is empty: "
+                                    + overrideEntryString);
+                }
+                overridesToRemove.add(changeId);
+                continue;
+            }
+            if (!BOOLEAN_PATTERN.matcher(enabledStr).matches()) {
+                Slog.w(TAG, "Invalid enabled string in override entry: " + overrideEntryString);
+                continue;
+            }
+            boolean enabled = Boolean.parseBoolean(enabledStr);
+            PackageOverride.Builder overrideBuilder = new PackageOverride.Builder().setEnabled(
+                    enabled);
+            try {
+                if (!minVersionCodeStr.isEmpty()) {
+                    overrideBuilder.setMinVersionCode(Long.parseLong(minVersionCodeStr));
+                }
+                if (!maxVersionCodeStr.isEmpty()) {
+                    overrideBuilder.setMaxVersionCode(Long.parseLong(maxVersionCodeStr));
+                }
+            } catch (NumberFormatException e) {
+                Slog.w(TAG,
+                        "Invalid min/max version code in override entry: " + overrideEntryString,
+                        e);
+                continue;
+            }
+
+            try {
+                PackageOverride override = overrideBuilder.build();
+                if (!overridesToAdd.containsKey(changeId)
+                        || comparator.compare(override, overridesToAdd.get(changeId)) < 0) {
+                    overridesToAdd.put(changeId, override);
+                }
+            } catch (IllegalArgumentException e) {
+                Slog.w(TAG, "Failed to build PackageOverride", e);
+            }
+        }
+
+        for (Long changeId : overridesToRemove) {
+            if (overridesToAdd.containsKey(changeId)) {
+                Slog.w(
+                        TAG,
+                        "Change ID ["
+                                + changeId
+                                + "] is both added and removed in package override flag: "
+                                + configStr);
+                overridesToAdd.remove(changeId);
+            }
+        }
+
+        return new PackageOverrides(overridesToAdd, overridesToRemove);
+    }
+
+    /**
+     * A container for a map from change ID to {@link PackageOverride} to add and a set of change
+     * IDs to remove overrides for.
+     *
+     * <p>The map of overrides to add and the set of overrides to remove are mutually exclusive.
+     */
+    static final class PackageOverrides {
+        public final Map<Long, PackageOverride> overridesToAdd;
+        public final Set<Long> overridesToRemove;
+
+        PackageOverrides() {
+            this(emptyMap(), emptySet());
+        }
+
+        PackageOverrides(Map<Long, PackageOverride> overridesToAdd, Set<Long> overridesToRemove) {
+            this.overridesToAdd = overridesToAdd;
+            this.overridesToRemove = overridesToRemove;
+        }
+    }
+
+    /**
+     * A {@link Comparator} that compares @link PackageOverride} instances with respect to a
+     * specified {@code versionCode} as follows:
+     *
+     * <ul>
+     *   <li>Prefer the {@link PackageOverride} whose version range contains {@code versionCode}.
+     *   <li>Otherwise, prefer the {@link PackageOverride} whose version range is closest to {@code
+     *       versionCode} from below.
+     *   <li>Otherwise, prefer the {@link PackageOverride} whose version range is closest to {@code
+     *       versionCode} from above.
+     * </ul>
+     */
+    private static final class PackageOverrideComparator implements Comparator<PackageOverride> {
+        private final long mVersionCode;
+
+        PackageOverrideComparator(long versionCode) {
+            this.mVersionCode = versionCode;
+        }
+
+        @Override
+        public int compare(PackageOverride o1, PackageOverride o2) {
+            // Prefer overrides whose version range contains versionCode.
+            boolean isVersionInRange1 = isVersionInRange(o1, mVersionCode);
+            boolean isVersionInRange2 = isVersionInRange(o2, mVersionCode);
+            if (isVersionInRange1 != isVersionInRange2) {
+                return isVersionInRange1 ? -1 : 1;
+            }
+
+            // Otherwise, prefer overrides whose version range is before versionCode.
+            boolean isVersionAfterRange1 = isVersionAfterRange(o1, mVersionCode);
+            boolean isVersionAfterRange2 = isVersionAfterRange(o2, mVersionCode);
+            if (isVersionAfterRange1 != isVersionAfterRange2) {
+                return isVersionAfterRange1 ? -1 : 1;
+            }
+
+            // If both overrides' version ranges are either before or after versionCode, prefer
+            // those whose version range is closer to versionCode.
+            return Long.compare(
+                    getVersionProximity(o1, mVersionCode), getVersionProximity(o2, mVersionCode));
+        }
+
+        /**
+         * Returns true if the version range in the given {@code override} contains {@code
+         * versionCode}.
+         */
+        private static boolean isVersionInRange(PackageOverride override, long versionCode) {
+            return override.getMinVersionCode() <= versionCode
+                    && versionCode <= override.getMaxVersionCode();
+        }
+
+        /**
+         * Returns true if the given {@code versionCode} is strictly after the version range in the
+         * given {@code override}.
+         */
+        private static boolean isVersionAfterRange(PackageOverride override, long versionCode) {
+            return override.getMaxVersionCode() < versionCode;
+        }
+
+        /**
+         * Returns true if the given {@code versionCode} is strictly before the version range in the
+         * given {@code override}.
+         */
+        private static boolean isVersionBeforeRange(PackageOverride override, long versionCode) {
+            return override.getMinVersionCode() > versionCode;
+        }
+
+        /**
+         * In case the given {@code versionCode} is strictly before or after the version range in
+         * the given {@code override}, returns the distance from it, otherwise returns zero.
+         */
+        private static long getVersionProximity(PackageOverride override, long versionCode) {
+            if (isVersionAfterRange(override, versionCode)) {
+                return versionCode - override.getMaxVersionCode();
+            }
+            if (isVersionBeforeRange(override, versionCode)) {
+                return override.getMinVersionCode() - versionCode;
+            }
+
+            // Version is in range. Note that when two overrides have a zero version proximity
+            // they will be ordered arbitrarily.
+            return 0;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/compat/overrides/AppCompatOverridesService.java b/services/core/java/com/android/server/compat/overrides/AppCompatOverridesService.java
new file mode 100644
index 0000000..a81b18b
--- /dev/null
+++ b/services/core/java/com/android/server/compat/overrides/AppCompatOverridesService.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.compat.overrides;
+
+import static android.content.pm.PackageManager.MATCH_ANY_USER;
+import static android.provider.DeviceConfig.NAMESPACE_APP_COMPAT_OVERRIDES;
+
+import static com.android.server.compat.overrides.AppCompatOverridesParser.FLAG_OWNED_CHANGE_IDS;
+import static com.android.server.compat.overrides.AppCompatOverridesParser.FLAG_REMOVE_OVERRIDES;
+
+import static java.util.Collections.emptySet;
+
+import android.annotation.Nullable;
+import android.app.compat.PackageOverride;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.provider.DeviceConfig;
+import android.provider.DeviceConfig.Properties;
+import android.util.ArraySet;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.compat.CompatibilityOverrideConfig;
+import com.android.internal.compat.CompatibilityOverridesToRemoveConfig;
+import com.android.internal.compat.IPlatformCompat;
+import com.android.server.SystemService;
+import com.android.server.compat.overrides.AppCompatOverridesParser.PackageOverrides;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Service for applying per-app compat overrides delivered via Device Config.
+ *
+ * <p>The service listens both on changes to supported Device Config namespaces and on package
+ * added/changed/removed events, and applies overrides accordingly.
+ *
+ * @hide
+ */
+public final class AppCompatOverridesService {
+    private static final String TAG = "AppCompatOverridesService";
+
+    private static final List<String> SUPPORTED_NAMESPACES = Arrays.asList(
+            NAMESPACE_APP_COMPAT_OVERRIDES);
+
+    private final Context mContext;
+    private final PackageManager mPackageManager;
+    private final IPlatformCompat mPlatformCompat;
+    private final List<String> mSupportedNamespaces;
+    private final List<DeviceConfigListener> mDeviceConfigListeners;
+    private final AppCompatOverridesParser mOverridesParser;
+
+    private AppCompatOverridesService(Context context) {
+        this(context, IPlatformCompat.Stub.asInterface(
+                ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)), SUPPORTED_NAMESPACES);
+    }
+
+    @VisibleForTesting
+    AppCompatOverridesService(Context context, IPlatformCompat platformCompat,
+            List<String> supportedNamespaces) {
+        mContext = context;
+        mPackageManager = mContext.getPackageManager();
+        mPlatformCompat = platformCompat;
+        mSupportedNamespaces = supportedNamespaces;
+        mDeviceConfigListeners = new ArrayList<>();
+        mOverridesParser = new AppCompatOverridesParser(mPackageManager);
+    }
+
+    @Override
+    public void finalize() {
+        unregisterDeviceConfigListeners();
+    }
+
+    @VisibleForTesting
+    void registerDeviceConfigListeners() {
+        for (String namespace : mSupportedNamespaces) {
+            DeviceConfigListener listener = new DeviceConfigListener(namespace);
+            DeviceConfig.addOnPropertiesChangedListener(namespace, mContext.getMainExecutor(),
+                    listener);
+            mDeviceConfigListeners.add(listener);
+        }
+    }
+
+    private void unregisterDeviceConfigListeners() {
+        for (DeviceConfigListener listener : mDeviceConfigListeners) {
+            DeviceConfig.removeOnPropertiesChangedListener(listener);
+        }
+    }
+
+    /**
+     * Same as {@link #applyOverrides(Properties, Map)} except all properties of the given {@code
+     * namespace} are fetched via {@link DeviceConfig#getProperties}.
+     */
+    private void applyAllOverrides(String namespace,
+            Map<String, Set<Long>> packageToChangeIdsToSkip) {
+        applyOverrides(DeviceConfig.getProperties(namespace), packageToChangeIdsToSkip);
+    }
+
+    /**
+     * Iterates all package override flags in the given {@code properties}, and for each flag whose
+     * package is installed on the device, parses its value and applies the overrides in it with
+     * respect to the package's current installed version.
+     */
+    private void applyOverrides(Properties properties,
+            Map<String, Set<Long>> packageToChangeIdsToSkip) {
+        Set<String> packageNames = new ArraySet<>(properties.getKeyset());
+        packageNames.remove(FLAG_OWNED_CHANGE_IDS);
+        packageNames.remove(FLAG_REMOVE_OVERRIDES);
+        for (String packageName : packageNames) {
+            Long versionCode = getVersionCodeOrNull(packageName);
+            if (versionCode == null) {
+                // Package isn't installed yet.
+                continue;
+            }
+
+            applyPackageOverrides(properties.getString(packageName, /* defaultValue= */ ""),
+                    packageName, versionCode,
+                    packageToChangeIdsToSkip.getOrDefault(packageName, emptySet()));
+        }
+    }
+
+    /**
+     * Calls {@link AppCompatOverridesParser#parsePackageOverrides} on the given arguments, adds the
+     * resulting {@link PackageOverrides#overridesToAdd} via {@link
+     * IPlatformCompat#putOverridesOnReleaseBuilds}, and removes the resulting {@link
+     * PackageOverrides#overridesToRemove} via {@link
+     * IPlatformCompat#removeOverridesOnReleaseBuilds}.
+     */
+    private void applyPackageOverrides(String configStr, String packageName,
+            long versionCode, Set<Long> changeIdsToSkip) {
+        PackageOverrides packageOverrides = AppCompatOverridesParser.parsePackageOverrides(
+                configStr, versionCode, changeIdsToSkip);
+        putPackageOverrides(packageName, packageOverrides.overridesToAdd);
+        removePackageOverrides(packageName, packageOverrides.overridesToRemove);
+    }
+
+    /**
+     * Calls {@link IPlatformCompat#removeOverridesOnReleaseBuilds} on each package name and
+     * respective change IDs in {@code overridesToRemove}.
+     */
+    private void removeOverrides(Map<String, Set<Long>> overridesToRemove) {
+        for (Map.Entry<String, Set<Long>> packageNameAndOverrides : overridesToRemove.entrySet()) {
+            removePackageOverrides(packageNameAndOverrides.getKey(),
+                    packageNameAndOverrides.getValue());
+        }
+    }
+
+    /**
+     * Fetches the value of {@link AppCompatOverridesParser#FLAG_REMOVE_OVERRIDES} for the given
+     * {@code namespace} and parses it into a map from package name to a set of change IDs to
+     * remove for that package.
+     */
+    private Map<String, Set<Long>> getOverridesToRemove(String namespace) {
+        return mOverridesParser.parseRemoveOverrides(
+                DeviceConfig.getString(namespace, FLAG_REMOVE_OVERRIDES, /* defaultValue= */ ""),
+                getOwnedChangeIds(namespace));
+    }
+
+    /**
+     * Fetches the value of {@link AppCompatOverridesParser#FLAG_OWNED_CHANGE_IDS} for the given
+     * {@code namespace} and parses it into a set of change IDs.
+     */
+    private static Set<Long> getOwnedChangeIds(String namespace) {
+        return AppCompatOverridesParser.parseOwnedChangeIds(
+                DeviceConfig.getString(namespace, FLAG_OWNED_CHANGE_IDS, /* defaultValue= */ ""));
+    }
+
+    private void putPackageOverrides(String packageName,
+            Map<Long, PackageOverride> overridesToAdd) {
+        if (overridesToAdd.isEmpty()) {
+            return;
+        }
+        CompatibilityOverrideConfig config = new CompatibilityOverrideConfig(overridesToAdd);
+        try {
+            mPlatformCompat.putOverridesOnReleaseBuilds(config, packageName);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Failed to call IPlatformCompat#putOverridesOnReleaseBuilds", e);
+        }
+    }
+
+    private void removePackageOverrides(String packageName, Set<Long> overridesToRemove) {
+        if (overridesToRemove.isEmpty()) {
+            return;
+        }
+        CompatibilityOverridesToRemoveConfig config = new CompatibilityOverridesToRemoveConfig(
+                overridesToRemove);
+        try {
+            mPlatformCompat.removeOverridesOnReleaseBuilds(config, packageName);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Failed to call IPlatformCompat#removeOverridesOnReleaseBuilds", e);
+        }
+    }
+
+    @Nullable
+    private Long getVersionCodeOrNull(String packageName) {
+        try {
+            ApplicationInfo applicationInfo = mPackageManager.getApplicationInfo(packageName,
+                    MATCH_ANY_USER);
+            return applicationInfo.longVersionCode;
+        } catch (PackageManager.NameNotFoundException e) {
+            // Package isn't installed yet.
+            return null;
+        }
+    }
+
+    /**
+     * SystemService lifecycle for AppCompatOverridesService.
+     *
+     * @hide
+     */
+    public static final class Lifecycle extends SystemService {
+        private AppCompatOverridesService mService;
+
+        public Lifecycle(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void onStart() {
+            mService = new AppCompatOverridesService(getContext());
+            mService.registerDeviceConfigListeners();
+        }
+    }
+
+    /**
+     * A {@link DeviceConfig.OnPropertiesChangedListener} that listens on changes to a given
+     * namespace and adds/removes overrides according to the changed flags.
+     */
+    private final class DeviceConfigListener implements DeviceConfig.OnPropertiesChangedListener {
+        private final String mNamespace;
+
+        private DeviceConfigListener(String namespace) {
+            mNamespace = namespace;
+        }
+
+        @Override
+        public void onPropertiesChanged(Properties properties) {
+            boolean removeOverridesFlagChanged = properties.getKeyset().contains(
+                    FLAG_REMOVE_OVERRIDES);
+            boolean ownedChangedIdsFlagChanged = properties.getKeyset().contains(
+                    FLAG_OWNED_CHANGE_IDS);
+
+            Map<String, Set<Long>> overridesToRemove = getOverridesToRemove(mNamespace);
+            if (removeOverridesFlagChanged || ownedChangedIdsFlagChanged) {
+                // In both cases it's possible that overrides that weren't removed before should
+                // now be removed.
+                removeOverrides(overridesToRemove);
+            }
+
+            if (removeOverridesFlagChanged) {
+                // We need to re-apply all overrides in the namespace since the remove overrides
+                // flag might have blocked some of them from being applied before.
+                applyAllOverrides(mNamespace, overridesToRemove);
+            } else {
+                applyOverrides(properties, overridesToRemove);
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/compat/overrides/TEST_MAPPING b/services/core/java/com/android/server/compat/overrides/TEST_MAPPING
new file mode 100644
index 0000000..4b8f08e
--- /dev/null
+++ b/services/core/java/com/android/server/compat/overrides/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+  "presubmit": [
+    {
+      "name": "FrameworksMockingServicesTests",
+      "options": [
+        {
+          "include-filter": "com.android.server.compat.overrides"
+        }
+      ]
+    }
+  ]
+}
diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java
index 35f2957..9f806af 100644
--- a/services/core/java/com/android/server/display/DisplayDevice.java
+++ b/services/core/java/com/android/server/display/DisplayDevice.java
@@ -43,6 +43,7 @@
     // The display device does not manage these properties itself, they are set by
     // the display manager service.  The display device shouldn't really be looking at these.
     private int mCurrentLayerStack = -1;
+    private int mCurrentFlags = 0;
     private int mCurrentOrientation = -1;
     private Rect mCurrentLayerStackRect;
     private Rect mCurrentDisplayRect;
@@ -212,6 +213,19 @@
     }
 
     /**
+     * Sets the display flags while in a transaction.
+     *
+     * Valid display flags:
+     *  {@link SurfaceControl#DISPLAY_RECEIVES_INPUT}
+     */
+    public final void setDisplayFlagsLocked(SurfaceControl.Transaction t, int flags) {
+        if (mCurrentFlags != flags) {
+            mCurrentFlags = flags;
+            t.setDisplayFlags(mDisplayToken, flags);
+        }
+    }
+
+    /**
      * Sets the display projection while in a transaction.
      *
      * @param orientation defines the display's orientation
@@ -298,6 +312,7 @@
         pw.println("mUniqueId=" + mUniqueId);
         pw.println("mDisplayToken=" + mDisplayToken);
         pw.println("mCurrentLayerStack=" + mCurrentLayerStack);
+        pw.println("mCurrentFlags=" + mCurrentFlags);
         pw.println("mCurrentOrientation=" + mCurrentOrientation);
         pw.println("mCurrentLayerStackRect=" + mCurrentLayerStackRect);
         pw.println("mCurrentDisplayRect=" + mCurrentDisplayRect);
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 9acb4c8..5186744 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -16,6 +16,8 @@
 
 package com.android.server.display;
 
+import static com.android.server.display.DisplayDeviceInfo.TOUCH_NONE;
+
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -512,6 +514,11 @@
             boolean isBlanked) {
         // Set the layer stack.
         device.setLayerStackLocked(t, isBlanked ? BLANK_LAYER_STACK : mLayerStack);
+        // Also inform whether the device is the same one sent to inputflinger for its layerstack.
+        // TODO(b/188914255): Remove once input can dispatch against device vs layerstack.
+        device.setDisplayFlagsLocked(t,
+                device.getDisplayDeviceInfoLocked().touch != TOUCH_NONE
+                        ? SurfaceControl.DISPLAY_RECEIVES_INPUT : 0);
 
         // Set the color mode and allowed display mode.
         if (device == mPrimaryDisplayDevice) {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index fd0f1c3..7955ecac 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2528,9 +2528,8 @@
                 }
                 if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
                 // Dispatch display id for InputMethodService to update context display.
-                executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOOO(
-                        MSG_INITIALIZE_IME, mCurTokenDisplayId, mCurMethod, mCurToken,
-                        mMethodMap.get(mCurMethodId).getConfigChanges()));
+                executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO(MSG_INITIALIZE_IME,
+                        mMethodMap.get(mCurMethodId).getConfigChanges(), mCurMethod, mCurToken));
                 scheduleNotifyImeUidToAudioService(mCurMethodUid);
                 if (mCurClient != null) {
                     clearClientSessionLocked(mCurClient);
@@ -4292,12 +4291,11 @@
                 try {
                     if (DEBUG) {
                         Slog.v(TAG, "Sending attach of token: " + args.arg2 + " for display: "
-                                + msg.arg1);
+                                + mCurTokenDisplayId);
                     }
                     final IBinder token = (IBinder) args.arg2;
-                    ((IInputMethod) args.arg1).initializeInternal(token, msg.arg1,
-                            new InputMethodPrivilegedOperationsImpl(this, token),
-                            (int) args.arg3);
+                    ((IInputMethod) args.arg1).initializeInternal(token,
+                            new InputMethodPrivilegedOperationsImpl(this, token), msg.arg1);
                 } catch (RemoteException e) {
                 }
                 args.recycle();
diff --git a/services/core/java/com/android/server/notification/VibratorHelper.java b/services/core/java/com/android/server/notification/VibratorHelper.java
index f47aa48..0a69aec 100644
--- a/services/core/java/com/android/server/notification/VibratorHelper.java
+++ b/services/core/java/com/android/server/notification/VibratorHelper.java
@@ -39,6 +39,9 @@
 
     private static final long[] DEFAULT_VIBRATE_PATTERN = {0, 250, 250, 250};
     private static final int VIBRATE_PATTERN_MAXLEN = 8 * 2 + 1; // up to eight bumps
+    private static final int CHIRP_LEVEL_DURATION_MILLIS = 100;
+    private static final int DEFAULT_CHIRP_RAMP_DURATION_MILLIS = 100;
+    private static final int FALLBACK_CHIRP_RAMP_DURATION_MILLIS = 50;
 
     private final Vibrator mVibrator;
     private final long[] mDefaultPattern;
@@ -102,6 +105,9 @@
      * @param insistent {@code true} if the vibration should loop until it is cancelled.
      */
     public VibrationEffect createFallbackVibration(boolean insistent) {
+        if (mVibrator.hasFrequencyControl()) {
+            return createChirpVibration(FALLBACK_CHIRP_RAMP_DURATION_MILLIS, insistent);
+        }
         return createWaveformVibration(mFallbackPattern, insistent);
     }
 
@@ -111,9 +117,32 @@
      * @param insistent {@code true} if the vibration should loop until it is cancelled.
      */
     public VibrationEffect createDefaultVibration(boolean insistent) {
+        if (mVibrator.hasFrequencyControl()) {
+            return createChirpVibration(DEFAULT_CHIRP_RAMP_DURATION_MILLIS, insistent);
+        }
         return createWaveformVibration(mDefaultPattern, insistent);
     }
 
+    private static VibrationEffect createChirpVibration(int rampDuration, boolean insistent) {
+        VibrationEffect.WaveformBuilder waveformBuilder = VibrationEffect.startWaveform()
+                .addStep(/* amplitude= */ 0, /* frequency= */ -0.85f, /* duration= */ 0)
+                .addRamp(/* amplitude= */ 1, /* frequency= */ -0.25f, rampDuration)
+                .addStep(/* amplitude= */ 1, /* frequency= */ -0.25f, CHIRP_LEVEL_DURATION_MILLIS)
+                .addRamp(/* amplitude= */ 0, /* frequency= */ -0.85f, rampDuration);
+
+        if (insistent) {
+            return waveformBuilder
+                    .addStep(/* amplitude= */ 0, CHIRP_LEVEL_DURATION_MILLIS)
+                    .build(/* repeat= */ 0);
+        }
+
+        VibrationEffect singleBeat = waveformBuilder.build();
+        return VibrationEffect.startComposition()
+                .addEffect(singleBeat)
+                .addEffect(singleBeat, /* delay= */ CHIRP_LEVEL_DURATION_MILLIS)
+                .compose();
+    }
+
     private static long[] getLongArray(Resources resources, int resId, int maxLength, long[] def) {
         int[] ar = resources.getIntArray(resId);
         if (ar == null) {
diff --git a/services/core/java/com/android/server/policy/DisplayFoldController.java b/services/core/java/com/android/server/policy/DisplayFoldController.java
index 3c9b106..04b5005 100644
--- a/services/core/java/com/android/server/policy/DisplayFoldController.java
+++ b/services/core/java/com/android/server/policy/DisplayFoldController.java
@@ -16,10 +16,8 @@
 
 package com.android.server.policy;
 
-import android.annotation.Nullable;
 import android.content.Context;
 import android.graphics.Rect;
-import android.hardware.ICameraService;
 import android.hardware.devicestate.DeviceStateManager;
 import android.hardware.devicestate.DeviceStateManager.FoldStateListener;
 import android.hardware.display.DisplayManagerInternal;
@@ -27,13 +25,11 @@
 import android.os.HandlerExecutor;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
-import android.util.Slog;
 import android.view.DisplayInfo;
 import android.view.IDisplayFoldListener;
 
 import com.android.server.DisplayThread;
 import com.android.server.LocalServices;
-import com.android.server.camera.CameraServiceProxy;
 import com.android.server.wm.WindowManagerInternal;
 
 /**
@@ -41,13 +37,8 @@
  * TODO(b/126160895): Move DisplayFoldController from PhoneWindowManager to DisplayPolicy.
  */
 class DisplayFoldController {
-    private static final String TAG = "DisplayFoldController";
-
     private final WindowManagerInternal mWindowManagerInternal;
     private final DisplayManagerInternal mDisplayManagerInternal;
-    // Camera service proxy can be disabled through a config.
-    @Nullable
-    private final CameraServiceProxy mCameraServiceProxy;
     private final int mDisplayId;
     private final Handler mHandler;
 
@@ -64,12 +55,10 @@
 
     DisplayFoldController(
             Context context, WindowManagerInternal windowManagerInternal,
-            DisplayManagerInternal displayManagerInternal,
-            @Nullable CameraServiceProxy cameraServiceProxy, int displayId, Rect foldedArea,
+            DisplayManagerInternal displayManagerInternal, int displayId, Rect foldedArea,
             Handler handler) {
         mWindowManagerInternal = windowManagerInternal;
         mDisplayManagerInternal = displayManagerInternal;
-        mCameraServiceProxy = cameraServiceProxy;
         mDisplayId = displayId;
         mFoldedArea = new Rect(foldedArea);
         mHandler = handler;
@@ -124,16 +113,6 @@
             }
         }
 
-        if (mCameraServiceProxy != null) {
-            if (folded) {
-                mCameraServiceProxy.setDeviceStateFlags(ICameraService.DEVICE_STATE_FOLDED);
-            } else {
-                mCameraServiceProxy.clearDeviceStateFlags(ICameraService.DEVICE_STATE_FOLDED);
-            }
-        } else {
-            Slog.w(TAG, "Camera service unavailable to toggle folded state.");
-        }
-
         mDurationLogger.setDeviceFolded(folded);
         mDurationLogger.logFocusedAppWithFoldState(folded, mFocusedApp);
         mFolded = folded;
@@ -188,8 +167,6 @@
                 LocalServices.getService(WindowManagerInternal.class);
         final DisplayManagerInternal displayService =
                 LocalServices.getService(DisplayManagerInternal.class);
-        final CameraServiceProxy cameraServiceProxy =
-                LocalServices.getService(CameraServiceProxy.class);
 
         final String configFoldedArea = context.getResources().getString(
                 com.android.internal.R.string.config_foldedArea);
@@ -201,6 +178,6 @@
         }
 
         return new DisplayFoldController(context, windowManagerService, displayService,
-                cameraServiceProxy, displayId, foldedArea, DisplayThread.getHandler());
+                displayId, foldedArea, DisplayThread.getHandler());
     }
 }
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index a436e6b..d95e826 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -22,6 +22,7 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
+import android.view.InsetsState;
 import android.view.InsetsState.InternalInsetsType;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
@@ -132,7 +133,7 @@
     /** @see com.android.internal.statusbar.IStatusBar#onSystemBarAttributesChanged */
     void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
             AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-            @Behavior int behavior, boolean isFullscreen);
+            @Behavior int behavior, InsetsState requestedState, String packageName);
 
     /** @see com.android.internal.statusbar.IStatusBar#showTransient */
     void showTransient(int displayId, @InternalInsetsType int[] types);
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 3a7e13b..47fdc4e 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -59,6 +59,7 @@
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.view.InsetsState;
 import android.view.InsetsState.InternalInsetsType;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
@@ -526,13 +527,13 @@
         @Override
         public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
                 AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-                @Behavior int behavior, boolean isFullscreen) {
+                @Behavior int behavior, InsetsState requestedState, String packageName) {
             getUiState(displayId).setBarAttributes(appearance, appearanceRegions,
-                    navbarColorManagedByIme, behavior, isFullscreen);
+                    navbarColorManagedByIme, behavior, requestedState, packageName);
             if (mBar != null) {
                 try {
                     mBar.onSystemBarAttributesChanged(displayId, appearance, appearanceRegions,
-                            navbarColorManagedByIme, behavior, isFullscreen);
+                            navbarColorManagedByIme, behavior, requestedState, packageName);
                 } catch (RemoteException ex) { }
             }
         }
@@ -1103,13 +1104,14 @@
         return state;
     }
 
-    private class UiState {
+    private static class UiState {
         private @Appearance int mAppearance = 0;
         private AppearanceRegion[] mAppearanceRegions = new AppearanceRegion[0];
-        private ArraySet<Integer> mTransientBarTypes = new ArraySet<>();
+        private final ArraySet<Integer> mTransientBarTypes = new ArraySet<>();
         private boolean mNavbarColorManagedByIme = false;
         private @Behavior int mBehavior;
-        private boolean mFullscreen = false;
+        private InsetsState mRequestedState = new InsetsState();
+        private String mPackageName = "none";
         private int mDisabled1 = 0;
         private int mDisabled2 = 0;
         private int mImeWindowVis = 0;
@@ -1119,12 +1121,13 @@
 
         private void setBarAttributes(@Appearance int appearance,
                 AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-                @Behavior int behavior, boolean isFullscreen) {
+                @Behavior int behavior, InsetsState requestedState, String packageName) {
             mAppearance = appearance;
             mAppearanceRegions = appearanceRegions;
             mNavbarColorManagedByIme = navbarColorManagedByIme;
             mBehavior = behavior;
-            mFullscreen = isFullscreen;
+            mRequestedState = requestedState;
+            mPackageName = packageName;
         }
 
         private void showTransient(@InternalInsetsType int[] types) {
@@ -1244,8 +1247,8 @@
                     state.mAppearance, state.mAppearanceRegions, state.mImeWindowVis,
                     state.mImeBackDisposition, state.mShowImeSwitcher,
                     gatherDisableActionsLocked(mCurrentUserId, 2), state.mImeToken,
-                    state.mNavbarColorManagedByIme, state.mBehavior, state.mFullscreen,
-                    transientBarTypes);
+                    state.mNavbarColorManagedByIme, state.mBehavior, state.mRequestedState,
+                    state.mPackageName, transientBarTypes);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index a24319f..54d97ee 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -16,6 +16,8 @@
 
 package com.android.server.wm;
 
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_MAGNIFICATION_CALLBACK;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK;
 import static android.os.Build.IS_USER;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
@@ -34,6 +36,7 @@
 import static com.android.server.accessibility.AccessibilityTraceProto.CALLING_PKG;
 import static com.android.server.accessibility.AccessibilityTraceProto.CALLING_STACKS;
 import static com.android.server.accessibility.AccessibilityTraceProto.ELAPSED_REALTIME_NANOS;
+import static com.android.server.accessibility.AccessibilityTraceProto.LOGGING_TYPE;
 import static com.android.server.accessibility.AccessibilityTraceProto.PROCESS_NAME;
 import static com.android.server.accessibility.AccessibilityTraceProto.THREAD_ID_NAME;
 import static com.android.server.accessibility.AccessibilityTraceProto.WHERE;
@@ -42,6 +45,7 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.utils.RegionUtils.forEachRect;
 
+import android.accessibilityservice.AccessibilityTrace;
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
@@ -90,6 +94,7 @@
 import com.android.internal.R;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.TraceBuffer;
+import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.LocalServices;
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.wm.WindowManagerInternal.AccessibilityControllerInternal;
@@ -99,8 +104,6 @@
 import java.io.File;
 import java.io.IOException;
 import java.io.PrintWriter;
-import java.nio.file.Files;
-import java.nio.file.Paths;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -116,21 +119,16 @@
     private static final String TAG = AccessibilityController.class.getSimpleName();
 
     private static final Object STATIC_LOCK = new Object();
-    static AccessibilityControllerInternal
+    static AccessibilityControllerInternalImpl
             getAccessibilityControllerInternal(WindowManagerService service) {
         return AccessibilityControllerInternalImpl.getInstance(service);
     }
 
-    private final AccessibilityTracing mAccessibilityTracing;
+    private final AccessibilityControllerInternalImpl mAccessibilityTracing;
     private final WindowManagerService mService;
     private static final Rect EMPTY_RECT = new Rect();
     private static final float[] sTempFloats = new float[9];
 
-    AccessibilityController(WindowManagerService service) {
-        mService = service;
-        mAccessibilityTracing = AccessibilityTracing.getInstance(service);
-    }
-
     private SparseArray<DisplayMagnifier> mDisplayMagnifiers = new SparseArray<>();
     private SparseArray<WindowsForAccessibilityObserver> mWindowsForAccessibilityObserver =
             new SparseArray<>();
@@ -138,10 +136,17 @@
     // Set to true if initializing window population complete.
     private boolean mAllObserversInitialized = true;
 
+    AccessibilityController(WindowManagerService service) {
+        mService = service;
+        mAccessibilityTracing =
+                AccessibilityController.getAccessibilityControllerInternal(service);
+    }
+
     boolean setMagnificationCallbacks(int displayId, MagnificationCallbacks callbacks) {
-        if (mAccessibilityTracing.isEnabled()) {
-            mAccessibilityTracing.logState(
+        if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+            mAccessibilityTracing.logTrace(
                     TAG + ".setMagnificationCallbacks",
+                    FLAGS_MAGNIFICATION_CALLBACK,
                     "displayId=" + displayId + "; callbacks={" + callbacks + "}");
         }
         boolean result = false;
@@ -172,25 +177,31 @@
 
     /**
      * Sets a callback for observing which windows are touchable for the purposes
-     * of accessibility on specified display.
+     * of accessibility on specified display. When a display is reparented and becomes
+     * an embedded one, the {@link WindowsForAccessibilityCallback#onDisplayReparented(int)}
+     * will notify the accessibility framework to remove the un-used window observer of
+     * this embedded display.
      *
      * @param displayId The logical display id.
      * @param callback The callback.
-     * @return {@code false} if display id is not valid or an embedded display.
+     * @return {@code false} if display id is not valid or an embedded display when the callback
+     * isn't null.
      */
     boolean setWindowsForAccessibilityCallback(int displayId,
             WindowsForAccessibilityCallback callback) {
-        if (mAccessibilityTracing.isEnabled()) {
-            mAccessibilityTracing.logState(
+        if (mAccessibilityTracing.isTracingEnabled(FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) {
+            mAccessibilityTracing.logTrace(
                     TAG + ".setWindowsForAccessibilityCallback",
+                    FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK,
                     "displayId=" + displayId + "; callback={" + callback + "}");
         }
-        final DisplayContent dc = mService.mRoot.getDisplayContentOrCreate(displayId);
-        if (dc == null) {
-            return false;
-        }
 
         if (callback != null) {
+            final DisplayContent dc = mService.mRoot.getDisplayContentOrCreate(displayId);
+            if (dc == null) {
+                return false;
+            }
+
             WindowsForAccessibilityObserver observer =
                     mWindowsForAccessibilityObserver.get(displayId);
             if (isEmbeddedDisplay(dc)) {
@@ -209,21 +220,13 @@
                 if (Build.IS_DEBUGGABLE) {
                     throw new IllegalStateException(errorMessage);
                 }
-                removeObserverOfEmbeddedDisplay(observer);
+                removeObserversForEmbeddedChildDisplays(observer);
                 mWindowsForAccessibilityObserver.remove(displayId);
             }
             observer = new WindowsForAccessibilityObserver(mService, displayId, callback);
             mWindowsForAccessibilityObserver.put(displayId, observer);
             mAllObserversInitialized &= observer.mInitialized;
         } else {
-            if (isEmbeddedDisplay(dc)) {
-                // If this display is an embedded one, its window observer should be removed along
-                // with the window observer of its parent display removed because the window
-                // observer of the embedded display and its parent display is the same, and would
-                // be removed together when stopping the window tracking of its parent display. So
-                // here don't need to do removing window observer of the embedded display again.
-                return true;
-            }
             final WindowsForAccessibilityObserver windowsForA11yObserver =
                     mWindowsForAccessibilityObserver.get(displayId);
             if (windowsForA11yObserver == null) {
@@ -234,16 +237,17 @@
                     throw new IllegalStateException(errorMessage);
                 }
             }
-            removeObserverOfEmbeddedDisplay(windowsForA11yObserver);
+            removeObserversForEmbeddedChildDisplays(windowsForA11yObserver);
             mWindowsForAccessibilityObserver.remove(displayId);
         }
         return true;
     }
 
     void performComputeChangedWindowsNot(int displayId, boolean forceSend) {
-        if (mAccessibilityTracing.isEnabled()) {
-            mAccessibilityTracing.logState(
+        if (mAccessibilityTracing.isTracingEnabled(FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) {
+            mAccessibilityTracing.logTrace(
                     TAG + ".performComputeChangedWindowsNot",
+                    FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK,
                     "displayId=" + displayId + "; forceSend=" + forceSend);
         }
         WindowsForAccessibilityObserver observer = null;
@@ -260,8 +264,10 @@
     }
 
     void setMagnificationSpec(int displayId, MagnificationSpec spec) {
-        if (mAccessibilityTracing.isEnabled()) {
-            mAccessibilityTracing.logState(TAG + ".setMagnificationSpec",
+        if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK
+                | FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) {
+            mAccessibilityTracing.logTrace(TAG + ".setMagnificationSpec",
+                    FLAGS_MAGNIFICATION_CALLBACK | FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK,
                     "displayId=" + displayId + "; spec={" + spec + "}");
         }
         final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
@@ -276,8 +282,9 @@
     }
 
     void getMagnificationRegion(int displayId, Region outMagnificationRegion) {
-        if (mAccessibilityTracing.isEnabled()) {
-            mAccessibilityTracing.logState(TAG + ".getMagnificationRegion",
+        if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+            mAccessibilityTracing.logTrace(TAG + ".getMagnificationRegion",
+                    FLAGS_MAGNIFICATION_CALLBACK,
                     "displayId=" + displayId + "; outMagnificationRegion={" + outMagnificationRegion
                             + "}");
         }
@@ -288,9 +295,10 @@
     }
 
     void onRectangleOnScreenRequested(int displayId, Rect rectangle) {
-        if (mAccessibilityTracing.isEnabled()) {
-            mAccessibilityTracing.logState(
+        if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+            mAccessibilityTracing.logTrace(
                     TAG + ".onRectangleOnScreenRequested",
+                    FLAGS_MAGNIFICATION_CALLBACK,
                     "displayId=" + displayId + "; rectangle={" + rectangle + "}");
         }
         final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
@@ -301,9 +309,11 @@
     }
 
     void onWindowLayersChanged(int displayId) {
-        if (mAccessibilityTracing.isEnabled()) {
-            mAccessibilityTracing.logState(
-                    TAG + ".onWindowLayersChanged", "displayId=" + displayId);
+        if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK
+                | FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) {
+            mAccessibilityTracing.logTrace(TAG + ".onWindowLayersChanged",
+                    FLAGS_MAGNIFICATION_CALLBACK | FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK,
+                    "displayId=" + displayId);
         }
         final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
         if (displayMagnifier != null) {
@@ -316,15 +326,18 @@
         }
     }
 
-    void onRotationChanged(DisplayContent displayContent) {
-        if (mAccessibilityTracing.isEnabled()) {
-            mAccessibilityTracing.logState(TAG + ".onRotationChanged",
+    void onDisplaySizeChanged(DisplayContent displayContent) {
+
+        if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK
+                | FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) {
+            mAccessibilityTracing.logTrace(TAG + ".onRotationChanged",
+                    FLAGS_MAGNIFICATION_CALLBACK | FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK,
                     "displayContent={" + displayContent + "}");
         }
         final int displayId = displayContent.getDisplayId();
         final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
         if (displayMagnifier != null) {
-            displayMagnifier.onRotationChanged(displayContent);
+            displayMagnifier.onDisplaySizeChanged(displayContent);
         }
         final WindowsForAccessibilityObserver windowsForA11yObserver =
                 mWindowsForAccessibilityObserver.get(displayId);
@@ -334,8 +347,9 @@
     }
 
     void onAppWindowTransition(int displayId, int transition) {
-        if (mAccessibilityTracing.isEnabled()) {
-            mAccessibilityTracing.logState(TAG + ".onAppWindowTransition",
+        if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+            mAccessibilityTracing.logTrace(TAG + ".onAppWindowTransition",
+                    FLAGS_MAGNIFICATION_CALLBACK,
                     "displayId=" + displayId + "; transition=" + transition);
         }
         final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
@@ -346,8 +360,10 @@
     }
 
     void onWindowTransition(WindowState windowState, int transition) {
-        if (mAccessibilityTracing.isEnabled()) {
-            mAccessibilityTracing.logState(TAG + ".onWindowTransition",
+        if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK
+                | FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) {
+            mAccessibilityTracing.logTrace(TAG + ".onWindowTransition",
+                    FLAGS_MAGNIFICATION_CALLBACK | FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK,
                     "windowState={" + windowState + "}; transition=" + transition);
         }
         final int displayId = windowState.getDisplayId();
@@ -364,9 +380,9 @@
 
     void onWindowFocusChangedNot(int displayId) {
         // Not relevant for the display magnifier.
-        if (mAccessibilityTracing.isEnabled()) {
-            mAccessibilityTracing.logState(
-                    TAG + ".onWindowFocusChangedNot", "displayId=" + displayId);
+        if (mAccessibilityTracing.isTracingEnabled(FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) {
+            mAccessibilityTracing.logTrace(TAG + ".onWindowFocusChangedNot",
+                    FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK, "displayId=" + displayId);
         }
         WindowsForAccessibilityObserver observer = null;
         synchronized (mService.mGlobalLock) {
@@ -426,12 +442,10 @@
     }
 
     void onSomeWindowResizedOrMovedWithCallingUid(int callingUid, int... displayIds) {
-        if (mAccessibilityTracing.isEnabled()) {
-            mAccessibilityTracing.logState(
-                    TAG + ".onSomeWindowResizedOrMoved",
-                    "displayIds={" + displayIds.toString() + "}",
-                    "".getBytes(),
-                    callingUid);
+        if (mAccessibilityTracing.isTracingEnabled(FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) {
+            mAccessibilityTracing.logTrace(TAG + ".onSomeWindowResizedOrMoved",
+                    FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK,
+                    "displayIds={" + displayIds.toString() + "}", "".getBytes(), callingUid);
         }
         // Not relevant for the display magnifier.
         for (int i = 0; i < displayIds.length; i++) {
@@ -444,9 +458,10 @@
     }
 
     void drawMagnifiedRegionBorderIfNeeded(int displayId, SurfaceControl.Transaction t) {
-        if (mAccessibilityTracing.isEnabled()) {
-            mAccessibilityTracing.logState(
+        if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+            mAccessibilityTracing.logTrace(
                     TAG + ".drawMagnifiedRegionBorderIfNeeded",
+                    FLAGS_MAGNIFICATION_CALLBACK,
                     "displayId=" + displayId + "; transaction={" + t + "}");
         }
         final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
@@ -457,8 +472,9 @@
     }
 
     MagnificationSpec getMagnificationSpecForWindow(WindowState windowState) {
-        if (mAccessibilityTracing.isEnabled()) {
-            mAccessibilityTracing.logState(TAG + ".getMagnificationSpecForWindow",
+        if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+            mAccessibilityTracing.logTrace(TAG + ".getMagnificationSpecForWindow",
+                    FLAGS_MAGNIFICATION_CALLBACK,
                     "windowState={" + windowState + "}");
         }
         final int displayId = windowState.getDisplayId();
@@ -470,17 +486,19 @@
     }
 
     boolean hasCallbacks() {
-        if (mAccessibilityTracing.isEnabled()) {
-            mAccessibilityTracing.logState(TAG + ".hasCallbacks");
+        if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK
+                | FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) {
+            mAccessibilityTracing.logTrace(TAG + ".hasCallbacks",
+                    FLAGS_MAGNIFICATION_CALLBACK | FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK);
         }
         return (mDisplayMagnifiers.size() > 0
                 || mWindowsForAccessibilityObserver.size() > 0);
     }
 
     void setForceShowMagnifiableBounds(int displayId, boolean show) {
-        if (mAccessibilityTracing.isEnabled()) {
-            mAccessibilityTracing.logState(TAG + ".setForceShowMagnifiableBounds",
-                    "displayId=" + displayId + "; show=" + show);
+        if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+            mAccessibilityTracing.logTrace(TAG + ".setForceShowMagnifiableBounds",
+                    FLAGS_MAGNIFICATION_CALLBACK, "displayId=" + displayId + "; show=" + show);
         }
         final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
         if (displayMagnifier != null) {
@@ -497,39 +515,50 @@
 
     void handleWindowObserverOfEmbeddedDisplay(
             int embeddedDisplayId, WindowState parentWindow, int callingUid) {
-        if (mAccessibilityTracing.isEnabled()) {
-            mAccessibilityTracing.logState(TAG + ".handleWindowObserverOfEmbeddedDisplay",
+        if (mAccessibilityTracing.isTracingEnabled(FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) {
+            mAccessibilityTracing.logTrace(TAG + ".handleWindowObserverOfEmbeddedDisplay",
+                    FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK,
                     "embeddedDisplayId=" + embeddedDisplayId + "; parentWindowState={"
-                    + parentWindow + "}",
-                    "".getBytes(),
-                    callingUid);
+                    + parentWindow + "}", "".getBytes(), callingUid);
         }
         if (embeddedDisplayId == Display.DEFAULT_DISPLAY || parentWindow == null) {
             return;
         }
-        // Finds the parent display of this embedded display
-        final int parentDisplayId;
-        WindowState candidate = parentWindow;
-        while (candidate != null) {
-            parentWindow = candidate;
-            candidate = parentWindow.getDisplayContent().getParentWindow();
+        mService.mH.sendMessage(PooledLambda.obtainMessage(
+                AccessibilityController::updateWindowObserverOfEmbeddedDisplay,
+                this, embeddedDisplayId, parentWindow));
+    }
+
+    private void updateWindowObserverOfEmbeddedDisplay(int embeddedDisplayId,
+            WindowState parentWindow) {
+        final WindowsForAccessibilityObserver windowsForA11yObserver;
+
+        synchronized (mService.mGlobalLock) {
+            // Finds the parent display of this embedded display
+            WindowState candidate = parentWindow;
+            while (candidate != null) {
+                parentWindow = candidate;
+                candidate = parentWindow.getDisplayContent().getParentWindow();
+            }
+            final int parentDisplayId = parentWindow.getDisplayId();
+            // Uses the observer of parent display
+            windowsForA11yObserver = mWindowsForAccessibilityObserver.get(parentDisplayId);
         }
-        parentDisplayId = parentWindow.getDisplayId();
-        // Uses the observer of parent display
-        final WindowsForAccessibilityObserver windowsForA11yObserver =
-                mWindowsForAccessibilityObserver.get(parentDisplayId);
 
         if (windowsForA11yObserver != null) {
+            windowsForA11yObserver.notifyDisplayReparented(embeddedDisplayId);
             windowsForA11yObserver.addEmbeddedDisplay(embeddedDisplayId);
-            // Replaces the observer of embedded display to the one of parent display
-            mWindowsForAccessibilityObserver.put(embeddedDisplayId, windowsForA11yObserver);
+            synchronized (mService.mGlobalLock) {
+                // Replaces the observer of embedded display to the one of parent display
+                mWindowsForAccessibilityObserver.put(embeddedDisplayId, windowsForA11yObserver);
+            }
         }
     }
 
     void onImeSurfaceShownChanged(WindowState windowState, boolean shown) {
-        if (mAccessibilityTracing.isEnabled()) {
-            mAccessibilityTracing.logState(TAG + ".onImeSurfaceShownChanged",
-                    "windowState=" + windowState + "; shown=" + shown);
+        if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+            mAccessibilityTracing.logTrace(TAG + ".onImeSurfaceShownChanged",
+                    FLAGS_MAGNIFICATION_CALLBACK, "windowState=" + windowState + ";shown=" + shown);
         }
         final int displayId = windowState.getDisplayId();
         final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
@@ -555,7 +584,7 @@
                 + "mWindowsForAccessibilityObserver=" + mWindowsForAccessibilityObserver);
     }
 
-    private void removeObserverOfEmbeddedDisplay(WindowsForAccessibilityObserver
+    private void removeObserversForEmbeddedChildDisplays(WindowsForAccessibilityObserver
             observerOfParentDisplay) {
         final IntArray embeddedDisplayIdList =
                 observerOfParentDisplay.getAndClearEmbeddedDisplayIdList();
@@ -580,7 +609,7 @@
         private static final String LOG_TAG = TAG_WITH_CLASS_NAME ? "DisplayMagnifier" : TAG_WM;
 
         private static final boolean DEBUG_WINDOW_TRANSITIONS = false;
-        private static final boolean DEBUG_ROTATION = false;
+        private static final boolean DEBUG_DISPLAY_SIZE = false;
         private static final boolean DEBUG_LAYERS = false;
         private static final boolean DEBUG_RECTANGLE_REQUESTED = false;
         private static final boolean DEBUG_VIEWPORT_WINDOW = false;
@@ -599,7 +628,7 @@
         private final Handler mHandler;
         private final DisplayContent mDisplayContent;
         private final Display mDisplay;
-        private final AccessibilityTracing mAccessibilityTracing;
+        private final AccessibilityControllerInternalImpl mAccessibilityTracing;
 
         private final MagnificationCallbacks mCallbacks;
 
@@ -618,11 +647,13 @@
             mDisplay = display;
             mHandler = new MyHandler(mService.mH.getLooper());
             mMagnifedViewport = new MagnifiedViewport();
-            mAccessibilityTracing = AccessibilityTracing.getInstance(mService);
+            mAccessibilityTracing =
+                    AccessibilityController.getAccessibilityControllerInternal(mService);
             mLongAnimationDuration = mDisplayContext.getResources().getInteger(
                     com.android.internal.R.integer.config_longAnimTime);
-            if (mAccessibilityTracing.isEnabled()) {
-                mAccessibilityTracing.logState(LOG_TAG + ".DisplayMagnifier.constructor",
+            if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+                mAccessibilityTracing.logTrace(LOG_TAG + ".DisplayMagnifier.constructor",
+                        FLAGS_MAGNIFICATION_CALLBACK,
                         "windowManagerService={" + windowManagerService + "}; displayContent={"
                                 + displayContent + "}; display={" + display + "}; callbacks={"
                                 + callbacks + "}");
@@ -630,9 +661,9 @@
         }
 
         void setMagnificationSpec(MagnificationSpec spec) {
-            if (mAccessibilityTracing.isEnabled()) {
-                mAccessibilityTracing.logState(
-                        LOG_TAG + ".setMagnificationSpec", "spec={" + spec + "}");
+            if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+                mAccessibilityTracing.logTrace(LOG_TAG + ".setMagnificationSpec",
+                        FLAGS_MAGNIFICATION_CALLBACK, "spec={" + spec + "}");
             }
             mMagnifedViewport.updateMagnificationSpec(spec);
             mMagnifedViewport.recomputeBounds();
@@ -642,25 +673,26 @@
         }
 
         void setForceShowMagnifiableBounds(boolean show) {
-            if (mAccessibilityTracing.isEnabled()) {
-                mAccessibilityTracing.logState(
-                        LOG_TAG + ".setForceShowMagnifiableBounds", "show=" + show);
+            if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+                mAccessibilityTracing.logTrace(LOG_TAG + ".setForceShowMagnifiableBounds",
+                        FLAGS_MAGNIFICATION_CALLBACK, "show=" + show);
             }
             mForceShowMagnifiableBounds = show;
             mMagnifedViewport.setMagnifiedRegionBorderShown(show, true);
         }
 
         boolean isForceShowingMagnifiableBounds() {
-            if (mAccessibilityTracing.isEnabled()) {
-                mAccessibilityTracing.logState(LOG_TAG + ".isForceShowingMagnifiableBounds");
+            if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+                mAccessibilityTracing.logTrace(LOG_TAG + ".isForceShowingMagnifiableBounds",
+                        FLAGS_MAGNIFICATION_CALLBACK);
             }
             return mForceShowMagnifiableBounds;
         }
 
         void onRectangleOnScreenRequested(Rect rectangle) {
-            if (mAccessibilityTracing.isEnabled()) {
-                mAccessibilityTracing.logState(
-                        LOG_TAG + ".onRectangleOnScreenRequested", "rectangle={" + rectangle + "}");
+            if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+                mAccessibilityTracing.logTrace(LOG_TAG + ".onRectangleOnScreenRequested",
+                        FLAGS_MAGNIFICATION_CALLBACK, "rectangle={" + rectangle + "}");
             }
             if (DEBUG_RECTANGLE_REQUESTED) {
                 Slog.i(LOG_TAG, "Rectangle on screen requested: " + rectangle);
@@ -683,8 +715,9 @@
         }
 
         void onWindowLayersChanged() {
-            if (mAccessibilityTracing.isEnabled()) {
-                mAccessibilityTracing.logState(LOG_TAG + ".onWindowLayersChanged");
+            if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+                mAccessibilityTracing.logTrace(
+                        LOG_TAG + ".onWindowLayersChanged", FLAGS_MAGNIFICATION_CALLBACK);
             }
             if (DEBUG_LAYERS) {
                 Slog.i(LOG_TAG, "Layers changed.");
@@ -693,23 +726,24 @@
             mService.scheduleAnimationLocked();
         }
 
-        void onRotationChanged(DisplayContent displayContent) {
-            if (mAccessibilityTracing.isEnabled()) {
-                mAccessibilityTracing.logState(
-                        LOG_TAG + ".onRotationChanged", "displayContent={" + displayContent + "}");
+        void onDisplaySizeChanged(DisplayContent displayContent) {
+            if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+                mAccessibilityTracing.logTrace(LOG_TAG + ".onDisplaySizeChanged",
+                        FLAGS_MAGNIFICATION_CALLBACK, "displayContent={" + displayContent + "}");
             }
-            if (DEBUG_ROTATION) {
+            if (DEBUG_DISPLAY_SIZE) {
                 final int rotation = displayContent.getRotation();
                 Slog.i(LOG_TAG, "Rotation: " + Surface.rotationToString(rotation)
                         + " displayId: " + displayContent.getDisplayId());
             }
-            mMagnifedViewport.onRotationChanged();
-            mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_ROTATION_CHANGED);
+            mMagnifedViewport.onDisplaySizeChanged();
+            mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_DISPLAY_SIZE_CHANGED);
         }
 
         void onAppWindowTransition(int displayId, int transition) {
-            if (mAccessibilityTracing.isEnabled()) {
-                mAccessibilityTracing.logState(LOG_TAG + ".onAppWindowTransition",
+            if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+                mAccessibilityTracing.logTrace(LOG_TAG + ".onAppWindowTransition",
+                        FLAGS_MAGNIFICATION_CALLBACK,
                         "displayId=" + displayId + "; transition=" + transition);
             }
             if (DEBUG_WINDOW_TRANSITIONS) {
@@ -733,8 +767,9 @@
         }
 
         void onWindowTransition(WindowState windowState, int transition) {
-            if (mAccessibilityTracing.isEnabled()) {
-                mAccessibilityTracing.logState(LOG_TAG + ".onWindowTransition",
+            if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+                mAccessibilityTracing.logTrace(LOG_TAG + ".onWindowTransition",
+                        FLAGS_MAGNIFICATION_CALLBACK,
                         "windowState={" + windowState + "}; transition=" + transition);
             }
             if (DEBUG_WINDOW_TRANSITIONS) {
@@ -791,18 +826,18 @@
         }
 
         void onImeSurfaceShownChanged(boolean shown) {
-            if (mAccessibilityTracing.isEnabled()) {
-                mAccessibilityTracing.logState(
-                        LOG_TAG + ".onImeSurfaceShownChanged", "shown=" + shown);
+            if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+                mAccessibilityTracing.logTrace(LOG_TAG + ".onImeSurfaceShownChanged",
+                        FLAGS_MAGNIFICATION_CALLBACK, "shown=" + shown);
             }
             mHandler.obtainMessage(MyHandler.MESSAGE_NOTIFY_IME_WINDOW_VISIBILITY_CHANGED,
                     shown ? 1 : 0, 0).sendToTarget();
         }
 
         MagnificationSpec getMagnificationSpecForWindow(WindowState windowState) {
-            if (mAccessibilityTracing.isEnabled()) {
-                mAccessibilityTracing.logState(LOG_TAG + ".getMagnificationSpecForWindow",
-                        "windowState={" + windowState + "}");
+            if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+                mAccessibilityTracing.logTrace(LOG_TAG + ".getMagnificationSpecForWindow",
+                        FLAGS_MAGNIFICATION_CALLBACK, "windowState={" + windowState + "}");
             }
             MagnificationSpec spec = mMagnifedViewport.getMagnificationSpec();
             if (spec != null && !spec.isNop()) {
@@ -814,8 +849,9 @@
         }
 
         void getMagnificationRegion(Region outMagnificationRegion) {
-            if (mAccessibilityTracing.isEnabled()) {
-                mAccessibilityTracing.logState(LOG_TAG + ".getMagnificationRegion",
+            if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+                mAccessibilityTracing.logTrace(LOG_TAG + ".getMagnificationRegion",
+                        FLAGS_MAGNIFICATION_CALLBACK,
                         "outMagnificationRegion={" + outMagnificationRegion + "}");
             }
             // Make sure we're working with the most current bounds
@@ -824,25 +860,26 @@
         }
 
         void destroy() {
-            if (mAccessibilityTracing.isEnabled()) {
-                mAccessibilityTracing.logState(LOG_TAG + ".destroy");
+            if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+                mAccessibilityTracing.logTrace(LOG_TAG + ".destroy", FLAGS_MAGNIFICATION_CALLBACK);
             }
             mMagnifedViewport.destroyWindow();
         }
 
         // Can be called outside of a surface transaction
         void showMagnificationBoundsIfNeeded() {
-            if (mAccessibilityTracing.isEnabled()) {
-                mAccessibilityTracing.logState(LOG_TAG + ".showMagnificationBoundsIfNeeded");
+            if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+                mAccessibilityTracing.logTrace(LOG_TAG + ".showMagnificationBoundsIfNeeded",
+                        FLAGS_MAGNIFICATION_CALLBACK);
             }
             mHandler.obtainMessage(MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED)
                     .sendToTarget();
         }
 
         void drawMagnifiedRegionBorderIfNeeded(SurfaceControl.Transaction t) {
-            if (mAccessibilityTracing.isEnabled()) {
-                mAccessibilityTracing.logState(LOG_TAG + ".drawMagnifiedRegionBorderIfNeeded",
-                        "transition={" + t + "}");
+            if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+                mAccessibilityTracing.logTrace(LOG_TAG + ".drawMagnifiedRegionBorderIfNeeded",
+                        FLAGS_MAGNIFICATION_CALLBACK, "transition={" + t + "}");
             }
             mMagnifedViewport.drawWindowIfNeeded(t);
         }
@@ -887,7 +924,8 @@
 
                 if (mDisplayContext.getResources().getConfiguration().isScreenRound()) {
                     mCircularPath = new Path();
-                    mDisplay.getRealSize(mScreenSize);
+
+                    getDisplaySizeLocked(mScreenSize);
                     final int centerXY = mScreenSize.x / 2;
                     mCircularPath.addCircle(centerXY, centerXY, centerXY, Path.Direction.CW);
                 } else {
@@ -917,7 +955,7 @@
             }
 
             void recomputeBounds() {
-                mDisplay.getRealSize(mScreenSize);
+                getDisplaySizeLocked(mScreenSize);
                 final int screenWidth = mScreenSize.x;
                 final int screenHeight = mScreenSize.y;
 
@@ -1052,9 +1090,10 @@
                         || windowType == TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
             }
 
-            void onRotationChanged() {
+            void onDisplaySizeChanged() {
                 // If we are showing the magnification border, hide it immediately so
-                // the user does not see strange artifacts during rotation. The screenshot
+                // the user does not see strange artifacts during display size changed caused by
+                // rotation or folding/unfolding the device. In the rotation case, the screenshot
                 // used for rotation already has the border. After the rotation is complete
                 // we will show the border.
                 if (isMagnifying() || isForceShowingMagnifiableBounds()) {
@@ -1112,6 +1151,12 @@
                 }, false /* traverseTopToBottom */ );
             }
 
+            private void getDisplaySizeLocked(Point outSize) {
+                final Rect bounds =
+                        mDisplayContent.getConfiguration().windowConfiguration.getBounds();
+                outSize.set(bounds.width(), bounds.height());
+            }
+
             void dump(PrintWriter pw, String prefix) {
                 mWindow.dump(pw, prefix);
             }
@@ -1226,7 +1271,7 @@
 
                 void updateSize() {
                     synchronized (mService.mGlobalLock) {
-                        mDisplay.getRealSize(mScreenSize);
+                        getDisplaySizeLocked(mScreenSize);
                         mBlastBufferQueue.update(mSurfaceControl, mScreenSize.x, mScreenSize.y,
                                 PixelFormat.RGBA_8888);
                         invalidate(mDirtyRect);
@@ -1365,7 +1410,7 @@
             public static final int MESSAGE_NOTIFY_MAGNIFICATION_REGION_CHANGED = 1;
             public static final int MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED = 2;
             public static final int MESSAGE_NOTIFY_USER_CONTEXT_CHANGED = 3;
-            public static final int MESSAGE_NOTIFY_ROTATION_CHANGED = 4;
+            public static final int MESSAGE_NOTIFY_DISPLAY_SIZE_CHANGED = 4;
             public static final int MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED = 5;
             public static final int MESSAGE_NOTIFY_IME_WINDOW_VISIBILITY_CHANGED = 6;
 
@@ -1397,9 +1442,8 @@
                         mCallbacks.onUserContextChanged();
                     } break;
 
-                    case MESSAGE_NOTIFY_ROTATION_CHANGED: {
-                        final int rotation = message.arg1;
-                        mCallbacks.onRotationChanged(rotation);
+                    case MESSAGE_NOTIFY_DISPLAY_SIZE_CHANGED: {
+                        mCallbacks.onDisplaySizeChanged();
                     } break;
 
                     case MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED : {
@@ -1482,7 +1526,7 @@
 
         private final Handler mHandler;
 
-        private final AccessibilityTracing mAccessibilityTracing;
+        private final AccessibilityControllerInternalImpl mAccessibilityTracing;
 
         private final WindowsForAccessibilityCallback mCallback;
 
@@ -1502,24 +1546,26 @@
             mCallback = callback;
             mDisplayId = displayId;
             mHandler = new MyHandler(mService.mH.getLooper());
-            mAccessibilityTracing = AccessibilityTracing.getInstance(mService);
+            mAccessibilityTracing =
+                    AccessibilityController.getAccessibilityControllerInternal(mService);
             mRecurringAccessibilityEventsIntervalMillis = ViewConfiguration
                     .getSendRecurringAccessibilityEventsInterval();
             computeChangedWindows(true);
         }
 
         void performComputeChangedWindows(boolean forceSend) {
-            if (mAccessibilityTracing.isEnabled()) {
-                mAccessibilityTracing.logState(LOG_TAG + ".performComputeChangedWindows",
-                        "forceSend=" + forceSend);
+            if (mAccessibilityTracing.isTracingEnabled(FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) {
+                mAccessibilityTracing.logTrace(LOG_TAG + ".performComputeChangedWindows",
+                        FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK, "forceSend=" + forceSend);
             }
             mHandler.removeMessages(MyHandler.MESSAGE_COMPUTE_CHANGED_WINDOWS);
             computeChangedWindows(forceSend);
         }
 
         void scheduleComputeChangedWindows() {
-            if (mAccessibilityTracing.isEnabled()) {
-                mAccessibilityTracing.logState(LOG_TAG + ".scheduleComputeChangedWindows");
+            if (mAccessibilityTracing.isTracingEnabled(FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) {
+                mAccessibilityTracing.logTrace(LOG_TAG + ".scheduleComputeChangedWindows",
+                        FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK);
             }
             if (!mHandler.hasMessages(MyHandler.MESSAGE_COMPUTE_CHANGED_WINDOWS)) {
                 mHandler.sendEmptyMessageDelayed(MyHandler.MESSAGE_COMPUTE_CHANGED_WINDOWS,
@@ -1542,6 +1588,13 @@
             mEmbeddedDisplayIdList.add(displayId);
         }
 
+        void notifyDisplayReparented(int embeddedDisplayId) {
+            // Notifies the A11y framework the display is reparented and
+            // becomes an embedded display for removing the un-used
+            // displayWindowObserver of this embedded one.
+            mCallback.onDisplayReparented(embeddedDisplayId);
+        }
+
         boolean shellRootIsAbove(WindowState windowState, ShellRoot shellRoot) {
             int wsLayer = mService.mPolicy.getWindowLayerLw(windowState);
             int shellLayer = mService.mPolicy.getWindowLayerFromTypeLw(shellRoot.getWindowType(),
@@ -1594,9 +1647,9 @@
          * @param forceSend Send the windows the accessibility even if they haven't changed.
          */
         void computeChangedWindows(boolean forceSend) {
-            if (mAccessibilityTracing.isEnabled()) {
-                mAccessibilityTracing.logState(
-                        LOG_TAG + ".computeChangedWindows", "forceSend=" + forceSend);
+            if (mAccessibilityTracing.isTracingEnabled(FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) {
+                mAccessibilityTracing.logTrace(LOG_TAG + ".computeChangedWindows",
+                        FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK, "forceSend=" + forceSend);
             }
             if (DEBUG) {
                 Slog.i(LOG_TAG, "computeChangedWindows()");
@@ -1945,8 +1998,8 @@
     private static final class AccessibilityControllerInternalImpl
             implements AccessibilityControllerInternal {
 
-        private static AccessibilityControllerInternal sInstance;
-        static AccessibilityControllerInternal getInstance(WindowManagerService service) {
+        private static AccessibilityControllerInternalImpl sInstance;
+        static AccessibilityControllerInternalImpl getInstance(WindowManagerService service) {
             synchronized (STATIC_LOCK) {
                 if (sInstance == null) {
                     sInstance = new AccessibilityControllerInternalImpl(service);
@@ -1956,18 +2009,23 @@
         }
 
         private final AccessibilityTracing mTracing;
+        private volatile long mEnabledTracingFlags;
+
         private AccessibilityControllerInternalImpl(WindowManagerService service) {
             mTracing = AccessibilityTracing.getInstance(service);
+            mEnabledTracingFlags = 0L;
         }
 
         @Override
-        public void startTrace() {
+        public void startTrace(long loggingTypes) {
+            mEnabledTracingFlags = loggingTypes;
             mTracing.startTrace();
         }
 
         @Override
         public void stopTrace() {
             mTracing.stopTrace();
+            mEnabledTracingFlags = 0L;
         }
 
         @Override
@@ -1975,19 +2033,37 @@
             return mTracing.isEnabled();
         }
 
-        @Override
-        public void logTrace(
-                String where, String callingParams, byte[] a11yDump, int callingUid,
-                StackTraceElement[] stackTrace) {
-            mTracing.logState(where, callingParams, a11yDump, callingUid, stackTrace);
+        boolean isTracingEnabled(long flags) {
+            return (flags & mEnabledTracingFlags) != 0L;
+        }
+
+        void logTrace(String where, long loggingTypes) {
+            logTrace(where, loggingTypes, "");
+        }
+
+        void logTrace(String where, long loggingTypes, String callingParams) {
+            logTrace(where, loggingTypes, callingParams, "".getBytes(), Binder.getCallingUid());
+        }
+
+        void logTrace(String where, long loggingTypes, String callingParams, byte[] a11yDump,
+                int callingUid) {
+            mTracing.logState(where, loggingTypes, callingParams, a11yDump, callingUid,
+                    new HashSet<String>(Arrays.asList("logTrace")));
         }
 
         @Override
-        public void logTrace(
-                String where, String callingParams, byte[] a11yDump, int callingUid,
-                StackTraceElement[] callStack, long timeStamp, int processId, long threadId) {
-            mTracing.logState(where, callingParams, a11yDump, callingUid, callStack, timeStamp,
-                    processId, threadId);
+        public void logTrace(String where, long loggingTypes, String callingParams, byte[] a11yDump,
+                int callingUid, StackTraceElement[] stackTrace, Set<String> ignoreStackEntries) {
+            mTracing.logState(where, loggingTypes, callingParams, a11yDump, callingUid, stackTrace,
+                    ignoreStackEntries);
+        }
+
+        @Override
+        public void logTrace(String where, long loggingTypes, String callingParams, byte[] a11yDump,
+                int callingUid, StackTraceElement[] callStack, long timeStamp, int processId,
+                long threadId, Set<String> ignoreStackEntries) {
+            mTracing.logState(where, loggingTypes, callingParams, a11yDump, callingUid, callStack,
+                    timeStamp, processId, threadId, ignoreStackEntries);
         }
     }
 
@@ -2004,7 +2080,6 @@
 
         private static final int BUFFER_CAPACITY = 1024 * 1024 * 12;
         private static final String TRACE_FILENAME = "/data/misc/a11ytrace/a11y_trace.pb";
-        private static final String TRACE_DIRECTORY = "/data/misc/a11ytrace/";
         private static final String TAG = "AccessibilityTracing";
         private static final long MAGIC_NUMBER_VALUE =
                 ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
@@ -2034,13 +2109,6 @@
                 return;
             }
             synchronized (mLock) {
-                try {
-                    Files.createDirectories(Paths.get(TRACE_DIRECTORY));
-                    mTraceFile.createNewFile();
-                } catch (Exception e) {
-                    Slog.e(TAG, "Error: Failed to create trace file.");
-                    return;
-                }
                 mEnabled = true;
                 mBuffer.resetBuffer();
             }
@@ -2071,86 +2139,127 @@
         /**
          * Write an accessibility trace log entry.
          */
-        void logState(String where) {
+        void logState(String where, long loggingTypes) {
             if (!mEnabled) {
                 return;
             }
-            logState(where, "");
+            logState(where, loggingTypes, "");
         }
 
         /**
          * Write an accessibility trace log entry.
          */
-        void logState(String where, String callingParams) {
+        void logState(String where, long loggingTypes, String callingParams) {
             if (!mEnabled) {
                 return;
             }
-            logState(where, callingParams, "".getBytes());
+            logState(where, loggingTypes, callingParams, "".getBytes());
         }
 
         /**
          * Write an accessibility trace log entry.
          */
-        void logState(String where, String callingParams, byte[] a11yDump) {
+        void logState(String where, long loggingTypes, String callingParams, byte[] a11yDump) {
             if (!mEnabled) {
                 return;
             }
-            logState(where, callingParams, a11yDump, Binder.getCallingUid());
+            logState(where, loggingTypes, callingParams, a11yDump, Binder.getCallingUid(),
+                    new HashSet<String>(Arrays.asList("logState")));
         }
 
         /**
          * Write an accessibility trace log entry.
          */
-        void logState(
-                String where, String callingParams, byte[] a11yDump, int callingUid) {
+        void logState(String where, long loggingTypes, String callingParams, byte[] a11yDump,
+                int callingUid, Set<String> ignoreStackEntries) {
             if (!mEnabled) {
                 return;
             }
             StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
-
-            logState(where, callingParams, a11yDump, callingUid, stackTraceElements);
+            ignoreStackEntries.add("logState");
+            logState(where, loggingTypes, callingParams, a11yDump, callingUid, stackTraceElements,
+                    ignoreStackEntries);
         }
 
         /**
          * Write an accessibility trace log entry.
          */
-        void logState(String where, String callingParams, byte[] a11yDump, int callingUid,
-                StackTraceElement[] stackTrace) {
+        void logState(String where, long loggingTypes, String callingParams, byte[] a11yDump,
+                int callingUid, StackTraceElement[] stackTrace, Set<String> ignoreStackEntries) {
             if (!mEnabled) {
                 return;
             }
-
-            log(where, callingParams, a11yDump, callingUid, stackTrace,
+            log(where, loggingTypes, callingParams, a11yDump, callingUid, stackTrace,
                     SystemClock.elapsedRealtimeNanos(),
                     Process.myPid() + ":" + Application.getProcessName(),
-                    Thread.currentThread().getId() + ":" + Thread.currentThread().getName());
+                    Thread.currentThread().getId() + ":" + Thread.currentThread().getName(),
+                    ignoreStackEntries);
         }
 
         /**
          * Write an accessibility trace log entry.
          */
-        void logState(String where, String callingParams, byte[] a11yDump, int callingUid,
-                StackTraceElement[] callingStack, long timeStamp, int processId, long threadId) {
+        void logState(String where, long loggingTypes, String callingParams, byte[] a11yDump,
+                int callingUid, StackTraceElement[] callingStack, long timeStamp, int processId,
+                long threadId, Set<String> ignoreStackEntries) {
             if (!mEnabled) {
                 return;
             }
-            log(where, callingParams, a11yDump, callingUid, callingStack, timeStamp,
-                    String.valueOf(processId), String.valueOf(threadId));
+            log(where, loggingTypes, callingParams, a11yDump, callingUid, callingStack, timeStamp,
+                    String.valueOf(processId), String.valueOf(threadId), ignoreStackEntries);
         }
 
-        private  String toStackTraceString(StackTraceElement[] stackTraceElements) {
+        private  String toStackTraceString(StackTraceElement[] stackTraceElements,
+                Set<String> ignoreStackEntries) {
+
             if (stackTraceElements == null) {
                 return "";
             }
+
             StringBuilder stringBuilder = new StringBuilder();
-            boolean skip = true;
-            for (int i = 0; i < stackTraceElements.length; i++) {
-                if (stackTraceElements[i].toString().contains(
-                            AccessibilityTracing.class.getSimpleName())) {
-                    skip = false;
-                } else if (!skip) {
-                    stringBuilder.append(stackTraceElements[i].toString()).append("\n");
+            int i = 0;
+
+            // Skip the first a few elements until after any ignoreStackEntries
+            int firstMatch = -1;
+            while (i < stackTraceElements.length) {
+                for (String ele : ignoreStackEntries) {
+                    if (stackTraceElements[i].toString().contains(ele)) {
+                        // found the first stack element containing the ignorable stack entries
+                        firstMatch = i;
+                        break;
+                    }
                 }
+                if (firstMatch < 0) {
+                    // Haven't found the first match yet, continue
+                    i++;
+                } else {
+                    break;
+                }
+            }
+            int lastMatch = firstMatch;
+            if (i < stackTraceElements.length) {
+                i++;
+                // Found the first match. Now look for the last match.
+                while (i < stackTraceElements.length) {
+                    for (String ele : ignoreStackEntries) {
+                        if (stackTraceElements[i].toString().contains(ele)) {
+                            // This is a match. Look at the next stack element.
+                            lastMatch = i;
+                            break;
+                        }
+                    }
+                    if (lastMatch != i) {
+                        // Found a no-match.
+                        break;
+                    }
+                    i++;
+                }
+            }
+
+            i = lastMatch + 1;
+            while (i < stackTraceElements.length) {
+                stringBuilder.append(stackTraceElements[i].toString()).append("\n");
+                i++;
             }
             return stringBuilder.toString();
         }
@@ -2158,19 +2267,22 @@
         /**
          * Write the current state to the buffer
          */
-        private void log(String where, String callingParams, byte[] a11yDump, int callingUid,
-                StackTraceElement[] callingStack, long timeStamp, String processName,
-                String threadName) {
+        private void log(String where, long loggingTypes, String callingParams, byte[] a11yDump,
+                int callingUid, StackTraceElement[] callingStack, long timeStamp,
+                String processName, String threadName, Set<String> ignoreStackEntries) {
             SomeArgs args = SomeArgs.obtain();
             args.arg1 = timeStamp;
-            args.arg2 = where;
-            args.arg3 = processName;
-            args.arg4 = threadName;
-            args.arg5 = callingUid;
-            args.arg6 = callingParams;
-            args.arg7 = callingStack;
-            args.arg8 = a11yDump;
-            mHandler.obtainMessage(LogHandler.MESSAGE_LOG_TRACE_ENTRY, args).sendToTarget();
+            args.arg2 = loggingTypes;
+            args.arg3 = where;
+            args.arg4 = processName;
+            args.arg5 = threadName;
+            args.arg6 = ignoreStackEntries;
+            args.arg7 = callingParams;
+            args.arg8 = callingStack;
+            args.arg9 = a11yDump;
+
+            mHandler.obtainMessage(
+                    LogHandler.MESSAGE_LOG_TRACE_ENTRY, callingUid, 0, args).sendToTarget();
         }
 
         /**
@@ -2199,8 +2311,6 @@
                                     LocalServices.getService(PackageManagerInternal.class);
 
                             long tokenOuter = os.start(ENTRY);
-                            String callingStack =
-                                    toStackTraceString((StackTraceElement[]) args.arg7);
 
                             long reportedTimeStampNanos = (long) args.arg1;
                             long currentElapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos();
@@ -2213,13 +2323,25 @@
 
                             os.write(ELAPSED_REALTIME_NANOS, reportedTimeStampNanos);
                             os.write(CALENDAR_TIME, fm.format(reportedTimeMillis).toString());
-                            os.write(WHERE, (String) args.arg2);
-                            os.write(PROCESS_NAME, (String) args.arg3);
-                            os.write(THREAD_ID_NAME, (String) args.arg4);
-                            os.write(CALLING_PKG, pmInternal.getNameForUid((int) args.arg5));
-                            os.write(CALLING_PARAMS, (String) args.arg6);
+
+                            long loggingTypes = (long) args.arg2;
+                            List<String> loggingTypeNames =
+                                    AccessibilityTrace.getNamesOfLoggingTypes(loggingTypes);
+
+                            for (String type : loggingTypeNames) {
+                                os.write(LOGGING_TYPE, type);
+                            }
+                            os.write(WHERE, (String) args.arg3);
+                            os.write(PROCESS_NAME, (String) args.arg4);
+                            os.write(THREAD_ID_NAME, (String) args.arg5);
+                            os.write(CALLING_PKG, pmInternal.getNameForUid(message.arg1));
+                            os.write(CALLING_PARAMS, (String) args.arg7);
+
+                            String callingStack = toStackTraceString(
+                                    (StackTraceElement[]) args.arg8, (Set<String>) args.arg6);
+
                             os.write(CALLING_STACKS, callingStack);
-                            os.write(ACCESSIBILITY_SERVICE, (byte[]) args.arg8);
+                            os.write(ACCESSIBILITY_SERVICE, (byte[]) args.arg9);
 
                             long tokenInner = os.start(WINDOW_MANAGER_SERVICE);
                             synchronized (mService.mGlobalLock) {
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 3a4faf7..8a76e3e5 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -28,6 +28,11 @@
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IMMERSIVE;
+import static com.android.server.wm.ActivityRecord.State.DESTROYED;
+import static com.android.server.wm.ActivityRecord.State.DESTROYING;
+import static com.android.server.wm.ActivityRecord.State.PAUSING;
+import static com.android.server.wm.ActivityRecord.State.RESTARTING_PROCESS;
+import static com.android.server.wm.ActivityRecord.State.RESUMED;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ALL;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
@@ -35,8 +40,6 @@
 import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
 import static com.android.server.wm.ActivityTaskManagerService.TAG_SWITCH;
 import static com.android.server.wm.ActivityTaskManagerService.enforceNotIsolatedCaller;
-import static com.android.server.wm.Task.ActivityState.DESTROYED;
-import static com.android.server.wm.Task.ActivityState.DESTROYING;
 
 import android.annotation.NonNull;
 import android.app.Activity;
@@ -68,6 +71,7 @@
 import android.util.Slog;
 import android.view.RemoteAnimationDefinition;
 import android.window.SizeConfigurationBuckets;
+import android.window.TransitionInfo;
 
 import com.android.internal.app.AssistUtils;
 import com.android.internal.policy.IKeyguardDismissCallback;
@@ -188,7 +192,7 @@
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "activityStopped");
             r = ActivityRecord.isInRootTaskLocked(token);
             if (r != null) {
-                if (r.attachedToProcess() && r.isState(Task.ActivityState.RESTARTING_PROCESS)) {
+                if (r.attachedToProcess() && r.isState(RESTARTING_PROCESS)) {
                     // The activity was requested to restart from
                     // {@link #restartActivityProcessIfVisible}.
                     restartingName = r.app.mName;
@@ -1010,10 +1014,13 @@
         final long origId = Binder.clearCallingIdentity();
         synchronized (mGlobalLock) {
             final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
-            if (r != null && r.isState(Task.ActivityState.RESUMED, Task.ActivityState.PAUSING)) {
+            if (r != null && r.isState(RESUMED, PAUSING)) {
                 r.mDisplayContent.mAppTransition.overridePendingAppTransition(
                         packageName, enterAnim, exitAnim, null, null,
                         r.mOverrideTaskTransition);
+                mService.getTransitionController().setOverrideAnimation(
+                        TransitionInfo.AnimationOptions.makeCustomAnimOptions(packageName,
+                                enterAnim, exitAnim, r.mOverrideTaskTransition));
             }
         }
         Binder.restoreCallingIdentity(origId);
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 0f6a718..b2e3fcb 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -775,7 +775,7 @@
             Slog.i(TAG, "notifyVisibilityChanged " + r + " visible=" + r.mVisibleRequested
                     + " state=" + r.getState() + " finishing=" + r.finishing);
         }
-        if (r.isState(Task.ActivityState.RESUMED) && r.mDisplayContent.isSleeping()) {
+        if (r.isState(ActivityRecord.State.RESUMED) && r.mDisplayContent.isSleeping()) {
             // The activity may be launching while keyguard is locked. The keyguard may be dismissed
             // after the activity finished relayout, so skip the visibility check to avoid aborting
             // the tracking of launch event.
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 64b5fc7..5eba87d 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -129,6 +129,17 @@
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SWITCH;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+import static com.android.server.wm.ActivityRecord.State.DESTROYED;
+import static com.android.server.wm.ActivityRecord.State.DESTROYING;
+import static com.android.server.wm.ActivityRecord.State.FINISHING;
+import static com.android.server.wm.ActivityRecord.State.INITIALIZING;
+import static com.android.server.wm.ActivityRecord.State.PAUSED;
+import static com.android.server.wm.ActivityRecord.State.PAUSING;
+import static com.android.server.wm.ActivityRecord.State.RESTARTING_PROCESS;
+import static com.android.server.wm.ActivityRecord.State.RESUMED;
+import static com.android.server.wm.ActivityRecord.State.STARTED;
+import static com.android.server.wm.ActivityRecord.State.STOPPED;
+import static com.android.server.wm.ActivityRecord.State.STOPPING;
 import static com.android.server.wm.ActivityRecordProto.ALL_DRAWN;
 import static com.android.server.wm.ActivityRecordProto.APP_STOPPED;
 import static com.android.server.wm.ActivityRecordProto.CLIENT_VISIBLE;
@@ -191,18 +202,7 @@
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
-import static com.android.server.wm.Task.ActivityState.DESTROYED;
-import static com.android.server.wm.Task.ActivityState.DESTROYING;
-import static com.android.server.wm.Task.ActivityState.FINISHING;
-import static com.android.server.wm.Task.ActivityState.INITIALIZING;
-import static com.android.server.wm.Task.ActivityState.PAUSED;
-import static com.android.server.wm.Task.ActivityState.PAUSING;
-import static com.android.server.wm.Task.ActivityState.RESTARTING_PROCESS;
-import static com.android.server.wm.Task.ActivityState.RESUMED;
-import static com.android.server.wm.Task.ActivityState.STARTED;
-import static com.android.server.wm.Task.ActivityState.STOPPED;
-import static com.android.server.wm.Task.ActivityState.STOPPING;
-import static com.android.server.wm.Task.TASK_VISIBILITY_VISIBLE;
+import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE;
 import static com.android.server.wm.TaskPersister.DEBUG;
 import static com.android.server.wm.TaskPersister.IMAGE_EXTENSION;
 import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
@@ -302,6 +302,7 @@
 import android.view.RemoteAnimationAdapter;
 import android.view.RemoteAnimationDefinition;
 import android.view.RemoteAnimationTarget;
+import android.view.Surface.Rotation;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
 import android.view.WindowInsets.Type;
@@ -315,12 +316,14 @@
 import android.window.SplashScreenView;
 import android.window.SplashScreenView.SplashScreenViewParcelable;
 import android.window.TaskSnapshot;
+import android.window.TransitionInfo.AnimationOptions;
 import android.window.WindowContainerToken;
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.ResolverActivity;
 import com.android.internal.content.ReferrerIntent;
+import com.android.internal.os.TransferPipe;
 import com.android.internal.policy.AttributeCache;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.ToBooleanFunction;
@@ -337,7 +340,6 @@
 import com.android.server.uri.UriPermissionOwner;
 import com.android.server.wm.ActivityMetricsLogger.TransitionInfoSnapshot;
 import com.android.server.wm.SurfaceAnimator.AnimationType;
-import com.android.server.wm.Task.ActivityState;
 import com.android.server.wm.WindowManagerService.H;
 import com.android.server.wm.utils.InsetUtils;
 
@@ -346,6 +348,7 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.File;
+import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
@@ -489,7 +492,7 @@
     ActivityServiceConnectionsHolder mServiceConnectionsHolder; // Service connections.
     UriPermissionOwner uriPermissions; // current special URI access perms.
     WindowProcessController app;      // if non-null, hosting application
-    private ActivityState mState;    // current state we are in
+    private State mState;    // current state we are in
     private Bundle mIcicle;         // last saved activity state
     private PersistableBundle mPersistentState; // last persistently saved activity state
     private boolean mHaveState = true; // Indicates whether the last saved state of activity is
@@ -551,6 +554,21 @@
     static final int LAUNCH_SOURCE_TYPE_HOME = 2;
     static final int LAUNCH_SOURCE_TYPE_SYSTEMUI = 3;
     static final int LAUNCH_SOURCE_TYPE_APPLICATION = 4;
+
+    enum State {
+        INITIALIZING,
+        STARTED,
+        RESUMED,
+        PAUSING,
+        PAUSED,
+        STOPPING,
+        STOPPED,
+        FINISHING,
+        DESTROYING,
+        DESTROYED,
+        RESTARTING_PROCESS
+    }
+
     /**
      * The type of launch source.
      */
@@ -647,6 +665,14 @@
     boolean allDrawn;
     private boolean mLastAllDrawn;
 
+    /**
+     * Solely for reporting to ActivityMetricsLogger. Just tracks whether, the last time this
+     * Actiivty was part of a syncset, all windows were ready by the time the sync was ready (vs.
+     * only the top-occluding ones). The assumption here is if some were not ready, they were
+     * covered with starting-window/splash-screen.
+     */
+    boolean mLastAllReadyAtSync = false;
+
     private boolean mLastContainsShowWhenLockedWindow;
     private boolean mLastContainsDismissKeyguardWindow;
     private boolean mLastContainsTurnScreenOnWindow;
@@ -681,6 +707,8 @@
     private boolean mInSizeCompatModeForBounds = false;
 
     // Whether the aspect ratio restrictions applied to the activity bounds in applyAspectRatio().
+    // TODO(b/182268157): Aspect ratio can also be applie in resolveFixedOrientationConfiguration
+    // but that isn't reflected in this boolean.
     private boolean mIsAspectRatioApplied = false;
 
     // Bounds populated in resolveFixedOrientationConfiguration when this activity is letterboxed
@@ -790,6 +818,7 @@
      */
     private final Configuration mTmpConfig = new Configuration();
     private final Rect mTmpBounds = new Rect();
+    private final Rect mTmpOutNonDecorBounds = new Rect();
 
     // Token for targeting this activity for assist purposes.
     final Binder assistToken = new Binder();
@@ -1135,6 +1164,76 @@
         mLetterboxUiController.dump(pw, prefix);
     }
 
+    static boolean dumpActivity(FileDescriptor fd, PrintWriter pw, int index, ActivityRecord r,
+            String prefix, String label, boolean complete, boolean brief, boolean client,
+            String dumpPackage, boolean needNL, Runnable header, Task lastTask) {
+        if (dumpPackage != null && !dumpPackage.equals(r.packageName)) {
+            return false;
+        }
+
+        final boolean full = !brief && (complete || !r.isInHistory());
+        if (needNL) {
+            pw.println("");
+        }
+        if (header != null) {
+            header.run();
+        }
+
+        String innerPrefix = prefix + "      ";
+        String[] args = new String[0];
+        if (lastTask != r.getTask()) {
+            lastTask = r.getTask();
+            pw.print(prefix);
+            pw.print(full ? "* " : "  ");
+            pw.println(lastTask);
+            if (full) {
+                lastTask.dump(pw, prefix + "  ");
+            } else if (complete) {
+                // Complete + brief == give a summary.  Isn't that obvious?!?
+                if (lastTask.intent != null) {
+                    pw.print(prefix);
+                    pw.print("  ");
+                    pw.println(lastTask.intent.toInsecureString());
+                }
+            }
+        }
+        pw.print(prefix); pw.print(full ? "* " : "    "); pw.print(label);
+        pw.print(" #"); pw.print(index); pw.print(": ");
+        pw.println(r);
+        if (full) {
+            r.dump(pw, innerPrefix, true /* dumpAll */);
+        } else if (complete) {
+            // Complete + brief == give a summary.  Isn't that obvious?!?
+            pw.print(innerPrefix);
+            pw.println(r.intent.toInsecureString());
+            if (r.app != null) {
+                pw.print(innerPrefix);
+                pw.println(r.app);
+            }
+        }
+        if (client && r.attachedToProcess()) {
+            // flush anything that is already in the PrintWriter since the thread is going
+            // to write to the file descriptor directly
+            pw.flush();
+            try {
+                TransferPipe tp = new TransferPipe();
+                try {
+                    r.app.getThread().dumpActivity(
+                            tp.getWriteFd(), r.appToken, innerPrefix, args);
+                    // Short timeout, since blocking here can deadlock with the application.
+                    tp.go(fd, 2000);
+                } finally {
+                    tp.kill();
+                }
+            } catch (IOException e) {
+                pw.println(innerPrefix + "Failure while dumping the activity: " + e);
+            } catch (RemoteException e) {
+                pw.println(innerPrefix + "Got a RemoteException while dumping the activity");
+            }
+        }
+        return true;
+    }
+
     void setAppTimeTracker(AppTimeTracker att) {
         appTimeTracker = att;
     }
@@ -1244,11 +1343,10 @@
                 updatePictureInPictureMode(null, false);
             } else {
                 mLastReportedMultiWindowMode = inMultiWindowMode;
-                computeConfigurationAfterMultiWindowModeChange();
                 // If the activity is in stopping or stopped state, for instance, it's in the
                 // split screen task and not the top one, the last configuration it should keep
                 // is the one before multi-window mode change.
-                final ActivityState state = getState();
+                final State state = getState();
                 if (state != STOPPED && state != STOPPING) {
                     ensureActivityConfiguration(0 /* globalChanges */, PRESERVE_WINDOWS,
                             true /* ignoreVisibility */);
@@ -1271,31 +1369,25 @@
             // precede the configuration change from the resize.
             mLastReportedPictureInPictureMode = inPictureInPictureMode;
             mLastReportedMultiWindowMode = inPictureInPictureMode;
-            if (targetRootTaskBounds != null && !targetRootTaskBounds.isEmpty()) {
-                computeConfigurationAfterMultiWindowModeChange();
-            }
             ensureActivityConfiguration(0 /* globalChanges */, PRESERVE_WINDOWS,
                     true /* ignoreVisibility */);
         }
     }
 
-    private void computeConfigurationAfterMultiWindowModeChange() {
-        final Configuration newConfig = new Configuration();
-        newConfig.setTo(task.getRequestedOverrideConfiguration());
-        Rect outBounds = newConfig.windowConfiguration.getBounds();
-        final Configuration parentConfig = task.getParent().getConfiguration();
-        task.adjustForMinimalTaskDimensions(outBounds, outBounds, parentConfig);
-        task.computeConfigResourceOverrides(newConfig, parentConfig);
-    }
-
     Task getTask() {
         return task;
     }
 
+    @Nullable
+    TaskFragment getTaskFragment() {
+        WindowContainer parent = getParent();
+        return parent != null ? parent.asTaskFragment() : null;
+    }
+
     @Override
     void onParentChanged(ConfigurationContainer newParent, ConfigurationContainer oldParent) {
-        final Task oldTask = oldParent != null ? (Task) oldParent : null;
-        final Task newTask = newParent != null ? (Task) newParent : null;
+        final Task oldTask = oldParent != null ? ((TaskFragment) oldParent).getTask() : null;
+        final Task newTask = newParent != null ? ((TaskFragment) newParent).getTask() : null;
         this.task = newTask;
 
         super.onParentChanged(newParent, oldParent);
@@ -1353,11 +1445,12 @@
 
         updateColorTransform();
 
-        if (oldTask != null) {
-            oldTask.cleanUpActivityReferences(this);
+        if (oldParent != null) {
+            ((TaskFragment) oldParent).cleanUpActivityReferences(this);
         }
-        if (newTask != null && isState(RESUMED)) {
-            newTask.setResumedActivity(this, "onParentChanged");
+
+        if (newParent != null && isState(RESUMED)) {
+            ((TaskFragment) newParent).setResumedActivity(this, "onParentChanged");
         }
 
         if (rootTask != null && rootTask.topRunningActivity() == this) {
@@ -2321,23 +2414,24 @@
     }
 
     /**
-     * Reparents this activity into {@param newTask} at the provided {@param position}.  The caller
-     * should ensure that the {@param newTask} is not already the parent of this activity.
+     * Reparents this activity into {@param newTaskFrag} at the provided {@param position}. The
+     * caller should ensure that the {@param newTaskFrag} is not already the parent of this
+     * activity.
      */
-    void reparent(Task newTask, int position, String reason) {
+    void reparent(TaskFragment newTaskFrag, int position, String reason) {
         if (getParent() == null) {
             Slog.w(TAG, "reparent: Attempted to reparent non-existing app token: " + appToken);
             return;
         }
-        final Task prevTask = task;
-        if (prevTask == newTask) {
-            throw new IllegalArgumentException(reason + ": task=" + newTask
+        final TaskFragment prevTaskFrag = getTaskFragment();
+        if (prevTaskFrag == newTaskFrag) {
+            throw new IllegalArgumentException(reason + ": task fragment =" + newTaskFrag
                     + " is already the parent of r=" + this);
         }
 
         ProtoLog.i(WM_DEBUG_ADD_REMOVE, "reparent: moving activity=%s"
-                + " to task=%d at %d", this, task.mTaskId, position);
-        reparent(newTask, position);
+                + " to new task fragment in task=%d at %d", this, task.mTaskId, position);
+        reparent(newTaskFrag, position);
     }
 
     private boolean isHomeIntent(Intent intent) {
@@ -2903,7 +2997,7 @@
         }
 
         final Task rootTask = getRootTask();
-        final boolean mayAdjustTop = (isState(RESUMED) || rootTask.getResumedActivity() == null)
+        final boolean mayAdjustTop = (isState(RESUMED) || rootTask.getTopResumedActivity() == null)
                 && rootTask.isFocusedRootTaskOnDisplay()
                 // Do not adjust focus task because the task will be reused to launch new activity.
                 && !task.isClearingToReuseTask();
@@ -2982,12 +3076,12 @@
                 // Tell window manager to prepare for this one to be removed.
                 setVisibility(false);
 
-                if (task.getPausingActivity() == null) {
+                if (getTaskFragment().getPausingActivity() == null) {
                     ProtoLog.v(WM_DEBUG_STATES, "Finish needs to pause: %s", this);
                     if (DEBUG_USER_LEAVING) {
                         Slog.v(TAG_USER_LEAVING, "finish() => pause with userLeaving=false");
                     }
-                    task.startPausingLocked(false /* userLeaving */, false /* uiSleeping */,
+                    getTaskFragment().startPausing(false /* userLeaving */, false /* uiSleeping */,
                             null /* resuming */, "finish");
                 }
 
@@ -3118,8 +3212,8 @@
 
         // Clear last paused activity to ensure top activity can be resumed during sleeping.
         if (isNextNotYetVisible && mDisplayContent.isSleeping()
-                && next == next.getRootTask().mLastPausedActivity) {
-            next.getRootTask().mLastPausedActivity = null;
+                && next == next.getTaskFragment().mLastPausedActivity) {
+            next.getTaskFragment().clearLastPausedActivity();
         }
 
         if (isCurrentVisible) {
@@ -3306,8 +3400,8 @@
             if (DEBUG_SWITCH) {
                 final Task task = getTask();
                 Slog.v(TAG_SWITCH, "Safely destroying " + this + " in state " + getState()
-                        + " resumed=" + task.getResumedActivity()
-                        + " pausing=" + task.getPausingActivity()
+                        + " resumed=" + task.getTopResumedActivity()
+                        + " pausing=" + task.getTopPausingActivity()
                         + " for reason " + reason);
             }
             return destroyImmediately(reason);
@@ -3386,7 +3480,7 @@
      * Note: Call before {@link #removeFromHistory(String)}.
      */
     void cleanUp(boolean cleanServices, boolean setState) {
-        task.cleanUpActivityReferences(this);
+        getTaskFragment().cleanUpActivityReferences(this);
         clearLastParentBeforePip();
 
         // Clean up the splash screen if it was still displayed.
@@ -3494,7 +3588,7 @@
             // failed more than twice. Skip activities that's already finishing cleanly by itself.
             remove = false;
         } else if ((!mHaveState && !stateNotNeeded
-                && !isState(ActivityState.RESTARTING_PROCESS)) || finishing) {
+                && !isState(State.RESTARTING_PROCESS)) || finishing) {
             // Don't currently have state for the activity, or it is finishing -- always remove it.
             remove = true;
         } else if (!mVisibleRequested && launchCount > 2
@@ -4218,6 +4312,7 @@
     private void applyOptionsAnimation(ActivityOptions pendingOptions, Intent intent) {
         final int animationType = pendingOptions.getAnimationType();
         final DisplayContent displayContent = getDisplayContent();
+        AnimationOptions options = null;
         switch (animationType) {
             case ANIM_CUSTOM:
                 displayContent.mAppTransition.overridePendingAppTransition(
@@ -4227,11 +4322,17 @@
                         pendingOptions.getAnimationStartedListener(),
                         pendingOptions.getAnimationFinishedListener(),
                         pendingOptions.getOverrideTaskTransition());
+                options = AnimationOptions.makeCustomAnimOptions(pendingOptions.getPackageName(),
+                        pendingOptions.getCustomEnterResId(), pendingOptions.getCustomExitResId(),
+                        pendingOptions.getOverrideTaskTransition());
                 break;
             case ANIM_CLIP_REVEAL:
                 displayContent.mAppTransition.overridePendingAppTransitionClipReveal(
                         pendingOptions.getStartX(), pendingOptions.getStartY(),
                         pendingOptions.getWidth(), pendingOptions.getHeight());
+                options = AnimationOptions.makeClipRevealAnimOptions(
+                        pendingOptions.getStartX(), pendingOptions.getStartY(),
+                        pendingOptions.getWidth(), pendingOptions.getHeight());
                 if (intent.getSourceBounds() == null) {
                     intent.setSourceBounds(new Rect(pendingOptions.getStartX(),
                             pendingOptions.getStartY(),
@@ -4243,6 +4344,9 @@
                 displayContent.mAppTransition.overridePendingAppTransitionScaleUp(
                         pendingOptions.getStartX(), pendingOptions.getStartY(),
                         pendingOptions.getWidth(), pendingOptions.getHeight());
+                options = AnimationOptions.makeScaleUpAnimOptions(
+                        pendingOptions.getStartX(), pendingOptions.getStartY(),
+                        pendingOptions.getWidth(), pendingOptions.getHeight());
                 if (intent.getSourceBounds() == null) {
                     intent.setSourceBounds(new Rect(pendingOptions.getStartX(),
                             pendingOptions.getStartY(),
@@ -4258,6 +4362,8 @@
                         pendingOptions.getStartX(), pendingOptions.getStartY(),
                         pendingOptions.getAnimationStartedListener(),
                         scaleUp);
+                options = AnimationOptions.makeThumnbnailAnimOptions(buffer,
+                        pendingOptions.getStartX(), pendingOptions.getStartY(), scaleUp);
                 if (intent.getSourceBounds() == null && buffer != null) {
                     intent.setSourceBounds(new Rect(pendingOptions.getStartX(),
                             pendingOptions.getStartY(),
@@ -4297,6 +4403,7 @@
             case ANIM_OPEN_CROSS_PROFILE_APPS:
                 displayContent.mAppTransition
                         .overridePendingAppTransitionStartCrossProfileApps();
+                options = AnimationOptions.makeCrossProfileAnimOptions();
                 break;
             case ANIM_NONE:
             case ANIM_UNDEFINED:
@@ -4305,6 +4412,10 @@
                 Slog.e(TAG_WM, "applyOptionsLocked: Unknown animationType=" + animationType);
                 break;
         }
+
+        if (options != null) {
+            mAtmService.getTransitionController().setOverrideAnimation(options);
+        }
     }
 
     void clearAllDrawn() {
@@ -4437,6 +4548,10 @@
         }
     }
 
+    boolean getDeferHidingClient() {
+        return mDeferHidingClient;
+    }
+
     @Override
     boolean isVisible() {
         // If the activity isn't hidden then it is considered visible and there is no need to check
@@ -4601,7 +4716,7 @@
         }
 
         // If in a transition, defer commits for activities that are going invisible
-        if (!visible && mAtmService.getTransitionController().inTransition()) {
+        if (!visible && mAtmService.getTransitionController().inTransition(this)) {
             return;
         }
         // If we are preparing an app transition, then delay changing
@@ -4850,7 +4965,7 @@
         return mCurrentLaunchCanTurnScreenOn;
     }
 
-    void setState(ActivityState state, String reason) {
+    void setState(State state, String reason) {
         ProtoLog.v(WM_DEBUG_STATES, "State movement: %s from:%s to:%s reason:%s",
                 this, getState(), state, reason);
 
@@ -4862,8 +4977,8 @@
 
         mState = state;
 
-        if (task != null) {
-            task.onActivityStateChanged(this, state, reason);
+        if (getTaskFragment() != null) {
+            getTaskFragment().onActivityStateChanged(this, state, reason);
         }
 
         // The WindowManager interprets the app stopping signal as
@@ -4923,44 +5038,42 @@
         }
     }
 
-    ActivityState getState() {
+    State getState() {
         return mState;
     }
 
     /**
      * Returns {@code true} if the Activity is in the specified state.
      */
-    boolean isState(ActivityState state) {
+    boolean isState(State state) {
         return state == mState;
     }
 
     /**
      * Returns {@code true} if the Activity is in one of the specified states.
      */
-    boolean isState(ActivityState state1, ActivityState state2) {
+    boolean isState(State state1, State state2) {
         return state1 == mState || state2 == mState;
     }
 
     /**
      * Returns {@code true} if the Activity is in one of the specified states.
      */
-    boolean isState(ActivityState state1, ActivityState state2, ActivityState state3) {
+    boolean isState(State state1, State state2, State state3) {
         return state1 == mState || state2 == mState || state3 == mState;
     }
 
     /**
      * Returns {@code true} if the Activity is in one of the specified states.
      */
-    boolean isState(ActivityState state1, ActivityState state2, ActivityState state3,
-            ActivityState state4) {
+    boolean isState(State state1, State state2, State state3, State state4) {
         return state1 == mState || state2 == mState || state3 == mState || state4 == mState;
     }
 
     /**
      * Returns {@code true} if the Activity is in one of the specified states.
      */
-    boolean isState(ActivityState state1, ActivityState state2, ActivityState state3,
-            ActivityState state4, ActivityState state5) {
+    boolean isState(State state1, State state2, State state3, State state4, State state5) {
         return state1 == mState || state2 == mState || state3 == mState || state4 == mState
                 || state5 == mState;
     }
@@ -4968,8 +5081,8 @@
     /**
      * Returns {@code true} if the Activity is in one of the specified states.
      */
-    boolean isState(ActivityState state1, ActivityState state2, ActivityState state3,
-            ActivityState state4, ActivityState state5, ActivityState state6) {
+    boolean isState(State state1, State state2, State state3, State state4, State state5,
+            State state6) {
         return state1 == mState || state2 == mState || state3 == mState || state4 == mState
                 || state5 == mState || state6 == mState;
     }
@@ -5173,13 +5286,6 @@
                     supportsEnterPipOnTaskSwitch = false;
                     break;
                 case RESUMED:
-                    // If the app is capable of entering PIP, we should try pausing it now
-                    // so it can PIP correctly.
-                    if (deferHidingClient) {
-                        task.startPausingLocked(false /* uiSleeping */,
-                                null /* resuming */, "makeInvisible");
-                        break;
-                    }
                 case INITIALIZING:
                 case PAUSING:
                 case PAUSED:
@@ -5276,7 +5382,8 @@
      */
     private boolean shouldBeResumed(ActivityRecord activeActivity) {
         return shouldMakeActive(activeActivity) && isFocusable()
-                && getTask().getVisibility(activeActivity) == TASK_VISIBILITY_VISIBLE
+                && getTaskFragment().getVisibility(activeActivity)
+                        == TASK_FRAGMENT_VISIBILITY_VISIBLE
                 && canResumeByCompat();
     }
 
@@ -5330,7 +5437,7 @@
         if (!task.hasChild(this)) {
             throw new IllegalStateException("Activity not found in its task");
         }
-        return task.topRunningActivity() == this;
+        return getTaskFragment().topRunningActivity() == this;
     }
 
     void handleAlreadyVisible() {
@@ -5419,16 +5526,17 @@
         ProtoLog.v(WM_DEBUG_STATES, "Activity paused: token=%s, timeout=%b", appToken,
                 timeout);
 
-        if (task != null) {
+        final TaskFragment taskFragment = getTaskFragment();
+        if (taskFragment != null) {
             removePauseTimeout();
 
-            final ActivityRecord pausingActivity = task.getPausingActivity();
+            final ActivityRecord pausingActivity = taskFragment.getPausingActivity();
             if (pausingActivity == this) {
                 ProtoLog.v(WM_DEBUG_STATES, "Moving to PAUSED: %s %s", this,
                         (timeout ? "(due to timeout)" : " (pause complete)"));
                 mAtmService.deferWindowLayout();
                 try {
-                    task.completePauseLocked(true /* resumeNext */, null /* resumingActivity */);
+                    taskFragment.completePause(true /* resumeNext */, null /* resumingActivity */);
                 } finally {
                     mAtmService.continueWindowLayout();
                 }
@@ -6091,9 +6199,9 @@
                 return this;
             }
             // Try to use the one which is closest to top.
-            ActivityRecord r = rootTask.getResumedActivity();
+            ActivityRecord r = rootTask.getTopResumedActivity();
             if (r == null) {
-                r = rootTask.getPausingActivity();
+                r = rootTask.getTopPausingActivity();
             }
             if (r != null) {
                 return r;
@@ -6171,7 +6279,8 @@
             // This would be redundant.
             return false;
         }
-        if (isState(RESUMED) || getRootTask() == null || this == task.getPausingActivity()
+        if (isState(RESUMED) || getRootTask() == null
+                || this == getTaskFragment().getPausingActivity()
                 || !mHaveState || !stopped) {
             // We're not ready for this kind of thing.
             return false;
@@ -6698,8 +6807,7 @@
         }
         final Configuration displayConfig = mDisplayContent.getConfiguration();
         return getDisplayContent().mAppTransition.createThumbnailAspectScaleAnimationLocked(
-                appRect, insets, thumbnailHeader, task, displayConfig.uiMode,
-                displayConfig.orientation);
+                appRect, insets, thumbnailHeader, task, displayConfig.orientation);
     }
 
     @Override
@@ -7124,7 +7232,8 @@
             // If the activity has requested override bounds, the configuration needs to be
             // computed accordingly.
             if (!matchParentBounds()) {
-                task.computeConfigResourceOverrides(resolvedConfig, newParentConfiguration);
+                getTaskFragment().computeConfigResourceOverrides(resolvedConfig,
+                        newParentConfiguration);
             }
         // If activity in fullscreen mode is letterboxed because of fixed orientation then bounds
         // are already calculated in resolveFixedOrientationConfiguration.
@@ -7239,7 +7348,7 @@
         }
 
         // Since bounds has changed, the configuration needs to be computed accordingly.
-        task.computeConfigResourceOverrides(resolvedConfig, newParentConfiguration);
+        getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration);
     }
 
     /**
@@ -7255,8 +7364,53 @@
     }
 
     /**
-     * Computes bounds (letterbox or pillarbox) when the parent doesn't handle the orientation
-     * change and the requested orientation is different from the parent.
+     * In some cases, applying insets to bounds changes the orientation. For example, if a
+     * close-to-square display rotates to portrait to respect a portrait orientation activity, after
+     * insets such as the status and nav bars are applied, the activity may actually have a
+     * landscape orientation. This method checks whether the orientations of the activity window
+     * with and without insets match or if the orientation with insets already matches the
+     * requested orientation. If not, it may be necessary to letterbox the window.
+     * @param parentBounds are the new parent bounds passed down to the activity and should be used
+     *                     to compute the stable bounds.
+     * @param outBounds will store the stable bounds, which are the bounds with insets applied.
+     *                  These should be used to compute letterboxed bounds if orientation is not
+     *                  respected when insets are applied.
+     */
+    private boolean orientationRespectedWithInsets(Rect parentBounds, Rect outBounds) {
+        if (mDisplayContent == null) {
+            return true;
+        }
+        // Only need to make changes if activity sets an orientation
+        final int requestedOrientation = getRequestedConfigurationOrientation();
+        if (requestedOrientation == ORIENTATION_UNDEFINED) {
+            return true;
+        }
+        // Compute parent orientation from bounds
+        final int orientation = parentBounds.height() >= parentBounds.width()
+                ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
+        // Compute orientation from stable parent bounds (= parent bounds with insets applied)
+        final Task task = getTask();
+        task.calculateInsetFrames(mTmpOutNonDecorBounds /* outNonDecorBounds */,
+                outBounds /* outStableBounds */, parentBounds /* bounds */,
+                mDisplayContent.getDisplayInfo());
+        final int orientationWithInsets = outBounds.height() >= outBounds.width()
+                ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
+        // If orientation does not match the orientation with insets applied, then a
+        // display rotation will not be enough to respect orientation. However, even if they do
+        // not match but the orientation with insets applied matches the requested orientation, then
+        // there is no need to modify the bounds because when insets are applied, the activity will
+        // have the desired orientation.
+        return orientation == orientationWithInsets
+                || orientationWithInsets == requestedOrientation;
+    }
+
+    /**
+     * Computes bounds (letterbox or pillarbox) when either:
+     * 1. The parent doesn't handle the orientation change and the requested orientation is
+     *    different from the parent (see {@link DisplayContent#setIgnoreOrientationRequest()}.
+     * 2. The parent handling the orientation is not enough. This occurs when the display rotation
+     *    may not be enough to respect orientation requests (see {@link
+     *    ActivityRecord#orientationRespectedWithInsets}).
      *
      * <p>If letterboxed due to fixed orientation then aspect ratio restrictions are also applied
      * in this method.
@@ -7264,9 +7418,14 @@
     private void resolveFixedOrientationConfiguration(@NonNull Configuration newParentConfig,
             int windowingMode) {
         mLetterboxBoundsForFixedOrientationAndAspectRatio = null;
-        if (handlesOrientationChangeFromDescendant()) {
+        final Rect parentBounds = newParentConfig.windowConfiguration.getBounds();
+        final Rect containerBounds = new Rect(parentBounds);
+        boolean orientationRespectedWithInsets =
+                orientationRespectedWithInsets(parentBounds, containerBounds);
+        if (handlesOrientationChangeFromDescendant() && orientationRespectedWithInsets) {
             // No need to letterbox because of fixed orientation. Display will handle
-            // fixed-orientation requests.
+            // fixed-orientation requests and a display rotation is enough to respect requested
+            // orientation with insets applied.
             return;
         }
         if (WindowConfiguration.inMultiWindowMode(windowingMode) && isResizeable()) {
@@ -7286,7 +7445,8 @@
         // If the activity requires a different orientation (either by override or activityInfo),
         // make it fit the available bounds by scaling down its bounds.
         final int forcedOrientation = getRequestedConfigurationOrientation();
-        if (forcedOrientation == ORIENTATION_UNDEFINED || forcedOrientation == parentOrientation) {
+        if (forcedOrientation == ORIENTATION_UNDEFINED
+                || (forcedOrientation == parentOrientation && orientationRespectedWithInsets)) {
             return;
         }
 
@@ -7298,67 +7458,83 @@
             return;
         }
 
-        final Rect parentBounds = newParentConfig.windowConfiguration.getBounds();
-        final Rect parentAppBounds = newParentConfig.windowConfiguration.getAppBounds();
-        final Rect containingBounds = new Rect();
-        final Rect containingAppBounds = new Rect();
-        // Need to shrink the containing bounds into a square because the parent orientation does
-        // not match the activity requested orientation.
-        if (forcedOrientation == ORIENTATION_LANDSCAPE) {
-            // Shrink height to match width. Position height within app bounds.
-            final int bottom = Math.min(parentAppBounds.top + parentBounds.width(),
-                    parentAppBounds.bottom);
-            containingBounds.set(parentBounds.left, parentAppBounds.top, parentBounds.right,
-                    bottom);
-            containingAppBounds.set(parentAppBounds.left, parentAppBounds.top,
-                    parentAppBounds.right, bottom);
-        } else {
-            // Shrink width to match height. Position width within app bounds.
-            final int right = Math.min(parentAppBounds.left + parentBounds.height(),
-                    parentAppBounds.right);
-            containingBounds.set(parentAppBounds.left, parentBounds.top, right,
-                    parentBounds.bottom);
-            containingAppBounds.set(parentAppBounds.left, parentAppBounds.top, right,
-                    parentAppBounds.bottom);
-        }
-
-        Rect mTmpFullBounds = new Rect(resolvedBounds);
-        resolvedBounds.set(containingBounds);
+        // TODO(b/182268157) merge aspect ratio logic here and in
+        // {@link ActivityRecord#applyAspectRatio}
+        // if no aspect ratio constraints are provided, parent aspect ratio is used
+        float aspectRatio = computeAspectRatio(parentBounds);
 
         // Override from config_fixedOrientationLetterboxAspectRatio or via ADB with
         // set-fixed-orientation-letterbox-aspect-ratio.
         final float letterboxAspectRatioOverride =
                 mWmService.mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio();
-        final float desiredAspectRatio =
-                letterboxAspectRatioOverride > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO
-                        ? letterboxAspectRatioOverride : computeAspectRatio(parentBounds);
-        // Apply aspect ratio to resolved bounds
-        mIsAspectRatioApplied = applyAspectRatio(resolvedBounds, containingAppBounds,
-                containingBounds, desiredAspectRatio, true);
+        aspectRatio = letterboxAspectRatioOverride > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO
+                ? letterboxAspectRatioOverride : aspectRatio;
 
-        // Vertically center if orientation is landscape. Bounds will later be horizontally centered
-        // in {@link updateResolvedBoundsHorizontalPosition()} regardless of orientation.
+        // Adjust the fixed orientation letterbox bounds to fit the app request aspect ratio in
+        // order to use the extra available space.
+        final float maxAspectRatio = info.getMaxAspectRatio();
+        final float minAspectRatio = info.getMinAspectRatio();
+        if (aspectRatio > maxAspectRatio && maxAspectRatio != 0) {
+            aspectRatio = maxAspectRatio;
+        } else if (aspectRatio < minAspectRatio) {
+            aspectRatio = minAspectRatio;
+        }
+
+        // Store the current bounds to be able to revert to size compat mode values below if needed.
+        final Rect prevResolvedBounds = new Rect(resolvedBounds);
+
+        // Compute other dimension based on aspect ratio. Use bounds intersected with insets, stored
+        // in containerBounds after calling {@link ActivityRecord#orientationRespectedWithInsets()},
+        // to ensure that aspect ratio is respected after insets are applied.
+        int activityWidth;
+        int activityHeight;
         if (forcedOrientation == ORIENTATION_LANDSCAPE) {
-            final int offsetY = parentBounds.centerY() - resolvedBounds.centerY();
-            resolvedBounds.offset(0, offsetY);
+            activityWidth = parentBounds.width();
+            // Compute height from stable bounds width to ensure orientation respected after insets.
+            activityHeight = (int) Math.rint(containerBounds.width() / aspectRatio);
+            // Landscape is defined as width > height. To ensure activity is landscape when aspect
+            // ratio is close to 1, reduce the height by one pixel.
+            if (activityWidth == activityHeight) {
+                activityHeight -= 1;
+            }
+            // Center vertically within stable bounds in landscape to ensure insets do not trim
+            // height.
+            final int top = containerBounds.centerY() - activityHeight / 2;
+            resolvedBounds.set(parentBounds.left, top, parentBounds.right, top + activityHeight);
+        } else {
+            activityHeight = parentBounds.height();
+            // Compute width from stable bounds height to ensure orientation respected after insets.
+            activityWidth = (int) Math.rint(containerBounds.height() / aspectRatio);
+            // Center horizontally in portrait. For now, align to left and allow
+            // {@link ActivityRecord#updateResolvedBoundsHorizontalPosition()} to center
+            // horizontally. Exclude left insets from parent to ensure cutout does not trim width.
+            final Rect parentAppBounds = newParentConfig.windowConfiguration.getAppBounds();
+            resolvedBounds.set(parentAppBounds.left, parentBounds.top,
+                    parentAppBounds.left + activityWidth, parentBounds.bottom);
         }
 
         if (mCompatDisplayInsets != null) {
             mCompatDisplayInsets.getBoundsByRotation(
                     mTmpBounds, newParentConfig.windowConfiguration.getRotation());
-            if (resolvedBounds.width() != mTmpBounds.width()
-                    || resolvedBounds.height() != mTmpBounds.height()) {
+            // Insets may differ between different rotations, for example in the case of a display
+            // cutout. To ensure consistent bounds across rotations, compare the activity dimensions
+            // minus insets from the rotation the compat bounds were computed in.
+            Task.intersectWithInsetsIfFits(mTmpBounds, parentBounds,
+                    mCompatDisplayInsets.mStableInsets[mCompatDisplayInsets.mOriginalRotation]);
+            if (activityWidth != mTmpBounds.width()
+                    || activityHeight != mTmpBounds.height()) {
                 // The app shouldn't be resized, we only do fixed orientation letterboxing if the
                 // compat bounds are also from the same fixed orientation letterbox. Otherwise,
                 // clear the fixed orientation bounds to show app in size compat mode.
-                resolvedBounds.set(mTmpFullBounds);
+                resolvedBounds.set(prevResolvedBounds);
                 return;
             }
         }
 
         // Calculate app bounds using fixed orientation bounds because they will be needed later
         // for comparison with size compat app bounds in {@link resolveSizeCompatModeConfiguration}.
-        task.computeConfigResourceOverrides(getResolvedOverrideConfiguration(), newParentConfig);
+        getTaskFragment().computeConfigResourceOverrides(getResolvedOverrideConfiguration(),
+                newParentConfig);
         mLetterboxBoundsForFixedOrientationAndAspectRatio = new Rect(resolvedBounds);
     }
 
@@ -7382,11 +7558,13 @@
         // then they should be aligned later in #updateResolvedBoundsHorizontalPosition().
         if (!mTmpBounds.isEmpty()) {
             resolvedBounds.set(mTmpBounds);
+            // Exclude the horizontal decor area.
+            resolvedBounds.left = parentAppBounds.left;
         }
         if (!resolvedBounds.isEmpty() && !resolvedBounds.equals(parentBounds)) {
             // Compute the configuration based on the resolved bounds. If aspect ratio doesn't
             // restrict, the bounds should be the requested override bounds.
-            task.computeConfigResourceOverrides(resolvedConfig, newParentConfiguration,
+            getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration,
                     getFixedRotationTransformDisplayInfo());
         }
     }
@@ -7442,14 +7620,21 @@
             mIsAspectRatioApplied =
                     applyAspectRatio(resolvedBounds, containingAppBounds, containingBounds);
         }
+        // If the bounds are restricted by fixed aspect ratio, the resolved bounds should be put in
+        // the container app bounds. Otherwise the entire container bounds are available.
+        final boolean fillContainer = resolvedBounds.equals(containingBounds);
+        if (!fillContainer) {
+            // The horizontal position should not cover insets.
+            resolvedBounds.left = containingAppBounds.left;
+        }
 
         // Use resolvedBounds to compute other override configurations such as appBounds. The bounds
         // are calculated in compat container space. The actual position on screen will be applied
         // later, so the calculation is simpler that doesn't need to involve offset from parent.
-        task.computeConfigResourceOverrides(resolvedConfig, newParentConfiguration,
+        getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration,
                 mCompatDisplayInsets);
         // Use current screen layout as source because the size of app is independent to parent.
-        resolvedConfig.screenLayout = Task.computeScreenLayoutOverride(
+        resolvedConfig.screenLayout = TaskFragment.computeScreenLayoutOverride(
                 getConfiguration().screenLayout, resolvedConfig.screenWidthDp,
                 resolvedConfig.screenHeightDp);
 
@@ -7508,7 +7693,6 @@
         // Align to top of parent (bounds) - this is a UX choice and exclude the horizontal decor
         // if needed. Horizontal position is adjusted in updateResolvedBoundsHorizontalPosition.
         // Above coordinates are in "@" space, now place "*" and "#" to screen space.
-        final boolean fillContainer = resolvedBounds.equals(containingBounds);
         final int screenPosX = fillContainer ? containerBounds.left : containerAppBounds.left;
         final int screenPosY = containerBounds.top;
         if (screenPosX != 0 || screenPosY != 0) {
@@ -7593,6 +7777,10 @@
         if (getUid() == SYSTEM_UID) {
             return false;
         }
+        // Do not sandbox to activity window bounds if the feature is disabled.
+        if (mDisplayContent != null && !mDisplayContent.sandboxDisplayApis()) {
+            return false;
+        }
         // Never apply sandboxing to an app that should be explicitly excluded from the config.
         if (info != null && info.neverSandboxDisplayApis()) {
             return false;
@@ -7745,12 +7933,6 @@
         return true;
     }
 
-    private boolean applyAspectRatio(Rect outBounds, Rect containingAppBounds,
-            Rect containingBounds) {
-        return applyAspectRatio(outBounds, containingAppBounds, containingBounds,
-                0 /* desiredAspectRatio */, false /* fixedOrientationLetterboxed */);
-    }
-
     /**
      * Applies aspect ratio restrictions to outBounds. If no restrictions, then no change is
      * made to outBounds.
@@ -7759,19 +7941,17 @@
      */
     // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer.
     private boolean applyAspectRatio(Rect outBounds, Rect containingAppBounds,
-            Rect containingBounds, float desiredAspectRatio, boolean fixedOrientationLetterboxed) {
+            Rect containingBounds) {
         final float maxAspectRatio = info.getMaxAspectRatio();
         final Task rootTask = getRootTask();
         final float minAspectRatio = info.getMinAspectRatio();
 
         if (task == null || rootTask == null
-                || (inMultiWindowMode() && !shouldCreateCompatDisplayInsets()
-                    && !fixedOrientationLetterboxed)
-                || (maxAspectRatio < 1 && minAspectRatio < 1 && desiredAspectRatio < 1)
+                || (inMultiWindowMode() && !shouldCreateCompatDisplayInsets())
+                || (maxAspectRatio == 0 && minAspectRatio == 0)
                 || isInVrUiMode(getConfiguration())) {
-            // We don't enforce aspect ratio if the activity task is in multiwindow unless it is in
-            // size-compat mode or is letterboxed from fixed orientation. We also don't set it if we
-            // are in VR mode.
+            // We don't enforce aspect ratio if the activity task is in multiwindow unless it
+            // is in size-compat mode. We also don't set it if we are in VR mode.
             return false;
         }
 
@@ -7779,30 +7959,20 @@
         final int containingAppHeight = containingAppBounds.height();
         final float containingRatio = computeAspectRatio(containingAppBounds);
 
-        if (desiredAspectRatio < 1) {
-            desiredAspectRatio = containingRatio;
-        }
-
-        if (maxAspectRatio >= 1 && desiredAspectRatio > maxAspectRatio) {
-            desiredAspectRatio = maxAspectRatio;
-        } else if (minAspectRatio >= 1 && desiredAspectRatio < minAspectRatio) {
-            desiredAspectRatio = minAspectRatio;
-        }
-
         int activityWidth = containingAppWidth;
         int activityHeight = containingAppHeight;
 
-        if (containingRatio > desiredAspectRatio) {
+        if (containingRatio > maxAspectRatio && maxAspectRatio != 0) {
             if (containingAppWidth < containingAppHeight) {
                 // Width is the shorter side, so we use that to figure-out what the max. height
                 // should be given the aspect ratio.
-                activityHeight = (int) ((activityWidth * desiredAspectRatio) + 0.5f);
+                activityHeight = (int) ((activityWidth * maxAspectRatio) + 0.5f);
             } else {
                 // Height is the shorter side, so we use that to figure-out what the max. width
                 // should be given the aspect ratio.
-                activityWidth = (int) ((activityHeight * desiredAspectRatio) + 0.5f);
+                activityWidth = (int) ((activityHeight * maxAspectRatio) + 0.5f);
             }
-        } else if (containingRatio < desiredAspectRatio) {
+        } else if (containingRatio < minAspectRatio) {
             boolean adjustWidth;
             switch (getRequestedConfigurationOrientation()) {
                 case ORIENTATION_LANDSCAPE:
@@ -7830,9 +8000,9 @@
                     break;
             }
             if (adjustWidth) {
-                activityWidth = (int) ((activityHeight / desiredAspectRatio) + 0.5f);
+                activityWidth = (int) ((activityHeight / minAspectRatio) + 0.5f);
             } else {
-                activityHeight = (int) ((activityWidth / desiredAspectRatio) + 0.5f);
+                activityHeight = (int) ((activityWidth / minAspectRatio) + 0.5f);
             }
         }
 
@@ -7856,13 +8026,6 @@
         }
         outBounds.set(containingBounds.left, containingBounds.top, right, bottom);
 
-        // If the bounds are restricted by fixed aspect ratio, then out bounds should be put in the
-        // container app bounds. Otherwise the entire container bounds are available.
-        if (!outBounds.equals(containingBounds)) {
-            // The horizontal position should not cover insets.
-            outBounds.left = containingAppBounds.left;
-        }
-
         return true;
     }
 
@@ -8618,6 +8781,8 @@
      * compatibility mode activity compute the configuration without relying on its current display.
      */
     static class CompatDisplayInsets {
+        /** The original rotation the compat insets were computed in */
+        final @Rotation int mOriginalRotation;
         /** The container width on rotation 0. */
         private final int mWidth;
         /** The container height on rotation 0. */
@@ -8644,6 +8809,7 @@
         /** Constructs the environment to simulate the bounds behavior of the given container. */
         CompatDisplayInsets(DisplayContent display, ActivityRecord container,
                 @Nullable Rect fixedOrientationBounds) {
+            mOriginalRotation = display.getRotation();
             mIsFloating = container.getWindowConfiguration().tasksAreFloating();
             if (mIsFloating) {
                 final Rect containerBounds = container.getWindowConfiguration().getBounds();
@@ -8790,7 +8956,8 @@
                 outAppBounds.offset(insets.left, insets.top);
             } else if (rotation != ROTATION_UNDEFINED) {
                 // Ensure the app bounds won't overlap with insets.
-                Task.intersectWithInsetsIfFits(outAppBounds, outBounds, mNonDecorInsets[rotation]);
+                TaskFragment.intersectWithInsetsIfFits(outAppBounds, outBounds,
+                        mNonDecorInsets[rotation]);
             }
         }
     }
@@ -8859,6 +9026,14 @@
         return false;
     }
 
+    @Override
+    void finishSync(Transaction outMergedTransaction, boolean cancel) {
+        // This override is just for getting metrics. allFinished needs to be checked before
+        // finish because finish resets all the states.
+        mLastAllReadyAtSync = allSyncFinished();
+        super.finishSync(outMergedTransaction, cancel);
+    }
+
     static class Builder {
         private final ActivityTaskManagerService mAtmService;
         private WindowProcessController mCallerApp;
diff --git a/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java b/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java
index 8540fa7..30c7b23 100644
--- a/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java
+++ b/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java
@@ -16,10 +16,10 @@
 
 package com.android.server.wm;
 
+import static com.android.server.wm.ActivityRecord.State.PAUSING;
+import static com.android.server.wm.ActivityRecord.State.RESUMED;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CLEANUP;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
-import static com.android.server.wm.Task.ActivityState.PAUSING;
-import static com.android.server.wm.Task.ActivityState.RESUMED;
 
 import android.util.ArraySet;
 import android.util.Slog;
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index b6f2f24..d08d285 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -27,6 +27,7 @@
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityOptions;
 import android.app.IApplicationThread;
@@ -38,6 +39,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.IBinder;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -488,6 +490,16 @@
         return START_SUCCESS;
     }
 
+    void startActivityInTaskFragment(@NonNull TaskFragment taskFragment,
+            @NonNull Intent activityIntent, @Nullable Bundle activityOptions) {
+        obtainStarter(activityIntent, "startActivityInTaskFragment")
+                .setActivityOptions(activityOptions)
+                .setInTaskFragment(taskFragment)
+                .setCallingUid(Binder.getCallingUid())
+                .setCallingPid(Binder.getCallingPid())
+                .execute();
+    }
+
     void registerRemoteAnimationForNextActivityStart(String packageName,
             RemoteAnimationAdapter adapter) {
         mPendingRemoteAnimationRegistry.addPendingAnimation(packageName, adapter);
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index e2ef82b..10c1f6b 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -28,6 +28,7 @@
 import static android.app.ActivityManager.START_SUCCESS;
 import static android.app.ActivityManager.START_TASK_TO_FRONT;
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -58,6 +59,7 @@
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
+import static com.android.server.wm.ActivityRecord.State.RESUMED;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ACTIVITY_STARTS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RESULTS;
@@ -74,7 +76,6 @@
 import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_BOUNDS;
 import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_DISPLAY;
-import static com.android.server.wm.Task.ActivityState.RESUMED;
 import static com.android.server.wm.Task.REPARENT_MOVE_ROOT_TASK_TO_FRONT;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 
@@ -176,6 +177,7 @@
     private int mPreferredWindowingMode;
 
     private Task mInTask;
+    private TaskFragment mInTaskFragment;
     @VisibleForTesting
     boolean mAddingToTask;
     private Task mReuseTask;
@@ -341,6 +343,7 @@
         boolean avoidMoveToFront;
         ActivityRecord[] outActivity;
         Task inTask;
+        TaskFragment inTaskFragment;
         String reason;
         ProfilerInfo profilerInfo;
         Configuration globalConfig;
@@ -391,6 +394,7 @@
             componentSpecified = false;
             outActivity = null;
             inTask = null;
+            inTaskFragment = null;
             reason = null;
             profilerInfo = null;
             globalConfig = null;
@@ -406,7 +410,7 @@
         /**
          * Adopts all values from passed in request.
          */
-        void set(Request request) {
+        void set(@NonNull Request request) {
             caller = request.caller;
             intent = request.intent;
             intentGrants = request.intentGrants;
@@ -431,6 +435,7 @@
             componentSpecified = request.componentSpecified;
             outActivity = request.outActivity;
             inTask = request.inTask;
+            inTaskFragment = request.inTaskFragment;
             reason = request.reason;
             profilerInfo = request.profilerInfo;
             globalConfig = request.globalConfig;
@@ -573,6 +578,7 @@
         mPreferredWindowingMode = starter.mPreferredWindowingMode;
 
         mInTask = starter.mInTask;
+        mInTaskFragment = starter.mInTaskFragment;
         mAddingToTask = starter.mAddingToTask;
         mReuseTask = starter.mReuseTask;
 
@@ -834,6 +840,7 @@
         final int startFlags = request.startFlags;
         final SafeActivityOptions options = request.activityOptions;
         Task inTask = request.inTask;
+        mInTaskFragment = request.inTaskFragment;
 
         int err = ActivityManager.START_SUCCESS;
         // Pull the optional Ephemeral Installer-only bundle out of the options early.
@@ -1549,6 +1556,11 @@
             newTransition.setRemoteTransition(remoteTransition);
         }
         mService.getTransitionController().collect(r);
+        // TODO(b/188669821): Remove when navbar reparenting moves to shell
+        if (r.getActivityType() == ACTIVITY_TYPE_HOME && r.getOptions() != null
+                && r.getOptions().getTransientLaunch()) {
+            mService.getTransitionController().setIsLegacyRecents();
+        }
         try {
             mService.deferWindowLayout();
             Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "startActivityInner");
@@ -1580,7 +1592,8 @@
                         statusBar.collapsePanels();
                     }
                 }
-                if (result == START_SUCCESS || result == START_TASK_TO_FRONT) {
+                final boolean started = result == START_SUCCESS || result == START_TASK_TO_FRONT;
+                if (started) {
                     // The activity is started new rather than just brought forward, so record
                     // it as an existence change.
                     mService.getTransitionController().collectExistenceChange(r);
@@ -1588,7 +1601,7 @@
                 if (newTransition != null) {
                     mService.getTransitionController().requestStartTransition(newTransition,
                             mTargetTask, remoteTransition);
-                } else {
+                } else if (started) {
                     // Make the collecting transition wait until this request is ready.
                     mService.getTransitionController().setReady(false);
                 }
@@ -1650,7 +1663,6 @@
      *
      * Note: This method should only be called from {@link #startActivityUnchecked}.
      */
-
     // TODO(b/152429287): Make it easier to exercise code paths through startActivityInner
     @VisibleForTesting
     int startActivityInner(final ActivityRecord r, ActivityRecord sourceRecord,
@@ -1757,7 +1769,7 @@
         mStartActivity.logStartActivity(
                 EventLogTags.WM_CREATE_ACTIVITY, mStartActivity.getTask());
 
-        mTargetRootTask.mLastPausedActivity = null;
+        mStartActivity.getTaskFragment().clearLastPausedActivity();
 
         mRootWindowContainer.startPowerModeLaunchIfNeeded(
                 false /* forceSend */, mStartActivity);
@@ -2034,7 +2046,7 @@
         }
 
         // For paranoia, make sure we have correctly resumed the top activity.
-        topRootTask.mLastPausedActivity = null;
+        top.getTaskFragment().clearLastPausedActivity();
         if (mDoResume) {
             mRootWindowContainer.resumeFocusedTasksTopActivities();
         }
@@ -2130,7 +2142,7 @@
                 task.moveActivityToFrontLocked(act);
                 act.updateOptionsLocked(mOptions);
                 deliverNewIntent(act, intentGrants);
-                mTargetRootTask.mLastPausedActivity = null;
+                act.getTaskFragment().clearLastPausedActivity();
             } else {
                 mAddingToTask = true;
             }
@@ -2198,6 +2210,7 @@
         mPreferredWindowingMode = WINDOWING_MODE_UNDEFINED;
 
         mInTask = null;
+        mInTaskFragment = null;
         mAddingToTask = false;
         mReuseTask = null;
 
@@ -2558,7 +2571,7 @@
      */
     private void setTargetRootTaskIfNeeded(ActivityRecord intentActivity) {
         mTargetRootTask = intentActivity.getRootTask();
-        mTargetRootTask.mLastPausedActivity = null;
+        intentActivity.getTaskFragment().clearLastPausedActivity();
         Task intentTask = intentActivity.getTask();
         // If the target task is not in the front, then we need to bring it to the front...
         // except...  well, with SINGLE_TASK_LAUNCH it's not entirely clear. We'd like to have
@@ -2685,11 +2698,23 @@
         mIntentDelivered = true;
     }
 
-    private void addOrReparentStartingActivity(Task parent, String reason) {
-        if (mStartActivity.getTask() == null || mStartActivity.getTask() == parent) {
-            parent.addChild(mStartActivity);
+    private void addOrReparentStartingActivity(@NonNull Task task, String reason) {
+        TaskFragment newParent = task;
+        if (mInTaskFragment != null) {
+            // mInTaskFragment is created and added to the leaf task by task fragment organizer's
+            // request. If the task was resolved and different than mInTaskFragment, reparent the
+            // task to mInTaskFragment for embedding.
+            if (mInTaskFragment.getTask() != task) {
+                task.reparent(mInTaskFragment, POSITION_TOP);
+            } else {
+                newParent = mInTaskFragment;
+            }
+        }
+        if (mStartActivity.getTaskFragment() == null
+                || mStartActivity.getTaskFragment() == newParent) {
+            newParent.addChild(mStartActivity, POSITION_TOP);
         } else {
-            mStartActivity.reparent(parent, parent.getChildCount() /* top */, reason);
+            mStartActivity.reparent(newParent, newParent.getChildCount() /* top */, reason);
         }
     }
 
@@ -2921,6 +2946,11 @@
         return this;
     }
 
+    ActivityStarter setInTaskFragment(TaskFragment taskFragment) {
+        mRequest.inTaskFragment = taskFragment;
+        return this;
+    }
+
     ActivityStarter setWaitResult(WaitResult result) {
         mRequest.waitResult = result;
         return this;
@@ -3004,5 +3034,7 @@
         pw.print(mDoResume);
         pw.print(" mAddingToTask=");
         pw.println(mAddingToTask);
+        pw.print(" mInTaskFragment=");
+        pw.println(mInTaskFragment);
     }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 090a01a..b0d0f16 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -1222,8 +1222,8 @@
             // If this is coming from the currently resumed activity, it is
             // effectively saying that app switches are allowed at this point.
             final Task topFocusedRootTask = getTopDisplayFocusedRootTask();
-            if (topFocusedRootTask != null && topFocusedRootTask.getResumedActivity() != null
-                    && topFocusedRootTask.getResumedActivity().info.applicationInfo.uid
+            if (topFocusedRootTask != null && topFocusedRootTask.getTopResumedActivity() != null
+                    && topFocusedRootTask.getTopResumedActivity().info.applicationInfo.uid
                     == Binder.getCallingUid()) {
                 mAppSwitchesAllowed = true;
             }
@@ -1881,25 +1881,42 @@
     @Override
     public void setFocusedTask(int taskId) {
         enforceTaskPermission("setFocusedTask()");
-        ProtoLog.d(WM_DEBUG_FOCUS, "setFocusedTask: taskId=%d", taskId);
         final long callingId = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
-                final Task task = mRootWindowContainer.anyTaskForId(taskId,
-                        MATCH_ATTACHED_TASK_ONLY);
-                if (task == null) {
-                    return;
-                }
-                final ActivityRecord r = task.topRunningActivityLocked();
-                if (r != null && r.moveFocusableActivityToTop("setFocusedTask")) {
-                    mRootWindowContainer.resumeFocusedTasksTopActivities();
-                }
+                setFocusedTask(taskId, null /* touchedActivity */);
             }
         } finally {
             Binder.restoreCallingIdentity(callingId);
         }
     }
 
+    void setFocusedTask(int taskId, ActivityRecord touchedActivity) {
+        ProtoLog.d(WM_DEBUG_FOCUS, "setFocusedTask: taskId=%d touchedActivity=%s", taskId,
+                touchedActivity);
+        final Task task = mRootWindowContainer.anyTaskForId(taskId, MATCH_ATTACHED_TASK_ONLY);
+        if (task == null) {
+            return;
+        }
+        final ActivityRecord r = task.topRunningActivityLocked();
+        if (r == null) {
+            return;
+        }
+
+        if (r.moveFocusableActivityToTop("setFocusedTask")) {
+            mRootWindowContainer.resumeFocusedTasksTopActivities();
+        } else if (touchedActivity != null && touchedActivity != r
+                && touchedActivity.getTask() == r.getTask()
+                && touchedActivity.getTaskFragment() != r.getTaskFragment()) {
+            // Set the focused app directly since the focused window is not on the
+            // top-most TaskFragment of the top-most Task
+            final DisplayContent displayContent = touchedActivity.getDisplayContent();
+            displayContent.setFocusedApp(touchedActivity);
+            mWindowManager.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL,
+                    true /* updateInputWindows */);
+        }
+    }
+
     @Override
     public boolean removeTask(int taskId) {
         mAmInternal.enforceCallingPermission(REMOVE_TASKS, "removeTask()");
@@ -3769,6 +3786,20 @@
         }
     }
 
+    @Override
+    public void detachNavigationBarFromApp(@NonNull IBinder transition) {
+        mAmInternal.enforceCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS,
+                "detachNavigationBarFromApp");
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mGlobalLock) {
+                getTransitionController().legacyDetachNavigationBarFromApp(transition);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
     void dumpLastANRLocked(PrintWriter pw) {
         pw.println("ACTIVITY MANAGER LAST ANR (dumpsys activity lastanr)");
         if (mLastANRState == null) {
@@ -5055,6 +5086,34 @@
         process.registerDisplayAreaConfigurationListener(imeContainer);
     }
 
+    @Override
+    public void setRunningRemoteTransitionDelegate(IApplicationThread caller) {
+        mAmInternal.enforceCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS,
+                "setRunningRemoteTransition");
+        final int callingPid = Binder.getCallingPid();
+        final int callingUid = Binder.getCallingUid();
+        synchronized (mGlobalLock) {
+            // Also only allow a process which is already runningRemoteAnimation to mark another
+            // process.
+            final WindowProcessController callingProc = getProcessController(callingPid,
+                    callingUid);
+            if (callingProc == null || !callingProc.isRunningRemoteTransition()) {
+                final String msg = "Can't call setRunningRemoteTransition from a process (pid="
+                        + callingPid + " uid=" + callingUid + ") which isn't itself running a "
+                        + "remote transition.";
+                Slog.e(TAG, msg);
+                throw new SecurityException(msg);
+            }
+            final WindowProcessController wpc = getProcessController(caller);
+            if (wpc == null) {
+                Slog.w(TAG, "Unable to find process for application " + caller);
+                return;
+            }
+            wpc.setRunningRemoteAnimation(true /* running */);
+            callingProc.addRemoteAnimationDelegate(wpc);
+        }
+    }
+
     final class H extends Handler {
         static final int REPORT_TIME_TRACKER_MSG = 1;
         static final int UPDATE_PROCESS_ANIMATING_STATE = 2;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index d3d1c1c..74efe0c 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -48,6 +48,9 @@
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
+import static com.android.server.wm.ActivityRecord.State.PAUSED;
+import static com.android.server.wm.ActivityRecord.State.PAUSING;
+import static com.android.server.wm.ActivityRecord.State.RESTARTING_PROCESS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ALL;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CLEANUP;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_IDLE;
@@ -72,8 +75,6 @@
 import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_RECENT_TASKS_AND_RESTORE;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
-import static com.android.server.wm.Task.ActivityState.PAUSED;
-import static com.android.server.wm.Task.ActivityState.PAUSING;
 import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_PINNED_TASK;
 import static com.android.server.wm.Task.REPARENT_KEEP_ROOT_TASK_AT_FRONT;
 import static com.android.server.wm.Task.TAG_CLEANUP;
@@ -136,7 +137,6 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.content.ReferrerIntent;
-import com.android.internal.os.TransferPipe;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.function.pooled.PooledConsumer;
@@ -146,7 +146,6 @@
 import com.android.server.wm.ActivityMetricsLogger.LaunchingState;
 
 import java.io.FileDescriptor;
-import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
@@ -348,6 +347,12 @@
      */
     private int mVisibilityTransactionDepth;
 
+    /**
+     * Whether to the visibility updates that started from {@code RootWindowContainer} should be
+     * deferred.
+     */
+    private boolean mDeferRootVisibilityUpdate;
+
     private ActivityMetricsLogger mActivityMetricsLogger;
 
     /** Check if placing task or activity on specified display is allowed. */
@@ -1968,76 +1973,14 @@
     static boolean dumpHistoryList(FileDescriptor fd, PrintWriter pw, List<ActivityRecord> list,
             String prefix, String label, boolean complete, boolean brief, boolean client,
             String dumpPackage, boolean needNL, Runnable header, Task lastTask) {
-        String innerPrefix = null;
-        String[] args = null;
         boolean printed = false;
-        for (int i=list.size()-1; i>=0; i--) {
+        for (int i = list.size() - 1; i >= 0; i--) {
             final ActivityRecord r = list.get(i);
-            if (dumpPackage != null && !dumpPackage.equals(r.packageName)) {
-                continue;
-            }
-            if (innerPrefix == null) {
-                innerPrefix = prefix + "      ";
-                args = new String[0];
-            }
-            printed = true;
-            final boolean full = !brief && (complete || !r.isInHistory());
-            if (needNL) {
-                pw.println("");
-                needNL = false;
-            }
-            if (header != null) {
-                header.run();
-                header = null;
-            }
-            if (lastTask != r.getTask()) {
-                lastTask = r.getTask();
-                pw.print(prefix);
-                pw.print(full ? "* " : "  ");
-                pw.println(lastTask);
-                if (full) {
-                    lastTask.dump(pw, prefix + "  ");
-                } else if (complete) {
-                    // Complete + brief == give a summary.  Isn't that obvious?!?
-                    if (lastTask.intent != null) {
-                        pw.print(prefix); pw.print("  ");
-                                pw.println(lastTask.intent.toInsecureString());
-                    }
-                }
-            }
-            pw.print(prefix); pw.print(full ? "  * " : "    "); pw.print(label);
-            pw.print(" #"); pw.print(i); pw.print(": ");
-            pw.println(r);
-            if (full) {
-                r.dump(pw, innerPrefix, true /* dumpAll */);
-            } else if (complete) {
-                // Complete + brief == give a summary.  Isn't that obvious?!?
-                pw.print(innerPrefix); pw.println(r.intent.toInsecureString());
-                if (r.app != null) {
-                    pw.print(innerPrefix); pw.println(r.app);
-                }
-            }
-            if (client && r.attachedToProcess()) {
-                // flush anything that is already in the PrintWriter since the thread is going
-                // to write to the file descriptor directly
-                pw.flush();
-                try {
-                    TransferPipe tp = new TransferPipe();
-                    try {
-                        r.app.getThread().dumpActivity(
-                                tp.getWriteFd(), r.appToken, innerPrefix, args);
-                        // Short timeout, since blocking here can deadlock with the application.
-                        tp.go(fd, 2000);
-                    } finally {
-                        tp.kill();
-                    }
-                } catch (IOException e) {
-                    pw.println(innerPrefix + "Failure while dumping the activity: " + e);
-                } catch (RemoteException e) {
-                    pw.println(innerPrefix + "Got a RemoteException while dumping the activity");
-                }
-                needNL = true;
-            }
+            ActivityRecord.dumpActivity(fd, pw, i, r, prefix, label, complete, brief,
+                    client, dumpPackage, needNL, header, lastTask);
+            lastTask = r.getTask();
+            header = null;
+            needNL = client && r.attachedToProcess();
         }
         return printed;
     }
@@ -2064,7 +2007,7 @@
     void updateTopResumedActivityIfNeeded() {
         final ActivityRecord prevTopActivity = mTopResumedActivity;
         final Task topRootTask = mRootWindowContainer.getTopDisplayFocusedRootTask();
-        if (topRootTask == null || topRootTask.getResumedActivity() == prevTopActivity) {
+        if (topRootTask == null || topRootTask.getTopResumedActivity() == prevTopActivity) {
             if (mService.isSleepingLocked()) {
                 // There won't be a next resumed activity. The top process should still be updated
                 // according to the current top focused activity.
@@ -2086,7 +2029,7 @@
         }
 
         // Update the current top activity.
-        mTopResumedActivity = topRootTask.getResumedActivity();
+        mTopResumedActivity = topRootTask.getTopResumedActivity();
         scheduleTopResumedActivityStateIfNeeded();
 
         mService.updateTopApp(mTopResumedActivity);
@@ -2326,6 +2269,14 @@
         return mVisibilityTransactionDepth > 0;
     }
 
+    void setDeferRootVisibilityUpdate(boolean deferUpdate) {
+        mDeferRootVisibilityUpdate = deferUpdate;
+    }
+
+    boolean isRootVisibilityUpdateDeferred() {
+        return mDeferRootVisibilityUpdate;
+    }
+
     /**
      * Called when the state or visibility of an attached activity is changed.
      *
@@ -2393,8 +2344,7 @@
                     String processName = null;
                     int uid = 0;
                     synchronized (mService.mGlobalLock) {
-                        if (r.attachedToProcess()
-                                && r.isState(Task.ActivityState.RESTARTING_PROCESS)) {
+                        if (r.attachedToProcess() && r.isState(RESTARTING_PROCESS)) {
                             processName = r.app.mName;
                             uid = r.app.mUid;
                         }
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index c1b287f..ac687dc 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -78,10 +78,7 @@
 import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation;
 import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenEnterAnimation;
 import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenExitAnimation;
-import static com.android.internal.policy.TransitionAnimation.THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN;
-import static com.android.internal.policy.TransitionAnimation.THUMBNAIL_TRANSITION_ENTER_SCALE_UP;
-import static com.android.internal.policy.TransitionAnimation.THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN;
-import static com.android.internal.policy.TransitionAnimation.THUMBNAIL_TRANSITION_EXIT_SCALE_UP;
+import static com.android.internal.policy.TransitionAnimation.prepareThumbnailAnimationWithDuration;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM;
 import static com.android.server.wm.AppTransitionProto.APP_TRANSITION_STATE;
@@ -102,12 +99,7 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.TypedArray;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Picture;
 import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
 import android.hardware.HardwareBuffer;
 import android.os.Binder;
 import android.os.Debug;
@@ -132,7 +124,6 @@
 import android.view.animation.Animation;
 import android.view.animation.AnimationSet;
 import android.view.animation.AnimationUtils;
-import android.view.animation.ClipRectAnimation;
 import android.view.animation.Interpolator;
 import android.view.animation.PathInterpolator;
 import android.view.animation.ScaleAnimation;
@@ -641,24 +632,6 @@
     /**
      * Prepares the specified animation with a standard duration, interpolator, etc.
      */
-    Animation prepareThumbnailAnimationWithDuration(@Nullable Animation a, int appWidth,
-            int appHeight, long duration, Interpolator interpolator) {
-        if (a != null) {
-            if (duration > 0) {
-                a.setDuration(duration);
-            }
-            a.setFillAfter(true);
-            if (interpolator != null) {
-                a.setInterpolator(interpolator);
-            }
-            a.initialize(appWidth, appHeight, appWidth, appHeight);
-        }
-        return a;
-    }
-
-    /**
-     * Prepares the specified animation with a standard duration, interpolator, etc.
-     */
     Animation prepareThumbnailAnimation(Animation a, int appWidth, int appHeight, int transit) {
         // Pick the desired duration.  If this is an inter-activity transition,
         // it  is the standard duration for that.  Otherwise we use the longer
@@ -678,56 +651,16 @@
     }
 
     /**
-     * Return the current thumbnail transition state.
-     */
-    int getThumbnailTransitionState(boolean enter) {
-        if (enter) {
-            if (mNextAppTransitionScaleUp) {
-                return THUMBNAIL_TRANSITION_ENTER_SCALE_UP;
-            } else {
-                return THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN;
-            }
-        } else {
-            if (mNextAppTransitionScaleUp) {
-                return THUMBNAIL_TRANSITION_EXIT_SCALE_UP;
-            } else {
-                return THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN;
-            }
-        }
-    }
-
-    /**
      * Creates an overlay with a background color and a thumbnail for the cross profile apps
      * animation.
      */
     HardwareBuffer createCrossProfileAppsThumbnail(
             @DrawableRes int thumbnailDrawableRes, Rect frame) {
-        final int width = frame.width();
-        final int height = frame.height();
-
-        final Picture picture = new Picture();
-        final Canvas canvas = picture.beginRecording(width, height);
-        canvas.drawColor(Color.argb(0.6f, 0, 0, 0));
-        final int thumbnailSize = mService.mContext.getResources().getDimensionPixelSize(
-                com.android.internal.R.dimen.cross_profile_apps_thumbnail_size);
-        final Drawable drawable = mService.mContext.getDrawable(thumbnailDrawableRes);
-        drawable.setBounds(
-                (width - thumbnailSize) / 2,
-                (height - thumbnailSize) / 2,
-                (width + thumbnailSize) / 2,
-                (height + thumbnailSize) / 2);
-        drawable.setTint(mContext.getColor(android.R.color.white));
-        drawable.draw(canvas);
-        picture.endRecording();
-
-        return Bitmap.createBitmap(picture).getHardwareBuffer();
+        return mTransitionAnimation.createCrossProfileAppsThumbnail(thumbnailDrawableRes, frame);
     }
 
     Animation createCrossProfileAppsThumbnailAnimationLocked(Rect appRect) {
-        final Animation animation =
-                mTransitionAnimation.loadCrossProfileAppThumbnailEnterAnimation();
-        return prepareThumbnailAnimationWithDuration(animation, appRect.width(),
-                appRect.height(), 0, null);
+        return mTransitionAnimation.createCrossProfileAppsThumbnailAnimationLocked(appRect);
     }
 
     /**
@@ -735,115 +668,14 @@
      * when a thumbnail is specified with the pending animation override.
      */
     Animation createThumbnailAspectScaleAnimationLocked(Rect appRect, @Nullable Rect contentInsets,
-            HardwareBuffer thumbnailHeader, WindowContainer container, int uiMode,
-            int orientation) {
-        Animation a;
-        final int thumbWidthI = thumbnailHeader.getWidth();
-        final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
-        final int thumbHeightI = thumbnailHeader.getHeight();
-        final int appWidth = appRect.width();
-
-        float scaleW = appWidth / thumbWidth;
-        getNextAppTransitionStartRect(container, mTmpRect);
-        final float fromX;
-        float fromY;
-        final float toX;
-        float toY;
-        final float pivotX;
-        final float pivotY;
-        if (shouldScaleDownThumbnailTransition(uiMode, orientation)) {
-            fromX = mTmpRect.left;
-            fromY = mTmpRect.top;
-
-            // For the curved translate animation to work, the pivot points needs to be at the
-            // same absolute position as the one from the real surface.
-            toX = mTmpRect.width() / 2 * (scaleW - 1f) + appRect.left;
-            toY = appRect.height() / 2 * (1 - 1 / scaleW) + appRect.top;
-            pivotX = mTmpRect.width() / 2;
-            pivotY = appRect.height() / 2 / scaleW;
-            if (mGridLayoutRecentsEnabled) {
-                // In the grid layout, the header is displayed above the thumbnail instead of
-                // overlapping it.
-                fromY -= thumbHeightI;
-                toY -= thumbHeightI * scaleW;
-            }
-        } else {
-            pivotX = 0;
-            pivotY = 0;
-            fromX = mTmpRect.left;
-            fromY = mTmpRect.top;
-            toX = appRect.left;
-            toY = appRect.top;
-        }
-        final long duration = getAspectScaleDuration();
-        final Interpolator interpolator = getAspectScaleInterpolator();
-        if (mNextAppTransitionScaleUp) {
-            // Animation up from the thumbnail to the full screen
-            Animation scale = new ScaleAnimation(1f, scaleW, 1f, scaleW, pivotX, pivotY);
-            scale.setInterpolator(interpolator);
-            scale.setDuration(duration);
-            Animation alpha = new AlphaAnimation(1f, 0f);
-            alpha.setInterpolator(mThumbnailFadeOutInterpolator);
-            alpha.setDuration(duration);
-            Animation translate = createCurvedMotion(fromX, toX, fromY, toY);
-            translate.setInterpolator(interpolator);
-            translate.setDuration(duration);
-
-            mTmpFromClipRect.set(0, 0, thumbWidthI, thumbHeightI);
-            mTmpToClipRect.set(appRect);
-
-            // Containing frame is in screen space, but we need the clip rect in the
-            // app space.
-            mTmpToClipRect.offsetTo(0, 0);
-            mTmpToClipRect.right = (int) (mTmpToClipRect.right / scaleW);
-            mTmpToClipRect.bottom = (int) (mTmpToClipRect.bottom / scaleW);
-
-            if (contentInsets != null) {
-                mTmpToClipRect.inset((int) (-contentInsets.left * scaleW),
-                        (int) (-contentInsets.top * scaleW),
-                        (int) (-contentInsets.right * scaleW),
-                        (int) (-contentInsets.bottom * scaleW));
-            }
-
-            Animation clipAnim = new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect);
-            clipAnim.setInterpolator(interpolator);
-            clipAnim.setDuration(duration);
-
-            // This AnimationSet uses the Interpolators assigned above.
-            AnimationSet set = new AnimationSet(false);
-            set.addAnimation(scale);
-            if (!mGridLayoutRecentsEnabled) {
-                // In the grid layout, the header should be shown for the whole animation.
-                set.addAnimation(alpha);
-            }
-            set.addAnimation(translate);
-            set.addAnimation(clipAnim);
-            a = set;
-        } else {
-            // Animation down from the full screen to the thumbnail
-            Animation scale = new ScaleAnimation(scaleW, 1f, scaleW, 1f, pivotX, pivotY);
-            scale.setInterpolator(interpolator);
-            scale.setDuration(duration);
-            Animation alpha = new AlphaAnimation(0f, 1f);
-            alpha.setInterpolator(mThumbnailFadeInInterpolator);
-            alpha.setDuration(duration);
-            Animation translate = createCurvedMotion(toX, fromX, toY, fromY);
-            translate.setInterpolator(interpolator);
-            translate.setDuration(duration);
-
-            // This AnimationSet uses the Interpolators assigned above.
-            AnimationSet set = new AnimationSet(false);
-            set.addAnimation(scale);
-            if (!mGridLayoutRecentsEnabled) {
-                // In the grid layout, the header should be shown for the whole animation.
-                set.addAnimation(alpha);
-            }
-            set.addAnimation(translate);
-            a = set;
-
-        }
-        return prepareThumbnailAnimationWithDuration(a, appWidth, appRect.height(), 0,
-                null);
+            HardwareBuffer thumbnailHeader, WindowContainer container, int orientation) {
+        AppTransitionAnimationSpec spec = mNextAppTransitionAnimationsSpecs.get(
+                container.hashCode());
+        return mTransitionAnimation.createThumbnailAspectScaleAnimationLocked(appRect,
+                contentInsets, thumbnailHeader, orientation, spec != null ? spec.rect : null,
+                mDefaultNextAppTransitionAnimationSpec != null
+                        ? mDefaultNextAppTransitionAnimationSpec.rect : null,
+                mNextAppTransitionScaleUp);
     }
 
     private Animation createCurvedMotion(float fromX, float toX, float fromY, float toY) {
@@ -1043,7 +875,7 @@
                             + "transit=%s Callers=%s",
                     a, appTransitionOldToString(transit), Debug.getCallers(3));
         } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CLIP_REVEAL) {
-            a = mTransitionAnimation.createClipRevealAnimationLocked(
+            a = mTransitionAnimation.createClipRevealAnimationLockedCompat(
                     transit, enter, frame, displayFrame,
                     mDefaultNextAppTransitionAnimationSpec != null
                             ? mDefaultNextAppTransitionAnimationSpec.rect : null);
@@ -1052,7 +884,7 @@
                             + "transit=%s Callers=%s",
                     a, appTransitionOldToString(transit), Debug.getCallers(3));
         } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_SCALE_UP) {
-            a = mTransitionAnimation.createScaleUpAnimationLocked(transit, enter, frame,
+            a = mTransitionAnimation.createScaleUpAnimationLockedCompat(transit, enter, frame,
                     mDefaultNextAppTransitionAnimationSpec != null
                             ? mDefaultNextAppTransitionAnimationSpec.rect : null);
             ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
@@ -1064,8 +896,8 @@
             mNextAppTransitionScaleUp =
                     (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP);
             final HardwareBuffer thumbnailHeader = getAppTransitionThumbnailHeader(container);
-            a = mTransitionAnimation.createThumbnailEnterExitAnimationLocked(
-                    getThumbnailTransitionState(enter), frame, transit, thumbnailHeader,
+            a = mTransitionAnimation.createThumbnailEnterExitAnimationLockedCompat(enter,
+                    mNextAppTransitionScaleUp, frame, transit, thumbnailHeader,
                     mDefaultNextAppTransitionAnimationSpec != null
                             ? mDefaultNextAppTransitionAnimationSpec.rect : null);
             ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
@@ -1080,9 +912,9 @@
                     (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP);
             AppTransitionAnimationSpec spec = mNextAppTransitionAnimationsSpecs.get(
                     container.hashCode());
-            a = mTransitionAnimation.createAspectScaledThumbnailEnterExitAnimationLocked(
-                    getThumbnailTransitionState(enter), orientation, transit, frame,
-                    insets, surfaceInsets, stableInsets, freeform, spec != null ? spec.rect : null,
+            a = mTransitionAnimation.createAspectScaledThumbnailEnterExitAnimationLocked(enter,
+                    mNextAppTransitionScaleUp, orientation, transit, frame, insets, surfaceInsets,
+                    stableInsets, freeform, spec != null ? spec.rect : null,
                     mDefaultNextAppTransitionAnimationSpec != null
                             ? mDefaultNextAppTransitionAnimationSpec.rect : null);
             ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
index faeb4ba..4355b38 100644
--- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java
+++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
@@ -166,6 +166,10 @@
         setReady(id, true);
     }
 
+    boolean isReady(int id) {
+        return mActiveSyncs.get(id).mReady;
+    }
+
     /**
      * Aborts the sync (ie. it doesn't wait for ready or anything to finish)
      */
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index 8fbe177..2eeabf2 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -34,6 +34,7 @@
 import static com.android.server.wm.ConfigurationContainerProto.OVERRIDE_CONFIGURATION;
 
 import android.annotation.CallSuper;
+import android.annotation.NonNull;
 import android.app.WindowConfiguration;
 import android.content.res.Configuration;
 import android.graphics.Point;
@@ -111,6 +112,7 @@
      * This method should be used for getting settings applied in each particular level of the
      * hierarchy.
      */
+    @NonNull
     public Configuration getConfiguration() {
         return mFullConfiguration;
     }
@@ -170,11 +172,13 @@
     }
 
     /** Returns requested override configuration applied to this configuration container. */
+    @NonNull
     public Configuration getRequestedOverrideConfiguration() {
         return mRequestedOverrideConfiguration;
     }
 
     /** Returns the resolved override configuration. */
+    @NonNull
     Configuration getResolvedOverrideConfiguration() {
         return mResolvedOverrideConfiguration;
     }
@@ -203,6 +207,7 @@
      * Get merged override configuration from the top of the hierarchy down to this particular
      * instance. This should be reported to client as override config.
      */
+    @NonNull
     public Configuration getMergedOverrideConfiguration() {
         return mMergedOverrideConfiguration;
     }
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index baa27e3..99f6fd4 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -495,8 +495,10 @@
 
 
     DisplayAreaInfo getDisplayAreaInfo() {
-        DisplayAreaInfo info = new DisplayAreaInfo(mRemoteToken.toWindowContainerToken(),
+        final DisplayAreaInfo info = new DisplayAreaInfo(mRemoteToken.toWindowContainerToken(),
                 getDisplayContent().getDisplayId(), mFeatureId);
+        final RootDisplayArea root = getRootDisplayArea();
+        info.rootDisplayAreaId = root == null ? getDisplayContent().mFeatureId : root.mFeatureId;
         info.configuration.setTo(getConfiguration());
         return info;
     }
diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
index 47d7c9d..3d7ac6c 100644
--- a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
+++ b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
@@ -25,6 +25,7 @@
 import static android.view.WindowManagerPolicyConstants.APPLICATION_LAYER;
 import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
 import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_LAST;
+import static android.window.DisplayAreaOrganizer.KEY_ROOT_DISPLAY_AREA_ID;
 
 import android.annotation.Nullable;
 import android.os.Bundle;
@@ -135,12 +136,6 @@
  */
 class DisplayAreaPolicyBuilder {
 
-    /**
-     * Key to specify the {@link RootDisplayArea} to attach the window to. Should be used by the
-     * function passed in from {@link #setSelectRootForWindowFunc(BiFunction)}
-     */
-    static final String KEY_ROOT_DISPLAY_AREA_ID = "root_display_area_id";
-
     @Nullable private HierarchyBuilder mRootHierarchyBuilder;
     private final ArrayList<HierarchyBuilder> mDisplayAreaGroupHierarchyBuilders =
             new ArrayList<>();
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index a72d9aa..632662d 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -94,6 +94,7 @@
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+import static com.android.server.wm.ActivityRecord.State.RESUMED;
 import static com.android.server.wm.DisplayContentProto.APP_TRANSITION;
 import static com.android.server.wm.DisplayContentProto.CLOSING_APPS;
 import static com.android.server.wm.DisplayContentProto.CURRENT_FOCUS;
@@ -116,7 +117,6 @@
 import static com.android.server.wm.DisplayContentProto.SCREEN_ROTATION_ANIMATION;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
-import static com.android.server.wm.Task.ActivityState.RESUMED;
 import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
 import static com.android.server.wm.WindowContainerChildProto.DISPLAY_CONTENT;
@@ -361,6 +361,13 @@
     boolean mIsSizeForced = false;
 
     /**
+     * Overridden display size and metrics to activity window bounds. Set via
+     * "adb shell wm set-sandbox-display-apis". Default to true, since only disable for debugging.
+     * @see WindowManagerService#setSandboxDisplayApis(int, boolean)
+     */
+    private boolean mSandboxDisplayApis = true;
+
+    /**
      * Overridden display density for current user. Initialized with {@link #mInitialDisplayDensity}
      * but can be set from Settings or via shell command "adb shell wm density".
      * @see WindowManagerService#setForcedDisplayDensityForUser(int, int, int)
@@ -689,6 +696,8 @@
     // well and thus won't change the top resumed / focused record
     boolean mDontMoveToTop;
 
+    private final ArrayList<ActivityRecord> mTmpActivityList = new ArrayList<>();
+
     private final Consumer<WindowState> mUpdateWindowsForAnimator = w -> {
         WindowStateAnimator winAnimator = w.mWinAnimator;
         final ActivityRecord activity = w.mActivityRecord;
@@ -763,6 +772,12 @@
                 mTmpWindow = null;
                 return true;
             }
+
+            if (focusedApp.getTask() == activity.getTask()
+                    && focusedApp.getTaskFragment() != activity.getTaskFragment()) {
+                // Do not use the activity window of another TaskFragment in the same leaf Task
+                return false;
+            }
         }
 
         ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "findFocusedWindow: Found new focus @ %s", w);
@@ -1012,6 +1027,8 @@
 
         mAppTransition = new AppTransition(mWmService.mContext, mWmService, this);
         mAppTransition.registerListenerLocked(mWmService.mActivityManagerAppTransitionNotifier);
+        mAtmService.getTransitionController().registerLegacyListener(
+                mWmService.mActivityManagerAppTransitionNotifier);
         mAppTransition.registerListenerLocked(mFixedRotationTransitionListener);
         mAppTransitionController = new AppTransitionController(mWmService, this);
         mUnknownAppVisibilityController = new UnknownAppVisibilityController(mWmService, this);
@@ -1909,10 +1926,6 @@
                 }
             }
         }
-
-        if (mWmService.mAccessibilityController != null) {
-            mWmService.mAccessibilityController.onRotationChanged(this);
-        }
     }
 
     void configureDisplayPolicy() {
@@ -2480,7 +2493,7 @@
 
     @Override
     boolean isVisibleRequested() {
-        return isVisible();
+        return isVisible() && !mRemoved && !mRemoving;
     }
 
     @Override
@@ -3095,7 +3108,11 @@
             screenRotationAnimation.dumpDebug(proto, SCREEN_ROTATION_ANIMATION);
         }
         mDisplayFrames.dumpDebug(proto, DISPLAY_FRAMES);
-        mAppTransition.dumpDebug(proto, APP_TRANSITION);
+        if (mAtmService.getTransitionController().isShellTransitionsEnabled()) {
+            mAtmService.getTransitionController().dumpDebugLegacy(proto, APP_TRANSITION);
+        } else {
+            mAppTransition.dumpDebug(proto, APP_TRANSITION);
+        }
         if (mFocusedApp != null) {
             mFocusedApp.writeNameToProto(proto, FOCUSED_APP);
         }
@@ -4499,6 +4516,8 @@
             }
         }
 
+        // clear first just in case.
+        mTmpActivityList.clear();
         // Time to remove any exiting applications?
         forAllRootTasks(task -> {
             final ArrayList<ActivityRecord> activities = task.mExitingActivities;
@@ -4506,16 +4525,24 @@
                 final ActivityRecord activity = activities.get(j);
                 if (!activity.hasVisible && !mDisplayContent.mClosingApps.contains(activity)
                         && (!activity.mIsExiting || activity.isEmpty())) {
-                    // Make sure there is no animation running on this activity, so any windows
-                    // associated with it will be removed as soon as their animations are
-                    // complete.
-                    cancelAnimation();
-                    ProtoLog.v(WM_DEBUG_ADD_REMOVE,
-                            "performLayout: Activity exiting now removed %s", activity);
-                    activity.removeIfPossible();
+                    mTmpActivityList.add(activity);
                 }
             }
         });
+        if (!mTmpActivityList.isEmpty()) {
+            // Make sure there is no animation running on this activity, so any windows
+            // associated with it will be removed as soon as their animations are
+            // complete.
+            cancelAnimation();
+        }
+        for (int i = 0; i < mTmpActivityList.size(); ++i) {
+            final ActivityRecord activity = mTmpActivityList.get(i);
+            ProtoLog.v(WM_DEBUG_ADD_REMOVE,
+                    "performLayout: Activity exiting now removed %s", activity);
+            activity.removeIfPossible();
+        }
+        // Clear afterwards so we don't hold references.
+        mTmpActivityList.clear();
     }
 
     @Override
@@ -5017,6 +5044,12 @@
 
     /** Check if pending app transition is for activity / task launch. */
     boolean isNextTransitionForward() {
+        // TODO(b/191375840): decouple "forwardness" from transition system.
+        if (mAtmService.getTransitionController().isShellTransitionsEnabled()) {
+            @WindowManager.TransitionType int type =
+                    mAtmService.getTransitionController().getCollectingTransitionType();
+            return type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT;
+        }
         return mAppTransition.containsTransitRequest(TRANSIT_OPEN)
                 || mAppTransition.containsTransitRequest(TRANSIT_TO_FRONT);
     }
@@ -5600,6 +5633,14 @@
                 ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED);
     }
 
+    @Override
+    void onResize() {
+        super.onResize();
+        if (mWmService.mAccessibilityController != null) {
+            mWmService.mAccessibilityController.onDisplaySizeChanged(this);
+        }
+    }
+
     /**
      * If the launching rotated activity ({@link #mFixedRotationLaunchingApp}) is null, it simply
      * applies the rotation to display. Otherwise because the activity has shown as rotated, the
@@ -5840,6 +5881,21 @@
         return true;
     }
 
+    /**
+     * Sets if Display APIs should be sandboxed to the activity window bounds.
+     */
+    void setSandboxDisplayApis(boolean sandboxDisplayApis) {
+        mSandboxDisplayApis = sandboxDisplayApis;
+    }
+
+    /**
+     * Returns {@code true} is Display APIs should be sandboxed to the activity window bounds,
+     * {@code false} otherwise. Default to true, unless set for debugging purposes.
+     */
+    boolean sandboxDisplayApis() {
+        return mSandboxDisplayApis;
+    }
+
     /** The entry for proceeding to handle {@link #mFixedRotationLaunchingApp}. */
     class FixedRotationTransitionListener extends WindowManagerInternal.AppTransitionListener {
 
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 97c19ab..d5a4345 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -20,6 +20,7 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.content.res.Configuration.UI_MODE_TYPE_CAR;
@@ -39,6 +40,7 @@
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
 import static android.view.InsetsState.ITYPE_TOP_MANDATORY_GESTURES;
 import static android.view.InsetsState.ITYPE_TOP_TAPPABLE_ELEMENT;
+import static android.view.ViewRootImpl.INSETS_LAYOUT_GENERALIZATION;
 import static android.view.ViewRootImpl.computeWindowBounds;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
@@ -173,6 +175,7 @@
 import com.android.server.wm.InputMonitor.EventReceiverInputConsumer;
 
 import java.io.PrintWriter;
+import java.util.Objects;
 import java.util.function.Consumer;
 
 /**
@@ -313,17 +316,26 @@
 
     private WindowState mSystemUiControllingWindow;
 
+    // Candidate window to determine the color of navigation bar.
+    private WindowState mNavBarColorWindowCandidate;
+
+    // The window to determine opacity and background of translucent navigation bar. The window
+    // needs to be opaque.
+    private WindowState mNavBarBackgroundWindow;
+
+    private String mFocusedApp;
     private int mLastDisableFlags;
     private int mLastAppearance;
     private int mLastFullscreenAppearance;
     private int mLastDockedAppearance;
     private int mLastBehavior;
+    private final InsetsState mRequestedState = new InsetsState();
     private final Rect mNonDockedRootTaskBounds = new Rect();
     private final Rect mDockedRootTaskBounds = new Rect();
     private final Rect mLastNonDockedRootTaskBounds = new Rect();
     private final Rect mLastDockedRootTaskBounds = new Rect();
 
-    // What we last reported to system UI about whether the focused window is fullscreen/immersive.
+    // What we last reported to input dispatcher about whether the focused window is fullscreen.
     private boolean mLastFocusIsFullscreen = false;
 
     // If nonzero, a panic gesture was performed at that time in uptime millis and is still pending.
@@ -333,6 +345,7 @@
     private static final Rect sTmpRect = new Rect();
     private static final Rect sTmpNavFrame = new Rect();
     private static final Rect sTmpStatusFrame = new Rect();
+    private static final Rect sTmpDecorFrame = new Rect();
     private static final Rect sTmpScreenDecorFrame = new Rect();
     private static final Rect sTmpLastParentFrame = new Rect();
     private static final Rect sTmpDisplayFrameBounds = new Rect();
@@ -344,8 +357,6 @@
     private boolean mTopIsFullscreen;
     private boolean mForceStatusBar;
     private int mNavBarOpacityMode = NAV_BAR_OPAQUE_WHEN_FREEFORM_OR_DOCKED;
-    private boolean mForcingShowNavBar;
-    private int mForcingShowNavBarLayer;
     private boolean mForceShowSystemBars;
 
     private boolean mShowingDream;
@@ -430,8 +441,10 @@
 
         final int displayId = displayContent.getDisplayId();
 
-        mBarContentFrames.put(TYPE_STATUS_BAR, new Rect());
-        mBarContentFrames.put(TYPE_NAVIGATION_BAR, new Rect());
+        if (!INSETS_LAYOUT_GENERALIZATION) {
+            mBarContentFrames.put(TYPE_STATUS_BAR, new Rect());
+            mBarContentFrames.put(TYPE_NAVIGATION_BAR, new Rect());
+        }
 
         final Resources r = mContext.getResources();
         mCarDockEnablesAccelerometer = r.getBoolean(R.bool.config_carDockEnablesAccelerometer);
@@ -613,6 +626,8 @@
             }
         };
         displayContent.mAppTransition.registerListenerLocked(mAppTransitionListener);
+        mService.mAtmService.getTransitionController().registerLegacyListener(
+                mAppTransitionListener);
         mImmersiveModeConfirmation = new ImmersiveModeConfirmation(mContext, looper,
                 mService.mVrModeEnabled);
 
@@ -1079,7 +1094,9 @@
                 mStatusBar = win;
                 final TriConsumer<DisplayFrames, WindowState, Rect> frameProvider =
                         (displayFrames, windowState, rect) -> {
-                            rect.bottom = rect.top + getStatusBarHeight(displayFrames);
+                            if (!INSETS_LAYOUT_GENERALIZATION) {
+                                rect.bottom = rect.top + getStatusBarHeight(displayFrames);
+                            }
                         };
                 mDisplayContent.setInsetProvider(ITYPE_STATUS_BAR, win, frameProvider);
                 mDisplayContent.setInsetProvider(ITYPE_TOP_MANDATORY_GESTURES, win, frameProvider);
@@ -1089,18 +1106,22 @@
                 mNavigationBar = win;
                 mDisplayContent.setInsetProvider(ITYPE_NAVIGATION_BAR, win,
                         (displayFrames, windowState, inOutFrame) -> {
-
-                            // In Gesture Nav, navigation bar frame is larger than frame to
-                            // calculate inset.
-                            if (navigationBarPosition(displayFrames.mDisplayWidth,
-                                    displayFrames.mDisplayHeight,
-                                    displayFrames.mRotation) == NAV_BAR_BOTTOM
-                                    && !mNavButtonForcedVisible) {
-                                sTmpRect.set(inOutFrame);
-                                sTmpRect.intersectUnchecked(displayFrames.mDisplayCutoutSafe);
-                                inOutFrame.top = sTmpRect.bottom
-                                        - getNavigationBarHeight(displayFrames.mRotation,
-                                        mDisplayContent.getConfiguration().uiMode);
+                            if (INSETS_LAYOUT_GENERALIZATION) {
+                                inOutFrame.inset(windowState.getLayoutingAttrs(
+                                        displayFrames.mRotation).providedInternalInsets);
+                            } else {
+                                // In Gesture Nav, navigation bar frame is larger than frame to
+                                // calculate inset.
+                                if (navigationBarPosition(displayFrames.mDisplayWidth,
+                                        displayFrames.mDisplayHeight,
+                                        displayFrames.mRotation) == NAV_BAR_BOTTOM
+                                        && !mNavButtonForcedVisible) {
+                                    sTmpRect.set(inOutFrame);
+                                    sTmpRect.intersectUnchecked(displayFrames.mDisplayCutoutSafe);
+                                    inOutFrame.top = sTmpRect.bottom
+                                            - getNavigationBarHeight(displayFrames.mRotation,
+                                            mDisplayContent.getConfiguration().uiMode);
+                                }
                             }
                         },
 
@@ -1161,7 +1182,14 @@
                                 mExtraNavBarAltPosition = getAltBarPosition(attrs);
                                 break;
                         }
-                        mDisplayContent.setInsetProvider(insetsType, win, null);
+                        if (!INSETS_LAYOUT_GENERALIZATION) {
+                            mDisplayContent.setInsetProvider(insetsType, win, null);
+                        } else {
+                            mDisplayContent.setInsetProvider(insetsType, win, (displayFrames,
+                                    windowState, inOutFrame) -> inOutFrame.inset(
+                                            windowState.getLayoutingAttrs(displayFrames.mRotation)
+                                                    .providedInternalInsets));
+                        }
                     }
                 }
                 break;
@@ -1252,8 +1280,17 @@
     }
 
     private int getStatusBarHeight(DisplayFrames displayFrames) {
-        return Math.max(mStatusBarHeightForRotation[displayFrames.mRotation],
-                displayFrames.mDisplayCutoutSafe.top);
+        int statusBarHeight;
+        if (INSETS_LAYOUT_GENERALIZATION) {
+            if (mStatusBar != null) {
+                statusBarHeight = mStatusBar.getLayoutingAttrs(displayFrames.mRotation).height;
+            } else {
+                statusBarHeight = 0;
+            }
+        } else {
+            statusBarHeight = mStatusBarHeightForRotation[displayFrames.mRotation];
+        }
+        return Math.max(statusBarHeight, displayFrames.mDisplayCutoutSafe.top);
     }
 
     WindowState getStatusBar() {
@@ -1388,7 +1425,7 @@
     /**
      * @return true if the system bars are forced to stay visible
      */
-    public boolean areSystemBarsForcedShownLw(WindowState windowState) {
+    public boolean areSystemBarsForcedShownLw() {
         return mForceShowSystemBars;
     }
 
@@ -1423,13 +1460,30 @@
             WindowFrames simulatedWindowFrames, SparseArray<Rect> contentFrames,
             Consumer<Rect> layout) {
         win.setSimulatedWindowFrames(simulatedWindowFrames);
+        final int requestedHeight = win.mRequestedHeight;
+        final int requestedWidth = win.mRequestedWidth;
+        if (INSETS_LAYOUT_GENERALIZATION) {
+            // Without a full layout process, in order to layout the system bars correctly, we need
+            // to set the requested size and the initial display frames to the window.
+            WindowManager.LayoutParams params = win.getLayoutingAttrs(displayFrames.mRotation);
+            win.setRequestedSize(params.width, params.height);
+            sTmpDecorFrame.set(0, 0, displayFrames.mDisplayWidth, displayFrames.mDisplayHeight);
+            simulatedWindowFrames.setFrames(sTmpDecorFrame /* parentFrame */,
+                    sTmpDecorFrame /* displayFrame */);
+            simulatedWindowFrames.mIsSimulatingDecorWindow = true;
+        }
         final Rect contentFrame = new Rect();
         try {
             layout.accept(contentFrame);
         } finally {
             win.setSimulatedWindowFrames(null);
+            if (INSETS_LAYOUT_GENERALIZATION) {
+                win.setRequestedSize(requestedWidth, requestedHeight);
+            }
         }
-        contentFrames.put(win.mAttrs.type, contentFrame);
+        if (!INSETS_LAYOUT_GENERALIZATION) {
+            contentFrames.put(win.mAttrs.type, contentFrame);
+        }
         mDisplayContent.getInsetsStateController().computeSimulatedState(
                 win, displayFrames, simulatedWindowFrames);
     }
@@ -1440,15 +1494,31 @@
      * some temporal states, but doesn't change the window frames used to show on screen.
      */
     void simulateLayoutDisplay(DisplayFrames displayFrames, SparseArray<Rect> barContentFrames) {
-        final WindowFrames simulatedWindowFrames = new WindowFrames();
         if (mNavigationBar != null) {
-            simulateLayoutDecorWindow(mNavigationBar, displayFrames, simulatedWindowFrames,
-                    barContentFrames, contentFrame -> layoutNavigationBar(displayFrames,
-                            contentFrame));
+            final WindowFrames simulatedWindowFrames = new WindowFrames();
+            if (INSETS_LAYOUT_GENERALIZATION) {
+                simulateLayoutDecorWindow(mNavigationBar, displayFrames, simulatedWindowFrames,
+                        barContentFrames,
+                        contentFrame -> simulateLayoutForContentFrame(displayFrames,
+                                mNavigationBar, contentFrame));
+            } else {
+                simulateLayoutDecorWindow(mNavigationBar, displayFrames, simulatedWindowFrames,
+                        barContentFrames, contentFrame -> layoutNavigationBar(displayFrames,
+                                contentFrame));
+            }
         }
         if (mStatusBar != null) {
-            simulateLayoutDecorWindow(mStatusBar, displayFrames, simulatedWindowFrames,
-                    barContentFrames, contentFrame -> layoutStatusBar(displayFrames, contentFrame));
+            final WindowFrames simulatedWindowFrames = new WindowFrames();
+            if (INSETS_LAYOUT_GENERALIZATION) {
+                simulateLayoutDecorWindow(mStatusBar, displayFrames, simulatedWindowFrames,
+                        barContentFrames,
+                        contentFrame -> simulateLayoutForContentFrame(displayFrames,
+                                mStatusBar, contentFrame));
+            } else {
+                simulateLayoutDecorWindow(mStatusBar, displayFrames, simulatedWindowFrames,
+                        barContentFrames,
+                        contentFrame -> layoutStatusBar(displayFrames, contentFrame));
+            }
         }
     }
 
@@ -1467,7 +1537,7 @@
         windowFrames.setFrames(sTmpStatusFrame /* parentFrame */,
                 sTmpStatusFrame /* displayFrame */);
         // Let the status bar determine its size.
-        mStatusBar.computeFrameAndUpdateSourceFrame();
+        mStatusBar.computeFrameAndUpdateSourceFrame(displayFrames);
 
         // For layout, the status bar is always at the top with our fixed height.
         int statusBarBottom = displayFrames.mUnrestricted.top
@@ -1518,18 +1588,18 @@
         } else if (navBarPosition == NAV_BAR_RIGHT) {
             // Landscape screen; nav bar goes to the right.
             navigationFrame.left = Math.min(cutoutSafeUnrestricted.right, navigationFrame.right)
-                    - getNavigationBarWidth(rotation, uiMode);
+                    - getNavigationBarWidth(rotation, uiMode, navBarPosition);
         } else if (navBarPosition == NAV_BAR_LEFT) {
             // Seascape screen; nav bar goes to the left.
             navigationFrame.right = Math.max(cutoutSafeUnrestricted.left, navigationFrame.left)
-                    + getNavigationBarWidth(rotation, uiMode);
+                    + getNavigationBarWidth(rotation, uiMode, navBarPosition);
         }
 
         // Compute the final frame.
         final WindowFrames windowFrames = mNavigationBar.getLayoutingWindowFrames();
         windowFrames.setFrames(navigationFrame /* parentFrame */,
                 navigationFrame /* displayFrame */);
-        mNavigationBar.computeFrameAndUpdateSourceFrame();
+        mNavigationBar.computeFrameAndUpdateSourceFrame(displayFrames);
         sTmpRect.set(windowFrames.mFrame);
         sTmpRect.intersect(displayFrames.mDisplayCutoutSafe);
         contentFrame.set(sTmpRect);
@@ -1538,6 +1608,16 @@
         return navBarPosition;
     }
 
+    private void simulateLayoutForContentFrame(DisplayFrames displayFrames, WindowState win,
+            Rect simulatedContentFrame) {
+        layoutWindowLw(win, null /* attached */, displayFrames);
+        final Rect contentFrame = sTmpRect;
+        contentFrame.set(win.getLayoutingWindowFrames().mFrame);
+        // Excluding the display cutout before set to the simulated content frame.
+        contentFrame.intersect(displayFrames.mDisplayCutoutSafe);
+        simulatedContentFrame.set(contentFrame);
+    }
+
     private boolean canReceiveInput(WindowState win) {
         boolean notFocusable =
                 (win.getAttrs().flags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0;
@@ -1559,16 +1639,16 @@
      * @param displayFrames The display frames.
      */
     public void layoutWindowLw(WindowState win, WindowState attached, DisplayFrames displayFrames) {
-        if (win == mNavigationBar) {
+        if (win == mNavigationBar && !INSETS_LAYOUT_GENERALIZATION) {
             mNavigationBarPosition = layoutNavigationBar(displayFrames,
                     mBarContentFrames.get(TYPE_NAVIGATION_BAR));
             return;
         }
-        if ((win == mStatusBar && !canReceiveInput(win))) {
+        if ((win == mStatusBar && !canReceiveInput(win)) && !INSETS_LAYOUT_GENERALIZATION) {
             layoutStatusBar(displayFrames, mBarContentFrames.get(TYPE_STATUS_BAR));
             return;
         }
-        final WindowManager.LayoutParams attrs = win.getAttrs();
+        final WindowManager.LayoutParams attrs = win.getLayoutingAttrs(displayFrames.mRotation);
 
         final int type = attrs.type;
         final int fl = attrs.flags;
@@ -1576,7 +1656,7 @@
         final int sim = attrs.softInputMode;
 
         displayFrames = win.getDisplayFrames(displayFrames);
-        final WindowFrames windowFrames = win.getWindowFrames();
+        final WindowFrames windowFrames = win.getLayoutingWindowFrames();
 
         sTmpLastParentFrame.set(windowFrames.mParentFrame);
         final Rect pf = windowFrames.mParentFrame;
@@ -1587,7 +1667,13 @@
         final boolean layoutInsetDecor = (fl & FLAG_LAYOUT_INSET_DECOR) == FLAG_LAYOUT_INSET_DECOR;
 
         final InsetsState state = win.getInsetsState();
-        computeWindowBounds(attrs, state, win.mToken.getBounds(), df);
+        if (windowFrames.mIsSimulatingDecorWindow && INSETS_LAYOUT_GENERALIZATION) {
+            // Override the bounds in window token has many side effects. Directly use the display
+            // frame set for the simulated layout for this case.
+            computeWindowBounds(attrs, state, df, df);
+        } else {
+            computeWindowBounds(attrs, state, win.mToken.getBounds(), df);
+        }
         if (attached == null) {
             pf.set(df);
             if ((pfl & PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME) != 0) {
@@ -1687,7 +1773,17 @@
             windowFrames.setContentChanged(true);
         }
 
-        win.computeFrameAndUpdateSourceFrame();
+        win.computeFrameAndUpdateSourceFrame(displayFrames);
+        if (INSETS_LAYOUT_GENERALIZATION && attrs.type == TYPE_STATUS_BAR) {
+            if (displayFrames.mDisplayCutoutSafe.top > displayFrames.mUnrestricted.top) {
+                // Make sure that the zone we're avoiding for the cutout is at least as tall as the
+                // status bar; otherwise fullscreen apps will end up cutting halfway into the status
+                // bar.
+                displayFrames.mDisplayCutoutSafe.top = Math.max(
+                        displayFrames.mDisplayCutoutSafe.top,
+                        windowFrames.mFrame.bottom);
+            }
+        }
     }
 
     WindowState getTopFullscreenOpaqueWindow() {
@@ -1706,9 +1802,9 @@
         mTopFullscreenOpaqueOrDimmingWindowState = null;
         mTopDockedOpaqueWindowState = null;
         mTopDockedOpaqueOrDimmingWindowState = null;
+        mNavBarColorWindowCandidate = null;
+        mNavBarBackgroundWindow = null;
         mForceStatusBar = false;
-        mForcingShowNavBar = false;
-        mForcingShowNavBarLayer = -1;
 
         mAllowLockscreenWhenOn = false;
         mShowingDream = false;
@@ -1728,11 +1824,6 @@
         if (DEBUG_LAYOUT) Slog.i(TAG, "Win " + win + ": affectsSystemUi=" + affectsSystemUi);
         applyKeyguardPolicy(win, imeTarget);
         final int fl = attrs.flags;
-        if (mTopFullscreenOpaqueWindowState == null && affectsSystemUi
-                && attrs.type == TYPE_INPUT_METHOD) {
-            mForcingShowNavBar = true;
-            mForcingShowNavBarLayer = win.getSurfaceLayer();
-        }
 
         boolean appWindow = attrs.type >= FIRST_APPLICATION_WINDOW
                 && attrs.type < FIRST_SYSTEM_WINDOW;
@@ -1803,11 +1894,34 @@
             }
         }
 
-        // Check if the freeform window overlaps with the navigation bar area.
         final WindowState navBarWin = hasNavigationBar() ? mNavigationBar : null;
-        if (!mIsFreeformWindowOverlappingWithNavBar && win.inFreeformWindowingMode()
-                && isOverlappingWithNavBar(win, navBarWin)) {
-            mIsFreeformWindowOverlappingWithNavBar = true;
+        if (isOverlappingWithNavBar(win, navBarWin)) {
+            // Check if the freeform window overlaps with the navigation bar area.
+            if (!mIsFreeformWindowOverlappingWithNavBar && win.inFreeformWindowingMode()) {
+                mIsFreeformWindowOverlappingWithNavBar = true;
+            }
+            // Cache app window that overlaps with the navigation bar area to determine opacity and
+            // appearance of the navigation bar. We only need to cache one window because there
+            // should be only one overlapping window if it's not in gesture navigation mode; if it's
+            // in gesture navigation mode, the navigation bar will be NAV_BAR_FORCE_TRANSPARENT and
+            // its appearance won't be decided by overlapping windows.
+            if (affectsSystemUi) {
+                if ((appWindow && attached == null && attrs.isFullscreen())
+                        || attrs.type == TYPE_VOICE_INTERACTION) {
+                    if (mNavBarColorWindowCandidate == null) {
+                        mNavBarColorWindowCandidate = win;
+                    }
+                    if (mNavBarBackgroundWindow == null) {
+                        mNavBarBackgroundWindow = win;
+                    }
+                } else if (win.isDimming()) {
+                    // For dimming window with it's host bounds overlapping with navigation bar, it
+                    // can be used to determine navigation bar's color but not opacity.
+                    if (mNavBarColorWindowCandidate == null) {
+                        mNavBarColorWindowCandidate = win;
+                    }
+                }
+            }
         }
 
         // Also keep track of any windows that are dimming but not necessarily fullscreen in the
@@ -2116,11 +2230,37 @@
         return mUiContext;
     }
 
-    private int getNavigationBarWidth(int rotation, int uiMode) {
-        if (ALTERNATE_CAR_MODE_NAV_SIZE && (uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_CAR) {
-            return mNavigationBarWidthForRotationInCarMode[rotation];
+    private int getNavigationBarWidth(int rotation, int uiMode, int position) {
+        if (INSETS_LAYOUT_GENERALIZATION) {
+            if (mNavigationBar == null) {
+                return 0;
+            }
+            LayoutParams lp = mNavigationBar.mAttrs;
+            if (lp.paramsForRotation != null
+                    && lp.paramsForRotation.length == 4
+                    && lp.paramsForRotation[rotation] != null) {
+                lp = lp.paramsForRotation[rotation];
+            }
+            if (position == NAV_BAR_LEFT) {
+                if (lp.width > lp.providedInternalInsets.right) {
+                    return lp.width - lp.providedInternalInsets.right;
+                } else {
+                    return 0;
+                }
+            } else if (position == NAV_BAR_RIGHT) {
+                if (lp.width > lp.providedInternalInsets.left) {
+                    return lp.width - lp.providedInternalInsets.left;
+                } else {
+                    return 0;
+                }
+            }
+            return lp.width;
         } else {
-            return mNavigationBarWidthForRotationDefault[rotation];
+            if (ALTERNATE_CAR_MODE_NAV_SIZE && (uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_CAR) {
+                return mNavigationBarWidthForRotationInCarMode[rotation];
+            } else {
+                return mNavigationBarWidthForRotationDefault[rotation];
+            }
         }
     }
 
@@ -2147,7 +2287,7 @@
         if (hasNavigationBar()) {
             final int navBarPosition = navigationBarPosition(fullWidth, fullHeight, rotation);
             if (navBarPosition == NAV_BAR_LEFT || navBarPosition == NAV_BAR_RIGHT) {
-                width -= getNavigationBarWidth(rotation, uiMode);
+                width -= getNavigationBarWidth(rotation, uiMode, navBarPosition);
             }
         }
         if (displayCutout != null) {
@@ -2157,10 +2297,21 @@
     }
 
     private int getNavigationBarHeight(int rotation, int uiMode) {
-        if (ALTERNATE_CAR_MODE_NAV_SIZE && (uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_CAR) {
-            return mNavigationBarHeightForRotationInCarMode[rotation];
+        if (INSETS_LAYOUT_GENERALIZATION) {
+            if (mNavigationBar == null) {
+                return 0;
+            }
+            LayoutParams lp = mNavigationBar.getLayoutingAttrs(rotation);
+            if (lp.height < lp.providedInternalInsets.top) {
+                return 0;
+            }
+            return lp.height - lp.providedInternalInsets.top;
         } else {
-            return mNavigationBarHeightForRotationDefault[rotation];
+            if (ALTERNATE_CAR_MODE_NAV_SIZE && (uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_CAR) {
+                return mNavigationBarHeightForRotationInCarMode[rotation];
+            } else {
+                return mNavigationBarHeightForRotationDefault[rotation];
+            }
         }
     }
 
@@ -2177,10 +2328,17 @@
      * @return navigation bar frame height
      */
     private int getNavigationBarFrameHeight(int rotation, int uiMode) {
-        if (ALTERNATE_CAR_MODE_NAV_SIZE && (uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_CAR) {
-            return mNavigationBarHeightForRotationInCarMode[rotation];
+        if (INSETS_LAYOUT_GENERALIZATION) {
+            if (mNavigationBar == null) {
+                return 0;
+            }
+            return mNavigationBar.mAttrs.height;
         } else {
-            return mNavigationBarFrameHeightForRotationDefault[rotation];
+            if (ALTERNATE_CAR_MODE_NAV_SIZE && (uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_CAR) {
+                return mNavigationBarHeightForRotationInCarMode[rotation];
+            } else {
+                return mNavigationBarFrameHeightForRotationDefault[rotation];
+            }
         }
     }
 
@@ -2301,9 +2459,9 @@
             if (position == NAV_BAR_BOTTOM) {
                 outInsets.bottom = getNavigationBarHeight(displayRotation, uiMode);
             } else if (position == NAV_BAR_RIGHT) {
-                outInsets.right = getNavigationBarWidth(displayRotation, uiMode);
+                outInsets.right = getNavigationBarWidth(displayRotation, uiMode, position);
             } else if (position == NAV_BAR_LEFT) {
-                outInsets.left = getNavigationBarWidth(displayRotation, uiMode);
+                outInsets.left = getNavigationBarWidth(displayRotation, uiMode, position);
             }
         }
 
@@ -2329,6 +2487,17 @@
 
     @NavigationBarPosition
     int navigationBarPosition(int displayWidth, int displayHeight, int displayRotation) {
+        if (INSETS_LAYOUT_GENERALIZATION && mNavigationBar != null) {
+            final int gravity = mNavigationBar.getLayoutingAttrs(displayRotation).gravity;
+            switch (gravity) {
+                case Gravity.LEFT:
+                    return NAV_BAR_LEFT;
+                case Gravity.RIGHT:
+                    return NAV_BAR_RIGHT;
+                default:
+                    return NAV_BAR_BOTTOM;
+            }
+        }
         if (navigationBarCanMove() && displayWidth > displayHeight) {
             if (displayRotation == Surface.ROTATION_270) {
                 return NAV_BAR_LEFT;
@@ -2486,8 +2655,6 @@
         final WindowState win = winCandidate;
         mSystemUiControllingWindow = win;
 
-        mDisplayContent.getInsetsPolicy().updateBarControlTarget(win);
-
         final boolean inSplitScreen =
                 mService.mRoot.getDefaultTaskDisplayArea().isSplitScreenModeActivated();
         if (inSplitScreen) {
@@ -2505,15 +2672,12 @@
                 mTopDockedOpaqueOrDimmingWindowState);
         final int disableFlags = win.getDisableFlags();
         final int opaqueAppearance = updateSystemBarsLw(win, disableFlags);
-        final WindowState navColorWin = chooseNavigationColorWindowLw(
-                mTopFullscreenOpaqueWindowState, mTopFullscreenOpaqueOrDimmingWindowState,
+        final WindowState navColorWin = chooseNavigationColorWindowLw(mNavBarColorWindowCandidate,
                 mDisplayContent.mInputMethodWindow, mNavigationBarPosition);
         final boolean isNavbarColorManagedByIme =
                 navColorWin != null && navColorWin == mDisplayContent.mInputMethodWindow;
-        final int appearance = updateLightNavigationBarLw(
-                win.mAttrs.insetsFlags.appearance, mTopFullscreenOpaqueWindowState,
-                mTopFullscreenOpaqueOrDimmingWindowState,
-                mDisplayContent.mInputMethodWindow, navColorWin) | opaqueAppearance;
+        final int appearance = updateLightNavigationBarLw(win.mAttrs.insetsFlags.appearance,
+                navColorWin) | opaqueAppearance;
         final int behavior = win.mAttrs.insetsFlags.behavior;
         final boolean isFullscreen = !win.getRequestedVisibility(ITYPE_STATUS_BAR)
                 || !win.getRequestedVisibility(ITYPE_NAVIGATION_BAR);
@@ -2522,6 +2686,8 @@
                 && mLastFullscreenAppearance == fullscreenAppearance
                 && mLastDockedAppearance == dockedAppearance
                 && mLastBehavior == behavior
+                && mRequestedState.equals(win.getRequestedState())
+                && Objects.equals(mFocusedApp, win.mAttrs.packageName)
                 && mLastFocusIsFullscreen == isFullscreen
                 && mLastNonDockedRootTaskBounds.equals(mNonDockedRootTaskBounds)
                 && mLastDockedRootTaskBounds.equals(mDockedRootTaskBounds)) {
@@ -2537,6 +2703,8 @@
         mLastFullscreenAppearance = fullscreenAppearance;
         mLastDockedAppearance = dockedAppearance;
         mLastBehavior = behavior;
+        mRequestedState.set(win.getRequestedState(), true /* copySources */);
+        mFocusedApp = win.mAttrs.packageName;
         mLastFocusIsFullscreen = isFullscreen;
         mLastNonDockedRootTaskBounds.set(mNonDockedRootTaskBounds);
         mLastDockedRootTaskBounds.set(mDockedRootTaskBounds);
@@ -2555,7 +2723,7 @@
                 final int displayId = getDisplayId();
                 statusBar.setDisableFlags(displayId, disableFlags, cause);
                 statusBar.onSystemBarAttributesChanged(displayId, appearance, appearanceRegions,
-                        isNavbarColorManagedByIme, behavior, isFullscreen);
+                        isNavbarColorManagedByIme, behavior, mRequestedState, mFocusedApp);
 
             }
         });
@@ -2572,8 +2740,7 @@
 
     @VisibleForTesting
     @Nullable
-    static WindowState chooseNavigationColorWindowLw(WindowState opaque,
-            WindowState opaqueOrDimming, WindowState imeWindow,
+    static WindowState chooseNavigationColorWindowLw(WindowState candidate, WindowState imeWindow,
             @NavigationBarPosition int navBarPosition) {
         // If the IME window is visible and FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS is set, then IME
         // window can be navigation color window.
@@ -2582,71 +2749,59 @@
                 && navBarPosition == NAV_BAR_BOTTOM
                 && (imeWindow.mAttrs.flags
                         & WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
-
-        if (opaque != null && opaqueOrDimming == opaque) {
-            // If the top fullscreen-or-dimming window is also the top fullscreen, respect it
-            // unless IME window is also eligible, since currently the IME window is always show
-            // above the opaque fullscreen app window, regardless of the IME target window.
-            // TODO(b/31559891): Maybe we need to revisit this condition once b/31559891 is fixed.
-            return imeWindowCanNavColorWindow ? imeWindow : opaque;
-        }
-
-        if (opaqueOrDimming == null || !opaqueOrDimming.isDimming()) {
-            // No dimming window is involved. Determine the result only with the IME window.
-            return imeWindowCanNavColorWindow ? imeWindow : null;
-        }
-
         if (!imeWindowCanNavColorWindow) {
-            // No IME window is involved. Determine the result only with opaqueOrDimming.
-            return opaqueOrDimming;
+            // No IME window is involved. Determine the result only with candidate window.
+            return candidate;
         }
 
-        // The IME window and the dimming window are competing.  Check if the dimming window can be
-        // IME target or not.
-        if (LayoutParams.mayUseInputMethod(opaqueOrDimming.mAttrs.flags)) {
-            // The IME window is above the dimming window.
-            return imeWindow;
-        } else {
-            // The dimming window is above the IME window.
-            return opaqueOrDimming;
+        if (candidate != null && candidate.isDimming()) {
+            // The IME window and the dimming window are competing. Check if the dimming window can
+            // be IME target or not.
+            if (LayoutParams.mayUseInputMethod(candidate.mAttrs.flags)) {
+                // The IME window is above the dimming window.
+                return imeWindow;
+            } else {
+                // The dimming window is above the IME window.
+                return candidate;
+            }
         }
+
+        return imeWindow;
     }
 
     @VisibleForTesting
-    int updateLightNavigationBarLw(int appearance, WindowState opaque,
-            WindowState opaqueOrDimming, WindowState imeWindow, WindowState navColorWin) {
-
-        if (navColorWin != null) {
-            if (navColorWin == imeWindow || navColorWin == opaque) {
-                // Respect the light flag.
-                appearance &= ~APPEARANCE_LIGHT_NAVIGATION_BARS;
-                appearance |= navColorWin.mAttrs.insetsFlags.appearance
-                        & APPEARANCE_LIGHT_NAVIGATION_BARS;
-            } else if (navColorWin == opaqueOrDimming && navColorWin.isDimming()) {
-                // Clear the light flag for dimming window.
-                appearance &= ~APPEARANCE_LIGHT_NAVIGATION_BARS;
-            }
-        }
-        if (!isLightBarAllowed(navColorWin, ITYPE_NAVIGATION_BAR)) {
+    int updateLightNavigationBarLw(int appearance, WindowState navColorWin) {
+        if (navColorWin == null || navColorWin.isDimming()
+                || !isLightBarAllowed(navColorWin, ITYPE_NAVIGATION_BAR)) {
+            // Clear the light flag while not allowed.
             appearance &= ~APPEARANCE_LIGHT_NAVIGATION_BARS;
+            return appearance;
         }
+
+        // Respect the light flag of navigation color window.
+        appearance &= ~APPEARANCE_LIGHT_NAVIGATION_BARS;
+        appearance |= navColorWin.mAttrs.insetsFlags.appearance
+                & APPEARANCE_LIGHT_NAVIGATION_BARS;
         return appearance;
     }
 
     private int updateSystemBarsLw(WindowState win, int disableFlags) {
-        final boolean dockedRootTaskVisible = mDisplayContent.getDefaultTaskDisplayArea()
-                .isRootTaskVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
-        final boolean resizing = mDisplayContent.getDockedDividerController().isResizing();
+        final TaskDisplayArea defaultTaskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea();
+        final boolean multiWindowTaskVisible =
+                defaultTaskDisplayArea.isRootTaskVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)
+                        || defaultTaskDisplayArea.isRootTaskVisible(WINDOWING_MODE_MULTI_WINDOW);
+        final boolean freeformRootTaskVisible =
+                defaultTaskDisplayArea.isRootTaskVisible(WINDOWING_MODE_FREEFORM);
 
-        // We need to force system bars when the docked root task is visible, when the freeform
-        // root task is focused but also when we are resizing for the transitions when docked
-        // root task visibility changes.
-        mForceShowSystemBars = dockedRootTaskVisible || win.inFreeformWindowingMode() || resizing;
+        // We need to force showing system bars when the multi-window or freeform root task is
+        // visible.
+        mForceShowSystemBars = multiWindowTaskVisible || freeformRootTaskVisible;
+        mDisplayContent.getInsetsPolicy().updateBarControlTarget(win);
 
         int appearance = APPEARANCE_OPAQUE_NAVIGATION_BARS | APPEARANCE_OPAQUE_STATUS_BARS;
-
         appearance = configureStatusBarOpacity(appearance);
-        appearance = configureNavBarOpacity(appearance, dockedRootTaskVisible, resizing);
+        appearance = configureNavBarOpacity(appearance, multiWindowTaskVisible,
+                freeformRootTaskVisible);
 
         final boolean requestHideNavBar = !win.getRequestedVisibility(ITYPE_NAVIGATION_BAR);
         final long now = SystemClock.uptimeMillis();
@@ -2692,7 +2847,24 @@
 
     private Rect getBarContentFrameForWindow(WindowState win, int windowType) {
         final Rect rotatedBarFrame = win.mToken.getFixedRotationBarContentFrame(windowType);
-        return rotatedBarFrame != null ? rotatedBarFrame : mBarContentFrames.get(windowType);
+        if (rotatedBarFrame != null) {
+            return rotatedBarFrame;
+        }
+        if (!INSETS_LAYOUT_GENERALIZATION) {
+            return mBarContentFrames.get(windowType);
+        }
+        // We only need a window specific information for the fixed rotation, use raw insets state
+        // for all other cases.
+        InsetsState insetsState = mDisplayContent.getInsetsStateController().getRawInsetsState();
+        final Rect tmpRect = new Rect();
+        if (windowType == TYPE_NAVIGATION_BAR) {
+            tmpRect.set(insetsState.getSource(InsetsState.ITYPE_NAVIGATION_BAR).getFrame());
+        }
+        if (windowType == TYPE_STATUS_BAR) {
+            tmpRect.set(insetsState.getSource(InsetsState.ITYPE_STATUS_BAR).getFrame());
+        }
+        tmpRect.intersect(mDisplayContent.mDisplayFrames.mDisplayCutoutSafe);
+        return tmpRect;
     }
 
     /**
@@ -2748,53 +2920,35 @@
      * @return the current visibility flags with the nav-bar opacity related flags toggled based
      *         on the nav bar opacity rules chosen by {@link #mNavBarOpacityMode}.
      */
-    private int configureNavBarOpacity(int appearance, boolean dockedRootTaskVisible,
-            boolean isDockedDividerResizing) {
-        final boolean freeformRootTaskVisible = mDisplayContent.getDefaultTaskDisplayArea()
-                .isRootTaskVisible(WINDOWING_MODE_FREEFORM);
-        final boolean fullscreenDrawsBackground =
-                drawsBarBackground(mTopFullscreenOpaqueWindowState);
-        final boolean dockedDrawsBackground =
-                drawsBarBackground(mTopDockedOpaqueWindowState);
+    private int configureNavBarOpacity(int appearance, boolean multiWindowTaskVisible,
+            boolean freeformRootTaskVisible) {
+        final boolean drawBackground = drawsBarBackground(mNavBarBackgroundWindow);
 
         if (mNavBarOpacityMode == NAV_BAR_FORCE_TRANSPARENT) {
-            if (fullscreenDrawsBackground && dockedDrawsBackground) {
+            if (drawBackground) {
                 appearance = clearNavBarOpaqueFlag(appearance);
-            } else if (dockedRootTaskVisible) {
-                appearance = setNavBarOpaqueFlag(appearance);
             }
         } else if (mNavBarOpacityMode == NAV_BAR_OPAQUE_WHEN_FREEFORM_OR_DOCKED) {
-            if (dockedRootTaskVisible || freeformRootTaskVisible || isDockedDividerResizing) {
+            if (multiWindowTaskVisible || freeformRootTaskVisible) {
                 if (mIsFreeformWindowOverlappingWithNavBar) {
                     appearance = clearNavBarOpaqueFlag(appearance);
-                } else {
-                    appearance = setNavBarOpaqueFlag(appearance);
                 }
-            } else if (fullscreenDrawsBackground) {
+            } else if (drawBackground) {
                 appearance = clearNavBarOpaqueFlag(appearance);
             }
         } else if (mNavBarOpacityMode == NAV_BAR_TRANSLUCENT_WHEN_FREEFORM_OPAQUE_OTHERWISE) {
-            if (isDockedDividerResizing) {
-                appearance = setNavBarOpaqueFlag(appearance);
-            } else if (freeformRootTaskVisible) {
+            if (freeformRootTaskVisible) {
                 appearance = clearNavBarOpaqueFlag(appearance);
-            } else {
-                appearance = setNavBarOpaqueFlag(appearance);
             }
         }
 
-        if (!isFullyTransparentAllowed(mTopFullscreenOpaqueWindowState, TYPE_NAVIGATION_BAR)
-                || !isFullyTransparentAllowed(mTopDockedOpaqueWindowState, TYPE_NAVIGATION_BAR)) {
+        if (!isFullyTransparentAllowed(mNavBarBackgroundWindow, TYPE_NAVIGATION_BAR)) {
             appearance |= APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS;
         }
 
         return appearance;
     }
 
-    private int setNavBarOpaqueFlag(int appearance) {
-        return appearance | APPEARANCE_OPAQUE_NAVIGATION_BARS;
-    }
-
     private int clearNavBarOpaqueFlag(int appearance) {
         return appearance & ~APPEARANCE_OPAQUE_NAVIGATION_BARS;
     }
@@ -2966,10 +3120,13 @@
             pw.print(prefix); pw.print("mTopFullscreenOpaqueOrDimmingWindowState=");
             pw.println(mTopFullscreenOpaqueOrDimmingWindowState);
         }
-        if (mForcingShowNavBar) {
-            pw.print(prefix); pw.print("mForcingShowNavBar="); pw.println(mForcingShowNavBar);
-            pw.print(prefix); pw.print("mForcingShowNavBarLayer=");
-            pw.println(mForcingShowNavBarLayer);
+        if (mNavBarColorWindowCandidate != null) {
+            pw.print(prefix); pw.print("mNavBarColorWindowCandidate=");
+            pw.println(mNavBarColorWindowCandidate);
+        }
+        if (mNavBarBackgroundWindow != null) {
+            pw.print(prefix); pw.print("mNavBarBackgroundWindow=");
+            pw.println(mNavBarBackgroundWindow);
         }
         pw.print(prefix); pw.print("mTopIsFullscreen="); pw.println(mTopIsFullscreen);
         pw.print(prefix); pw.print("mForceStatusBar="); pw.print(mForceStatusBar);
@@ -3051,13 +3208,17 @@
     }
 
     @VisibleForTesting
-    static boolean isOverlappingWithNavBar(WindowState targetWindow, WindowState navBarWindow) {
+    static boolean isOverlappingWithNavBar(@NonNull WindowState targetWindow,
+            WindowState navBarWindow) {
         if (navBarWindow == null || !navBarWindow.isVisible()
                 || targetWindow.mActivityRecord == null || !targetWindow.isVisible()) {
             return false;
         }
 
-        return Rect.intersects(targetWindow.getFrame(), navBarWindow.getFrame());
+        // When the window is dimming means it's requesting dim layer to its host container, so
+        // checking whether it's overlapping with navigation bar by its container's bounds.
+        return Rect.intersects(targetWindow.isDimming()
+                ? targetWindow.getBounds() : targetWindow.getFrame(), navBarWindow.getFrame());
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
index 316c20b..2f0d703 100644
--- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
+++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
@@ -24,22 +24,22 @@
 
 /** Helper class to ensure activities are in the right visible state for a container. */
 class EnsureActivitiesVisibleHelper {
-    private final Task mTask;
+    private final TaskFragment mTaskFragment;
     private ActivityRecord mTop;
     private ActivityRecord mStarting;
     private boolean mAboveTop;
     private boolean mContainerShouldBeVisible;
-    private boolean mBehindFullscreenActivity;
+    private boolean mBehindFullyOccludedContainer;
     private int mConfigChanges;
     private boolean mPreserveWindows;
     private boolean mNotifyClients;
 
-    EnsureActivitiesVisibleHelper(Task container) {
-        mTask = container;
+    EnsureActivitiesVisibleHelper(TaskFragment container) {
+        mTaskFragment = container;
     }
 
     /**
-     * Update all attributes except {@link mTask} to use in subsequent calculations.
+     * Update all attributes except {@link mTaskFragment} to use in subsequent calculations.
      *
      * @param starting The activity that is being started
      * @param configChanges Parts of the configuration that changed for this activity for evaluating
@@ -51,12 +51,12 @@
     void reset(ActivityRecord starting, int configChanges, boolean preserveWindows,
             boolean notifyClients) {
         mStarting = starting;
-        mTop = mTask.topRunningActivity();
+        mTop = mTaskFragment.topRunningActivity();
         // If the top activity is not fullscreen, then we need to make sure any activities under it
         // are now visible.
         mAboveTop = mTop != null;
-        mContainerShouldBeVisible = mTask.shouldBeVisible(mStarting);
-        mBehindFullscreenActivity = !mContainerShouldBeVisible;
+        mContainerShouldBeVisible = mTaskFragment.shouldBeVisible(mStarting);
+        mBehindFullyOccludedContainer = !mContainerShouldBeVisible;
         mConfigChanges = configChanges;
         mPreserveWindows = preserveWindows;
         mNotifyClients = notifyClients;
@@ -85,22 +85,35 @@
             Slog.v(TAG_VISIBILITY, "ensureActivitiesVisible behind " + mTop
                     + " configChanges=0x" + Integer.toHexString(configChanges));
         }
-        if (mTop != null) {
-            mTask.checkTranslucentActivityWaiting(mTop);
+        if (mTop != null && mTaskFragment.asTask() != null) {
+            // TODO(14709632): Check if this needed to be implemented in TaskFragment.
+            mTaskFragment.asTask().checkTranslucentActivityWaiting(mTop);
         }
 
         // We should not resume activities that being launched behind because these
         // activities are actually behind other fullscreen activities, but still required
         // to be visible (such as performing Recents animation).
         final boolean resumeTopActivity = mTop != null && !mTop.mLaunchTaskBehind
-                && mTask.isTopActivityFocusable()
-                && (starting == null || !starting.isDescendantOf(mTask));
+                && mTaskFragment.isTopActivityFocusable()
+                && (starting == null || !starting.isDescendantOf(mTaskFragment));
 
-        mTask.forAllActivities(a -> {
-            setActivityVisibilityState(a, starting, resumeTopActivity);
-        });
-        if (mTask.mAtmService.getTransitionController().getTransitionPlayer() != null) {
-            mTask.getDisplayContent().mWallpaperController.adjustWallpaperWindows();
+        for (int i = mTaskFragment.mChildren.size() - 1; i >= 0; --i) {
+            final WindowContainer child = mTaskFragment.mChildren.get(i);
+            if (child.asTaskFragment() != null) {
+                final TaskFragment childTaskFragment = child.asTaskFragment();
+                childTaskFragment.updateActivityVisibilities(starting, configChanges,
+                        preserveWindows, notifyClients);
+                mBehindFullyOccludedContainer = childTaskFragment.getBounds().equals(
+                        mTaskFragment.getBounds());
+                if (mAboveTop && mTop.getTaskFragment() == childTaskFragment) {
+                    mAboveTop = false;
+                }
+            } else if (child.asActivityRecord() != null) {
+                setActivityVisibilityState(child.asActivityRecord(), starting, resumeTopActivity);
+            }
+        }
+        if (mTaskFragment.mAtmService.getTransitionController().getTransitionPlayer() != null) {
+            mTaskFragment.getDisplayContent().mWallpaperController.adjustWallpaperWindows();
         }
     }
 
@@ -112,7 +125,7 @@
         }
         mAboveTop = false;
 
-        r.updateVisibilityIgnoringKeyguard(mBehindFullscreenActivity);
+        r.updateVisibilityIgnoringKeyguard(mBehindFullyOccludedContainer);
         final boolean reallyVisible = r.shouldBeVisibleUnchecked();
 
         // Check whether activity should be visible without Keyguard influence
@@ -122,11 +135,11 @@
                 if (DEBUG_VISIBILITY) {
                     Slog.v(TAG_VISIBILITY, "Fullscreen: at " + r
                             + " containerVisible=" + mContainerShouldBeVisible
-                            + " behindFullscreen=" + mBehindFullscreenActivity);
+                            + " behindFullyOccluded=" + mBehindFullyOccludedContainer);
                 }
-                mBehindFullscreenActivity = true;
+                mBehindFullyOccludedContainer = true;
             } else {
-                mBehindFullscreenActivity = false;
+                mBehindFullyOccludedContainer = false;
             }
         }
 
@@ -173,24 +186,25 @@
                 Slog.v(TAG_VISIBILITY, "Make invisible? " + r
                         + " finishing=" + r.finishing + " state=" + r.getState()
                         + " containerShouldBeVisible=" + mContainerShouldBeVisible
-                        + " behindFullscreenActivity=" + mBehindFullscreenActivity
+                        + " behindFullyOccludedContainer=" + mBehindFullyOccludedContainer
                         + " mLaunchTaskBehind=" + r.mLaunchTaskBehind);
             }
             r.makeInvisible();
         }
 
-        if (!mBehindFullscreenActivity && mTask.isActivityTypeHome() && r.isRootOfTask()) {
+        if (!mBehindFullyOccludedContainer && mTaskFragment.isActivityTypeHome()
+                && r.isRootOfTask()) {
             if (DEBUG_VISIBILITY) {
-                Slog.v(TAG_VISIBILITY, "Home task: at " + mTask
+                Slog.v(TAG_VISIBILITY, "Home task: at " + mTaskFragment
                         + " containerShouldBeVisible=" + mContainerShouldBeVisible
-                        + " behindFullscreenActivity=" + mBehindFullscreenActivity);
+                        + " behindOccludedParentContainer=" + mBehindFullyOccludedContainer);
             }
             // No other task in the root home task should be visible behind the home activity.
             // Home activities is usually a translucent activity with the wallpaper behind
             // them. However, when they don't have the wallpaper behind them, we want to
             // show activities in the next application root task behind them vs. another
             // task in the root home task like recents.
-            mBehindFullscreenActivity = true;
+            mBehindFullyOccludedContainer = true;
         }
     }
 
@@ -219,7 +233,8 @@
             r.setVisibility(true);
         }
         if (r != starting) {
-            mTask.mTaskSupervisor.startSpecificActivity(r, andResume, true /* checkConfig */);
+            mTaskFragment.mTaskSupervisor.startSpecificActivity(r, andResume,
+                    true /* checkConfig */);
         }
     }
 }
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index da47328..4f6a693 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -29,6 +29,7 @@
 import static com.android.server.wm.WindowManagerService.H.UPDATE_MULTI_WINDOW_STACKS;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.os.Trace;
 import android.util.proto.ProtoOutputStream;
 import android.view.InsetsSource;
@@ -90,6 +91,16 @@
         onSourceChanged();
     }
 
+    @Override
+    void updateControlForTarget(@Nullable InsetsControlTarget target, boolean force) {
+        if (target != null && target.getWindow() != null) {
+            // ime control target could be a different window.
+            // Refer WindowState#getImeControlTarget().
+            target = target.getWindow().getImeControlTarget();
+        }
+        super.updateControlForTarget(target, force);
+    }
+
     private void onSourceChanged() {
         if (mLastSource.equals(mSource)) {
             return;
diff --git a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
index 747d365..f3b9cdf 100644
--- a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
+++ b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
@@ -20,6 +20,7 @@
 import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
+import static android.window.DisplayAreaOrganizer.KEY_ROOT_DISPLAY_AREA_ID;
 
 import android.animation.ArgbEvaluator;
 import android.animation.ValueAnimator;
@@ -420,7 +421,7 @@
         }
 
         final Bundle options = new Bundle();
-        options.putInt(DisplayAreaPolicyBuilder.KEY_ROOT_DISPLAY_AREA_ID, rootDisplayAreaId);
+        options.putInt(KEY_ROOT_DISPLAY_AREA_ID, rootDisplayAreaId);
         return options;
     }
 
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index f2f1926..6b126ca 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -18,8 +18,6 @@
 
 import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
 import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.view.InsetsController.ANIMATION_TYPE_HIDE;
 import static android.view.InsetsController.ANIMATION_TYPE_SHOW;
 import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_HIDDEN;
@@ -135,11 +133,8 @@
             abortTransient();
         }
         mFocusedWin = focusedWin;
-        boolean forceShowsSystemBarsForWindowingMode = forceShowsSystemBarsForWindowingMode();
-        InsetsControlTarget statusControlTarget = getStatusControlTarget(focusedWin,
-                forceShowsSystemBarsForWindowingMode);
-        InsetsControlTarget navControlTarget = getNavControlTarget(focusedWin,
-                forceShowsSystemBarsForWindowingMode);
+        InsetsControlTarget statusControlTarget = getStatusControlTarget(focusedWin);
+        InsetsControlTarget navControlTarget = getNavControlTarget(focusedWin);
         mStateController.onBarControlTargetChanged(statusControlTarget,
                 getFakeControlTarget(focusedWin, statusControlTarget),
                 navControlTarget,
@@ -304,8 +299,7 @@
         return realControlTarget == mDummyControlTarget ? focused : null;
     }
 
-    private @Nullable InsetsControlTarget getStatusControlTarget(@Nullable WindowState focusedWin,
-            boolean forceShowsSystemBarsForWindowingMode) {
+    private @Nullable InsetsControlTarget getStatusControlTarget(@Nullable WindowState focusedWin) {
         if (mShowingTransientTypes.indexOf(ITYPE_STATUS_BAR) != -1) {
             return mDummyControlTarget;
         }
@@ -319,10 +313,9 @@
                     focusedWin.mAttrs.packageName);
             return mDisplayContent.mRemoteInsetsControlTarget;
         }
-        if (forceShowsSystemBarsForWindowingMode) {
-            // Status bar is forcibly shown for the windowing mode which is a steady state.
-            // We don't want the client to control the status bar, and we will dispatch the real
-            // visibility of status bar to the client.
+        if (mPolicy.areSystemBarsForcedShownLw()) {
+            // Status bar is forcibly shown. We don't want the client to control the status bar, and
+            // we will dispatch the real visibility of status bar to the client.
             return null;
         }
         if (forceShowsStatusBarTransiently()) {
@@ -350,8 +343,7 @@
                 && !win.inMultiWindowMode();
     }
 
-    private @Nullable InsetsControlTarget getNavControlTarget(@Nullable WindowState focusedWin,
-            boolean forceShowsSystemBarsForWindowingMode) {
+    private @Nullable InsetsControlTarget getNavControlTarget(@Nullable WindowState focusedWin) {
         final WindowState imeWin = mDisplayContent.mInputMethodWindow;
         if (imeWin != null && imeWin.isVisible()) {
             // Force showing navigation bar while IME is visible.
@@ -369,10 +361,9 @@
                     focusedWin.mAttrs.packageName);
             return mDisplayContent.mRemoteInsetsControlTarget;
         }
-        if (forceShowsSystemBarsForWindowingMode) {
-            // Navigation bar is forcibly shown for the windowing mode which is a steady state.
-            // We don't want the client to control the navigation bar, and we will dispatch the real
-            // visibility of navigation bar to the client.
+        if (mPolicy.areSystemBarsForcedShownLw()) {
+            // Navigation bar is forcibly shown. We don't want the client to control the navigation
+            // bar, and we will dispatch the real visibility of navigation bar to the client.
             return null;
         }
         if (forceShowsNavigationBarTransiently()) {
@@ -417,19 +408,6 @@
                 && (win.mAttrs.privateFlags & PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION) != 0;
     }
 
-    private boolean forceShowsSystemBarsForWindowingMode() {
-        final boolean isDockedRootTaskVisible = mDisplayContent.getDefaultTaskDisplayArea()
-                .isRootTaskVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
-        final boolean isFreeformRootTaskVisible = mDisplayContent.getDefaultTaskDisplayArea()
-                .isRootTaskVisible(WINDOWING_MODE_FREEFORM);
-        final boolean isResizing = mDisplayContent.getDockedDividerController().isResizing();
-
-        // We need to force system bars when the docked root task is visible, when the freeform
-        // root task is visible but also when we are resizing for the transitions when docked
-        // root task visibility changes.
-        return isDockedRootTaskVisible || isFreeformRootTaskVisible || isResizing;
-    }
-
     @VisibleForTesting
     void startAnimation(boolean show, Runnable callback) {
         int typesReady = 0;
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 7daebff..cbd1314 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -307,11 +307,6 @@
             // to control the window for now.
             return;
         }
-        if (target != null && target.getWindow() != null) {
-            // ime control target could be a different window.
-            // Refer WindowState#getImeControlTarget().
-            target = target.getWindow().getImeControlTarget();
-        }
 
         if (mWin != null && mWin.getSurfaceControl() == null) {
             // if window doesn't have a surface, set it null and return.
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index 3dbe79d..5a249a5 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -58,10 +58,12 @@
     private final LetterboxSurface mLeft = new LetterboxSurface("left");
     private final LetterboxSurface mBottom = new LetterboxSurface("bottom");
     private final LetterboxSurface mRight = new LetterboxSurface("right");
-    // Prevents wallpaper from peeking through near rounded corners. It's not included in
-    // mSurfaces array since it isn't needed in methods like notIntersectsOrFullyContains
-    // or attachInput.
-    private final LetterboxSurface mBehind = new LetterboxSurface("behind");
+    // One surface that fills the whole window is used over multiple surfaces to:
+    // - Prevents wallpaper from peeking through near rounded corners.
+    // - For "blurred wallpaper" background, to avoid having visible border between surfaces.
+    // One surface approach isn't always preferred over multiple surfaces due to rendering cost
+    // for overlaping an app window and letterbox surfaces.
+    private final LetterboxSurface mFullWindowSurface = new LetterboxSurface("fullWindow");
     private final LetterboxSurface[] mSurfaces = { mLeft, mTop, mRight, mBottom };
 
     /**
@@ -104,7 +106,7 @@
         mLeft.layout(outer.left, outer.top, inner.left, outer.bottom, surfaceOrigin);
         mBottom.layout(outer.left, inner.bottom, outer.right, outer.bottom, surfaceOrigin);
         mRight.layout(inner.right, outer.top, outer.right, outer.bottom, surfaceOrigin);
-        mBehind.layout(inner.left, inner.top, inner.right, inner.bottom, surfaceOrigin);
+        mFullWindowSurface.layout(outer.left, outer.top, outer.right, outer.bottom, surfaceOrigin);
     }
 
 
@@ -168,37 +170,46 @@
         for (LetterboxSurface surface : mSurfaces) {
             surface.remove();
         }
-        mBehind.remove();
+        mFullWindowSurface.remove();
     }
 
     /** Returns whether a call to {@link #applySurfaceChanges} would change the surface. */
     public boolean needsApplySurfaceChanges() {
+        if (useFullWindowSurface()) {
+            return mFullWindowSurface.needsApplySurfaceChanges();
+        }
         for (LetterboxSurface surface : mSurfaces) {
             if (surface.needsApplySurfaceChanges()) {
                 return true;
             }
         }
-        if (mAreCornersRounded.get() && mBehind.needsApplySurfaceChanges()) {
-            return true;
-        }
         return false;
     }
 
     public void applySurfaceChanges(SurfaceControl.Transaction t) {
-        for (LetterboxSurface surface : mSurfaces) {
-            surface.applySurfaceChanges(t);
-        }
-        if (mAreCornersRounded.get()) {
-            mBehind.applySurfaceChanges(t);
+        if (useFullWindowSurface()) {
+            mFullWindowSurface.applySurfaceChanges(t);
+
+            for (LetterboxSurface surface : mSurfaces) {
+                surface.remove();
+            }
         } else {
-            mBehind.remove();
+            for (LetterboxSurface surface : mSurfaces) {
+                surface.applySurfaceChanges(t);
+            }
+
+            mFullWindowSurface.remove();
         }
     }
 
     /** Enables touches to slide into other neighboring surfaces. */
     void attachInput(WindowState win) {
-        for (LetterboxSurface surface : mSurfaces) {
-            surface.attachInput(win);
+        if (useFullWindowSurface()) {
+            mFullWindowSurface.attachInput(win);
+        } else {
+            for (LetterboxSurface surface : mSurfaces) {
+                surface.attachInput(win);
+            }
         }
     }
 
@@ -208,6 +219,16 @@
                 surface.mInputInterceptor.mWindowHandle.displayId = displayId;
             }
         }
+        if (mFullWindowSurface.mInputInterceptor != null) {
+            mFullWindowSurface.mInputInterceptor.mWindowHandle.displayId = displayId;
+        }
+    }
+
+    /**
+     * Returns {@code true} when using {@link #mFullWindowSurface} instead of {@link mSurfaces}.
+     */
+    private boolean useFullWindowSurface() {
+        return mAreCornersRounded.get() || mHasWallpaperBackgroundSupplier.get();
     }
 
     private static class InputInterceptor {
@@ -308,6 +329,10 @@
             mInputInterceptor = new InputInterceptor("Letterbox_" + mType + "_", win);
         }
 
+        boolean isRemoved() {
+            return mSurface != null || mInputInterceptor != null;
+        }
+
         public void remove() {
             if (mSurface != null) {
                 mTransactionFactory.get().remove(mSurface).apply();
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index 7174e68..eb7087c 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -21,7 +21,6 @@
 import android.graphics.Color;
 
 import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -105,12 +104,20 @@
      * com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio} will be ignored and
      * the framework implementation will be used to determine the aspect ratio.
      */
-    @VisibleForTesting
     void setFixedOrientationLetterboxAspectRatio(float aspectRatio) {
         mFixedOrientationLetterboxAspectRatio = aspectRatio;
     }
 
     /**
+     * Resets the aspect ratio of letterbox for fixed orientation to {@link
+     * com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio}.
+     */
+    void resetFixedOrientationLetterboxAspectRatio() {
+        mFixedOrientationLetterboxAspectRatio = mContext.getResources().getFloat(
+                com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio);
+    }
+
+    /**
      * Gets the aspect ratio of letterbox for fixed orientation.
      */
     float getFixedOrientationLetterboxAspectRatio() {
@@ -118,6 +125,25 @@
     }
 
     /**
+     * Overrides corners raidus for activities presented in the letterbox mode. If given value < 0,
+     * both it and a value of {@link
+     * com.android.internal.R.integer.config_letterboxActivityCornersRadius} will be ignored and
+     * and corners of the activity won't be rounded.
+     */
+    void setLetterboxActivityCornersRadius(int cornersRadius) {
+        mLetterboxActivityCornersRadius = cornersRadius;
+    }
+
+    /**
+     * Resets corners raidus for activities presented in the letterbox mode to {@link
+     * com.android.internal.R.integer.config_letterboxActivityCornersRadius}.
+     */
+    void resetLetterboxActivityCornersRadius() {
+        mLetterboxActivityCornersRadius = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_letterboxActivityCornersRadius);
+    }
+
+    /**
      * Whether corners of letterboxed activities are rounded.
      */
     boolean isLetterboxActivityCornersRounded() {
@@ -140,6 +166,25 @@
         return mLetterboxBackgroundColor;
     }
 
+
+    /**
+     * Sets color of letterbox background which is used when {@link
+     * #getLetterboxBackgroundType()} is {@link #LETTERBOX_BACKGROUND_SOLID_COLOR} or as
+     * fallback for other backfround types.
+     */
+    void setLetterboxBackgroundColor(Color color) {
+        mLetterboxBackgroundColor = color;
+    }
+
+    /**
+     * Resets color of letterbox background to {@link
+     * com.android.internal.R.color.config_letterboxBackgroundColor}.
+     */
+    void resetLetterboxBackgroundColor() {
+        mLetterboxBackgroundColor = Color.valueOf(mContext.getResources().getColor(
+                com.android.internal.R.color.config_letterboxBackgroundColor));
+    }
+
     /**
      * Gets {@link LetterboxBackgroundType} specified in {@link
      * com.android.internal.R.integer.config_letterboxBackgroundType} or over via ADB command.
@@ -149,6 +194,19 @@
         return mLetterboxBackgroundType;
     }
 
+    /** Sets letterbox background type. */
+    void setLetterboxBackgroundType(@LetterboxBackgroundType int backgroundType) {
+        mLetterboxBackgroundType = backgroundType;
+    }
+
+    /**
+     * Resets cletterbox background type to {@link
+     * com.android.internal.R.integer.config_letterboxBackgroundType}.
+     */
+    void resetLetterboxBackgroundType() {
+        mLetterboxBackgroundType = readLetterboxBackgroundTypeFromConfig(mContext);
+    }
+
     /** Returns a string representing the given {@link LetterboxBackgroundType}. */
     static String letterboxBackgroundTypeToString(
             @LetterboxBackgroundType int backgroundType) {
@@ -178,6 +236,27 @@
     }
 
     /**
+     * Overrides alpha of a black scrim shown over wallpaper for {@link
+     * #LETTERBOX_BACKGROUND_WALLPAPER} option in {@link mLetterboxBackgroundType}.
+     *
+     * <p>If given value is < 0 or >= 1, both it and a value of {@link
+     * com.android.internal.R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha} are ignored
+     * and 0.0 (transparent) is instead.
+     */
+    void setLetterboxBackgroundWallpaperDarkScrimAlpha(float alpha) {
+        mLetterboxBackgroundWallpaperDarkScrimAlpha = alpha;
+    }
+
+    /**
+     * Resets alpha of a black scrim shown over wallpaper letterbox background to {@link
+     * com.android.internal.R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha}.
+     */
+    void resetLetterboxBackgroundWallpaperDarkScrimAlpha() {
+        mLetterboxBackgroundWallpaperDarkScrimAlpha = mContext.getResources().getFloat(
+                com.android.internal.R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha);
+    }
+
+    /**
      * Gets alpha of a black scrim shown over wallpaper letterbox background.
      */
     float getLetterboxBackgroundWallpaperDarkScrimAlpha() {
@@ -185,6 +264,28 @@
     }
 
     /**
+     * Overrides blur radius for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option in
+     * {@link mLetterboxBackgroundType}.
+     *
+     * <p> If given value <= 0, both it and a value of {@link
+     * com.android.internal.R.dimen.config_letterboxBackgroundWallpaperBlurRadius} are ignored
+     * and 0 is used instead.
+     */
+    void setLetterboxBackgroundWallpaperBlurRadius(int radius) {
+        mLetterboxBackgroundWallpaperBlurRadius = radius;
+    }
+
+    /**
+     * Resets blur raidus for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option in {@link
+     * mLetterboxBackgroundType} to {@link
+     * com.android.internal.R.dimen.config_letterboxBackgroundWallpaperBlurRadius}.
+     */
+    void resetLetterboxBackgroundWallpaperBlurRadius() {
+        mLetterboxBackgroundWallpaperBlurRadius = mContext.getResources().getDimensionPixelSize(
+                com.android.internal.R.dimen.config_letterboxBackgroundWallpaperBlurRadius);
+    }
+
+    /**
      * Gets blur raidus for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option in {@link
      * mLetterboxBackgroundType}.
      */
@@ -211,9 +312,17 @@
      * com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier} are ignored and
      * central position (0.5) is used.
      */
-    @VisibleForTesting
     void setLetterboxHorizontalPositionMultiplier(float multiplier) {
         mLetterboxHorizontalPositionMultiplier = multiplier;
     }
 
+    /**
+     * Resets horizontal position of a center of the letterboxed app window to {@link
+     * com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier}.
+     */
+    void resetLetterboxHorizontalPositionMultiplier() {
+        mLetterboxHorizontalPositionMultiplier = mContext.getResources().getFloat(
+                com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier);
+    }
+
 }
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index a10b5d6..7cd9164 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -25,6 +25,8 @@
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS;
+import static com.android.server.wm.ActivityRecord.State.STOPPED;
+import static com.android.server.wm.ActivityRecord.State.STOPPING;
 import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE;
 import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_ORIGINAL_POSITION;
@@ -149,8 +151,7 @@
 
         // Invisible activity should be stopped. If the recents activity is alive and its doesn't
         // need to relaunch by current configuration, then it may be already in stopped state.
-        if (!targetActivity.isState(Task.ActivityState.STOPPING,
-                Task.ActivityState.STOPPED)) {
+        if (!targetActivity.isState(STOPPING, STOPPED)) {
             // Add to stopping instead of stop immediately. So the client has the chance to perform
             // traversal in non-stopped state (ViewRootImpl.mStopped) that would initialize more
             // things (e.g. the measure can be done earlier). The actual stop will be performed when
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 9a6a518..0b80bae 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -37,10 +37,10 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE;
 import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
 import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
-import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED;
 import static android.view.WindowManager.TRANSIT_NONE;
+import static android.view.WindowManager.TRANSIT_PIP;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
@@ -53,6 +53,11 @@
 import static com.android.server.policy.PhoneWindowManager.SYSTEM_DIALOG_REASON_ASSIST;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+import static com.android.server.wm.ActivityRecord.State.FINISHING;
+import static com.android.server.wm.ActivityRecord.State.PAUSED;
+import static com.android.server.wm.ActivityRecord.State.RESUMED;
+import static com.android.server.wm.ActivityRecord.State.STOPPED;
+import static com.android.server.wm.ActivityRecord.State.STOPPING;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RECENTS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ROOT_TASK;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
@@ -69,14 +74,9 @@
 import static com.android.server.wm.RootWindowContainerProto.IS_HOME_RECENTS_COMPONENT;
 import static com.android.server.wm.RootWindowContainerProto.KEYGUARD_CONTROLLER;
 import static com.android.server.wm.RootWindowContainerProto.WINDOW_CONTAINER;
-import static com.android.server.wm.Task.ActivityState.FINISHING;
-import static com.android.server.wm.Task.ActivityState.PAUSED;
-import static com.android.server.wm.Task.ActivityState.RESUMED;
-import static com.android.server.wm.Task.ActivityState.STOPPED;
-import static com.android.server.wm.Task.ActivityState.STOPPING;
 import static com.android.server.wm.Task.REPARENT_LEAVE_ROOT_TASK_IN_PLACE;
 import static com.android.server.wm.Task.REPARENT_MOVE_ROOT_TASK_TO_FRONT;
-import static com.android.server.wm.Task.TASK_VISIBILITY_INVISIBLE;
+import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_INVISIBLE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_TRACE;
@@ -816,8 +816,7 @@
         }
 
         // Initialize state of exiting tokens.
-        final int numDisplays = mChildren.size();
-        for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+        for (int displayNdx = 0; displayNdx < mChildren.size(); ++displayNdx) {
             final DisplayContent displayContent = mChildren.get(displayNdx);
             displayContent.setExitingTokensHasVisible(false);
         }
@@ -866,7 +865,7 @@
             recentsAnimationController.checkAnimationReady(defaultDisplay.mWallpaperController);
         }
 
-        for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+        for (int displayNdx = 0; displayNdx < mChildren.size(); ++displayNdx) {
             final DisplayContent displayContent = mChildren.get(displayNdx);
             if (displayContent.mWallpaperMayChange) {
                 if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "Wallpaper may change!  Adjusting");
@@ -928,12 +927,12 @@
         }
 
         // Time to remove any exiting tokens?
-        for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+        for (int displayNdx = mChildren.size() - 1; displayNdx >= 0; --displayNdx) {
             final DisplayContent displayContent = mChildren.get(displayNdx);
             displayContent.removeExistingTokensIfPossible();
         }
 
-        for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+        for (int displayNdx = 0; displayNdx < mChildren.size(); ++displayNdx) {
             final DisplayContent displayContent = mChildren.get(displayNdx);
             if (displayContent.pendingLayoutChanges != 0) {
                 displayContent.setLayoutNeeded();
@@ -1864,7 +1863,7 @@
         if (focusedRootTask == null) {
             return null;
         }
-        final ActivityRecord resumedActivity = focusedRootTask.getResumedActivity();
+        final ActivityRecord resumedActivity = focusedRootTask.getTopResumedActivity();
         if (resumedActivity != null && resumedActivity.app != null) {
             return resumedActivity;
         }
@@ -1886,11 +1885,11 @@
         // foreground.
         WindowProcessController fgApp = getItemFromRootTasks(rootTask -> {
             if (isTopDisplayFocusedRootTask(rootTask)) {
-                final ActivityRecord resumedActivity = rootTask.getResumedActivity();
+                final ActivityRecord resumedActivity = rootTask.getTopResumedActivity();
                 if (resumedActivity != null) {
                     return resumedActivity.app;
-                } else if (rootTask.getPausingActivity() != null) {
-                    return rootTask.getPausingActivity().app;
+                } else if (rootTask.getTopPausingActivity() != null) {
+                    return rootTask.getTopPausingActivity().app;
                 }
             }
             return null;
@@ -1916,7 +1915,8 @@
                     return;
                 }
 
-                if (rootTask.getVisibility(null /*starting*/) == TASK_VISIBILITY_INVISIBLE) {
+                if (rootTask.getVisibility(null /* starting */)
+                        == TASK_FRAGMENT_VISIBILITY_INVISIBLE) {
                     return;
                 }
 
@@ -1973,7 +1973,8 @@
      */
     void ensureActivitiesVisible(ActivityRecord starting, int configChanges,
             boolean preserveWindows, boolean notifyClients) {
-        if (mTaskSupervisor.inActivityVisibilityUpdate()) {
+        if (mTaskSupervisor.inActivityVisibilityUpdate()
+                || mTaskSupervisor.isRootVisibilityUpdateDeferred()) {
             // Don't do recursive work.
             return;
         }
@@ -2185,7 +2186,7 @@
                 // display area, so reparent.
                 rootTask.reparent(taskDisplayArea, true /* onTop */);
             }
-            mService.getTransitionController().requestTransitionIfNeeded(TRANSIT_CHANGE, rootTask);
+            mService.getTransitionController().requestTransitionIfNeeded(TRANSIT_PIP, rootTask);
 
             // Defer the windowing mode change until after the transition to prevent the activity
             // from doing work and changing the activity visuals while animating
@@ -2385,7 +2386,9 @@
                 if (displayShouldSleep) {
                     rootTask.goToSleepIfPossible(false /* shuttingDown */);
                 } else {
-                    rootTask.awakeFromSleepingLocked();
+                    rootTask.forAllLeafTasksAndLeafTaskFragments(
+                            taskFragment -> taskFragment.awakeFromSleeping(),
+                            true /* traverseTopToBottom */);
                     if (rootTask.isFocusedRootTaskOnDisplay()
                             && !mTaskSupervisor.getKeyguardController()
                             .isKeyguardOrAodShowing(display.mDisplayId)) {
@@ -2747,8 +2750,8 @@
 
         if (DEBUG_SWITCH) {
             Slog.v(TAG_SWITCH, "Destroying " + r + " in state " + r.getState()
-                    + " resumed=" + r.getTask().getResumedActivity() + " pausing="
-                    + r.getTask().getPausingActivity() + " for reason "
+                    + " resumed=" + r.getTask().getTopResumedActivity() + " pausing="
+                    + r.getTask().getTopPausingActivity() + " for reason "
                     + mDestroyAllActivitiesReason);
         }
 
@@ -3335,7 +3338,7 @@
             if (rootTask == null || !rootTask.hasActivity()) {
                 continue;
             }
-            final ActivityRecord resumedActivity = rootTask.getResumedActivity();
+            final ActivityRecord resumedActivity = rootTask.getTopResumedActivity();
             if (resumedActivity == null || !resumedActivity.idle) {
                 ProtoLog.d(WM_DEBUG_STATES, "allResumedActivitiesIdle: rootTask=%d %s "
                         + "not idle", rootTask.getRootTaskId(), resumedActivity);
@@ -3350,7 +3353,7 @@
     boolean allResumedActivitiesVisible() {
         boolean[] foundResumed = {false};
         final boolean foundInvisibleResumedActivity = forAllRootTasks(rootTask -> {
-            final ActivityRecord r = rootTask.getResumedActivity();
+            final ActivityRecord r = rootTask.getTopResumedActivity();
             if (r != null) {
                 if (!r.nowVisible) {
                     return true;
@@ -3368,7 +3371,7 @@
     boolean allPausedActivitiesComplete() {
         boolean[] pausing = {true};
         final boolean hasActivityNotCompleted = forAllLeafTasks(task -> {
-            final ActivityRecord r = task.getPausingActivity();
+            final ActivityRecord r = task.getTopPausingActivity();
             if (r != null && !r.isState(PAUSED, STOPPED, STOPPING, FINISHING)) {
                 ProtoLog.d(WM_DEBUG_STATES, "allPausedActivitiesComplete: "
                         + "r=%s state=%s", r, r.getState());
@@ -3543,6 +3546,8 @@
         }
     }
 
+    // TODO(b/191434136): handle this properly when we add multi-window support on secondary
+    //  display.
     private void calculateDefaultMinimalSizeOfResizeableTasks() {
         final Resources res = mService.mContext.getResources();
         final float minimalSize = res.getDimension(
diff --git a/services/core/java/com/android/server/wm/SafeActivityOptions.java b/services/core/java/com/android/server/wm/SafeActivityOptions.java
index 4892005..2d4aef6 100644
--- a/services/core/java/com/android/server/wm/SafeActivityOptions.java
+++ b/services/core/java/com/android/server/wm/SafeActivityOptions.java
@@ -78,6 +78,18 @@
     }
 
     /**
+     * Constructs a new instance from a bundle and provided pid/uid.
+     *
+     * @param bOptions The {@link ActivityOptions} as {@link Bundle}.
+     */
+    static SafeActivityOptions fromBundle(Bundle bOptions, int callingPid, int callingUid) {
+        return bOptions != null
+                ? new SafeActivityOptions(ActivityOptions.fromBundle(bOptions),
+                        callingPid, callingUid)
+                : null;
+    }
+
+    /**
      * Constructs a new instance and records {@link Binder#getCallingPid}/
      * {@link Binder#getCallingUid}. Thus, calling identity MUST NOT be cleared when constructing
      * this object.
@@ -91,6 +103,17 @@
     }
 
     /**
+     * Constructs a new instance.
+     *
+     * @param options The options to wrap.
+     */
+    private SafeActivityOptions(@Nullable ActivityOptions options, int callingPid, int callingUid) {
+        mOriginalCallingPid = callingPid;
+        mOriginalCallingUid = callingUid;
+        mOriginalOptions = options;
+    }
+
+    /**
      * Overrides options with options from a caller and records {@link Binder#getCallingPid}/
      * {@link Binder#getCallingUid}. Thus, calling identity MUST NOT be cleared when calling this
      * method.
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 777306a..c803f06 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -27,13 +27,10 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.PINNED_WINDOWING_MODE_ELEVATION_IN_DIP;
-import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.app.WindowConfiguration.activityTypeToString;
 import static android.app.WindowConfiguration.windowingModeToString;
@@ -43,7 +40,6 @@
 import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
 import static android.content.pm.ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY;
-import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING;
 import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY;
@@ -53,9 +49,6 @@
 import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_AND_PIPABLE_DEPRECATED;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
-import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
 import static android.view.Display.DEFAULT_DISPLAY;
@@ -65,7 +58,6 @@
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED;
-import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
 import static android.view.WindowManager.TRANSIT_NONE;
 import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
 import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
@@ -83,21 +75,18 @@
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
 import static com.android.server.wm.ActivityRecord.STARTING_WINDOW_SHOWN;
+import static com.android.server.wm.ActivityRecord.State.INITIALIZING;
+import static com.android.server.wm.ActivityRecord.State.PAUSED;
+import static com.android.server.wm.ActivityRecord.State.PAUSING;
+import static com.android.server.wm.ActivityRecord.State.RESUMED;
+import static com.android.server.wm.ActivityRecord.State.STARTED;
 import static com.android.server.wm.ActivityRecord.TRANSFER_SPLASH_SCREEN_COPYING;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RECENTS;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RESULTS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TRANSITION;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_USER_LEAVING;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_ADD_REMOVE;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_APP;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_CLEANUP;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_LOCKTASK;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_PAUSE;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RECENTS;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RESULTS;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_ROOT_TASK;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_STATES;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_SWITCH;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_TASKS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_TRANSITION;
@@ -110,7 +99,6 @@
 import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
 import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.ActivityTaskSupervisor.REMOVE_FROM_RECENTS;
-import static com.android.server.wm.ActivityTaskSupervisor.dumpHistoryList;
 import static com.android.server.wm.ActivityTaskSupervisor.printThisActivity;
 import static com.android.server.wm.IdentifierProto.HASH_CODE;
 import static com.android.server.wm.IdentifierProto.TITLE;
@@ -121,21 +109,12 @@
 import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_LAUNCHABLE_PRIV;
 import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_PINNABLE;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
-import static com.android.server.wm.Task.ActivityState.PAUSED;
-import static com.android.server.wm.Task.ActivityState.PAUSING;
-import static com.android.server.wm.Task.ActivityState.RESUMED;
-import static com.android.server.wm.Task.ActivityState.STARTED;
-import static com.android.server.wm.Task.ActivityState.STOPPING;
-import static com.android.server.wm.TaskProto.ACTIVITY_TYPE;
 import static com.android.server.wm.TaskProto.AFFINITY;
 import static com.android.server.wm.TaskProto.BOUNDS;
 import static com.android.server.wm.TaskProto.CREATED_BY_ORGANIZER;
-import static com.android.server.wm.TaskProto.DISPLAY_ID;
 import static com.android.server.wm.TaskProto.FILLS_PARENT;
 import static com.android.server.wm.TaskProto.HAS_CHILD_PIP_ACTIVITY;
 import static com.android.server.wm.TaskProto.LAST_NON_FULLSCREEN_BOUNDS;
-import static com.android.server.wm.TaskProto.MIN_HEIGHT;
-import static com.android.server.wm.TaskProto.MIN_WIDTH;
 import static com.android.server.wm.TaskProto.ORIG_ACTIVITY;
 import static com.android.server.wm.TaskProto.REAL_ACTIVITY;
 import static com.android.server.wm.TaskProto.RESIZE_MODE;
@@ -143,7 +122,7 @@
 import static com.android.server.wm.TaskProto.ROOT_TASK_ID;
 import static com.android.server.wm.TaskProto.SURFACE_HEIGHT;
 import static com.android.server.wm.TaskProto.SURFACE_WIDTH;
-import static com.android.server.wm.TaskProto.WINDOW_CONTAINER;
+import static com.android.server.wm.TaskProto.TASK_FRAGMENT;
 import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
 import static com.android.server.wm.WindowContainerChildProto.TASK;
@@ -168,14 +147,7 @@
 import android.app.IActivityController;
 import android.app.PictureInPictureParams;
 import android.app.RemoteAction;
-import android.app.ResultInfo;
 import android.app.TaskInfo;
-import android.app.WindowConfiguration;
-import android.app.servertransaction.ActivityResultItem;
-import android.app.servertransaction.ClientTransaction;
-import android.app.servertransaction.NewIntentItem;
-import android.app.servertransaction.PauseActivityItem;
-import android.app.servertransaction.ResumeActivityItem;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -244,23 +216,21 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
-import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
 
-class Task extends WindowContainer<WindowContainer> {
+/**
+ * {@link Task} is a TaskFragment that can contain a group of activities to perform a certain job.
+ * Activities of the same task affinities usually group in the same {@link Task}. A {@link Task}
+ * can also be an entity that showing in the Recents Screen for a job that user interacted with.
+ * A {@link Task} can also contain other {@link Task}s.
+ */
+class Task extends TaskFragment {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "Task" : TAG_ATM;
-    static final String TAG_ADD_REMOVE = TAG + POSTFIX_ADD_REMOVE;
     private static final String TAG_RECENTS = TAG + POSTFIX_RECENTS;
-    private static final String TAG_LOCKTASK = TAG + POSTFIX_LOCKTASK;
     static final String TAG_TASKS = TAG + POSTFIX_TASKS;
-    private static final String TAG_APP = TAG + POSTFIX_APP;
     static final String TAG_CLEANUP = TAG + POSTFIX_CLEANUP;
-    private static final String TAG_PAUSE = TAG + POSTFIX_PAUSE;
-    private static final String TAG_RESULTS = TAG + POSTFIX_RESULTS;
-    private static final String TAG_ROOT_TASK = TAG + POSTFIX_ROOT_TASK;
-    private static final String TAG_STATES = TAG + POSTFIX_STATES;
     private static final String TAG_SWITCH = TAG + POSTFIX_SWITCH;
     private static final String TAG_TRANSITION = TAG + POSTFIX_TRANSITION;
     private static final String TAG_USER_LEAVING = TAG + POSTFIX_USER_LEAVING;
@@ -303,10 +273,6 @@
     private static final String ATTR_LAST_SNAPSHOT_CONTENT_INSETS = "last_snapshot_content_insets";
     private static final String ATTR_LAST_SNAPSHOT_BUFFER_SIZE = "last_snapshot_buffer_size";
 
-    // Set to false to disable the preview that is shown while a new activity
-    // is being started.
-    private static final boolean SHOW_APP_STARTING_PREVIEW = true;
-
     // How long to wait for all background Activities to redraw following a call to
     // convertToTranslucent().
     private static final long TRANSLUCENT_CONVERSION_TIMEOUT = 2000;
@@ -315,7 +281,6 @@
     // code.
     static final int PERSIST_TASK_VERSION = 1;
 
-    static final int INVALID_MIN_SIZE = -1;
     private float mShadowRadius = 0;
 
     /**
@@ -335,36 +300,6 @@
     // Do not move the root task as a part of reparenting
     static final int REPARENT_LEAVE_ROOT_TASK_IN_PLACE = 2;
 
-    @IntDef(prefix = {"TASK_VISIBILITY"}, value = {
-            TASK_VISIBILITY_VISIBLE,
-            TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
-            TASK_VISIBILITY_INVISIBLE,
-    })
-    @interface TaskVisibility {}
-
-    /** Task is visible. No other tasks on top that fully or partially occlude it. */
-    static final int TASK_VISIBILITY_VISIBLE = 0;
-
-    /** Task is partially occluded by other translucent task(s) on top of it. */
-    static final int TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT = 1;
-
-    /** Task is completely invisible. */
-    static final int TASK_VISIBILITY_INVISIBLE = 2;
-
-    enum ActivityState {
-        INITIALIZING,
-        STARTED,
-        RESUMED,
-        PAUSING,
-        PAUSED,
-        STOPPING,
-        STOPPED,
-        FINISHING,
-        DESTROYING,
-        DESTROYED,
-        RESTARTING_PROCESS
-    }
-
     // The topmost Activity passed to convertToTranslucent(). When non-null it means we are
     // waiting for all Activities in mUndrawnActivitiesBelowTopTranslucent to be removed as they
     // are drawn. When the last member of mUndrawnActivitiesBelowTopTranslucent is removed the
@@ -446,7 +381,6 @@
 
     CharSequence lastDescription; // Last description captured for this item.
 
-    Task mAdjacentTask; // Task adjacent to this one.
     int mAffiliatedTaskId; // taskId of parent affiliation or self if no parent.
     Task mPrevAffiliate; // previous task in affiliated chain.
     int mPrevAffiliateTaskId = INVALID_TASK_ID; // previous id for persistence.
@@ -458,21 +392,12 @@
     String mCallingPackage;
     String mCallingFeatureId;
 
-    private final Rect mTmpStableBounds = new Rect();
-    private final Rect mTmpNonDecorBounds = new Rect();
-    private final Rect mTmpBounds = new Rect();
-    private final Rect mTmpInsets = new Rect();
-    private final Rect mTmpFullBounds = new Rect();
     private static final Rect sTmpBounds = new Rect();
 
     // Last non-fullscreen bounds the task was launched in or resized to.
     // The information is persisted and used to determine the appropriate root task to launch the
     // task into on restore.
     Rect mLastNonFullscreenBounds = null;
-    // Minimal width and height of this task when it's resizeable. -1 means it should use the
-    // default minimal width/height.
-    int mMinWidth;
-    int mMinHeight;
 
     // The surface transition of the target when recents animation is finished.
     // This is originally introduced to carry out the current surface control position and window
@@ -497,10 +422,6 @@
     /** Used by fillTaskInfo */
     final TaskActivitiesReport mReuseActivitiesReport = new TaskActivitiesReport();
 
-    final ActivityTaskManagerService mAtmService;
-    final ActivityTaskSupervisor mTaskSupervisor;
-    final RootWindowContainer mRootWindowContainer;
-
     /* Unique identifier for this task. */
     final int mTaskId;
     /* User for which this task was created. */
@@ -571,29 +492,6 @@
     /** ActivityRecords that are exiting, but still on screen for animations. */
     final ArrayList<ActivityRecord> mExitingActivities = new ArrayList<>();
 
-    /**
-     * When we are in the process of pausing an activity, before starting the
-     * next one, this variable holds the activity that is currently being paused.
-     *
-     * Only set at leaf tasks.
-     */
-    @Nullable
-    private ActivityRecord mPausingActivity = null;
-
-    /**
-     * This is the last activity that we put into the paused state.  This is
-     * used to determine if we need to do an activity transition while sleeping,
-     * when we normally hold the top activity paused.
-     */
-    ActivityRecord mLastPausedActivity = null;
-
-    /**
-     * Current activity that is resumed, or null if there is none.
-     * Only set at leaf tasks.
-     */
-    @Nullable
-    private ActivityRecord mResumedActivity = null;
-
     private boolean mForceShowForAllUsers;
 
     /** When set, will force the task to report as invisible. */
@@ -641,121 +539,6 @@
     }
 
     private static final ResetTargetTaskHelper sResetTargetTaskHelper = new ResetTargetTaskHelper();
-    private final EnsureActivitiesVisibleHelper mEnsureActivitiesVisibleHelper =
-            new EnsureActivitiesVisibleHelper(this);
-    private final EnsureVisibleActivitiesConfigHelper mEnsureVisibleActivitiesConfigHelper =
-            new EnsureVisibleActivitiesConfigHelper();
-    private class EnsureVisibleActivitiesConfigHelper {
-        private boolean mUpdateConfig;
-        private boolean mPreserveWindow;
-        private boolean mBehindFullscreen;
-
-        void reset(boolean preserveWindow) {
-            mPreserveWindow = preserveWindow;
-            mUpdateConfig = false;
-            mBehindFullscreen = false;
-        }
-
-        void process(ActivityRecord start, boolean preserveWindow) {
-            if (start == null || !start.mVisibleRequested) {
-                return;
-            }
-            reset(preserveWindow);
-
-            final PooledFunction f = PooledLambda.obtainFunction(
-                    EnsureVisibleActivitiesConfigHelper::processActivity, this,
-                    PooledLambda.__(ActivityRecord.class));
-            forAllActivities(f, start, true /*includeBoundary*/, true /*traverseTopToBottom*/);
-            f.recycle();
-
-            if (mUpdateConfig) {
-                // Ensure the resumed state of the focus activity if we updated the configuration of
-                // any activity.
-                mRootWindowContainer.resumeFocusedTasksTopActivities();
-            }
-        }
-
-        boolean processActivity(ActivityRecord r) {
-            mUpdateConfig |= r.ensureActivityConfiguration(0 /*globalChanges*/, mPreserveWindow);
-            mBehindFullscreen |= r.occludesParent();
-            return mBehindFullscreen;
-        }
-    }
-
-    private final CheckBehindFullscreenActivityHelper mCheckBehindFullscreenActivityHelper =
-            new CheckBehindFullscreenActivityHelper();
-    private class CheckBehindFullscreenActivityHelper {
-        private boolean mAboveTop;
-        private boolean mBehindFullscreenActivity;
-        private ActivityRecord mToCheck;
-        private Consumer<ActivityRecord> mHandleBehindFullscreenActivity;
-        private boolean mHandlingOccluded;
-
-        private void reset(ActivityRecord toCheck,
-                Consumer<ActivityRecord> handleBehindFullscreenActivity) {
-            mToCheck = toCheck;
-            mHandleBehindFullscreenActivity = handleBehindFullscreenActivity;
-            mAboveTop = true;
-            mBehindFullscreenActivity = false;
-
-            if (!shouldBeVisible(null)) {
-                // The root task is not visible, so no activity in it should be displaying a
-                // starting window. Mark all activities below top and behind fullscreen.
-                mAboveTop = false;
-                mBehindFullscreenActivity = true;
-            }
-
-            mHandlingOccluded = mToCheck == null && mHandleBehindFullscreenActivity != null;
-        }
-
-        boolean process(ActivityRecord toCheck,
-                Consumer<ActivityRecord> handleBehindFullscreenActivity) {
-            reset(toCheck, handleBehindFullscreenActivity);
-
-            if (!mHandlingOccluded && mBehindFullscreenActivity) {
-                return true;
-            }
-
-            final ActivityRecord topActivity = topRunningActivity();
-            final PooledFunction f = PooledLambda.obtainFunction(
-                    CheckBehindFullscreenActivityHelper::processActivity, this,
-                    PooledLambda.__(ActivityRecord.class), topActivity);
-            forAllActivities(f);
-            f.recycle();
-
-            return mBehindFullscreenActivity;
-        }
-
-        /** Returns {@code true} to stop the outer loop and indicate the result is computed. */
-        private boolean processActivity(ActivityRecord r, ActivityRecord topActivity) {
-            if (mAboveTop) {
-                if (r == topActivity) {
-                    if (r == mToCheck) {
-                        // It is the top activity in a visible root task.
-                        mBehindFullscreenActivity = false;
-                        return true;
-                    }
-                    mAboveTop = false;
-                }
-                mBehindFullscreenActivity |= r.occludesParent();
-                return false;
-            }
-
-            if (mHandlingOccluded) {
-                // Iterating through all occluded activities.
-                if (mBehindFullscreenActivity) {
-                    mHandleBehindFullscreenActivity.accept(r);
-                }
-            } else if (r == mToCheck) {
-                return true;
-            } else if (mBehindFullscreenActivity) {
-                // It is occluded before {@param toCheck} is found.
-                return true;
-            }
-            mBehindFullscreenActivity |= r.occludesParent();
-            return false;
-        }
-    }
 
     private final FindRootHelper mFindRootHelper = new FindRootHelper();
     private class FindRootHelper {
@@ -819,18 +602,6 @@
      */
     private boolean mForceNotOrganized;
 
-    /**
-     * This task was created by the task organizer which has the following implementations.
-     * <ul>
-     *     <lis>The task won't be removed when it is empty. Removal has to be an explicit request
-     *     from the task organizer.</li>
-     *     <li>Unlike other non-root tasks, it's direct children are visible to the task
-     *     organizer for ordering purposes.</li>
-     * </ul>
-     */
-    @VisibleForTesting
-    boolean mCreatedByOrganizer;
-
     // Tracking cookie for the creation of this task.
     IBinder mLaunchCookie;
 
@@ -858,11 +629,8 @@
             IVoiceInteractionSession _voiceSession, IVoiceInteractor _voiceInteractor,
             boolean _createdByOrganizer, IBinder _launchCookie, boolean _deferTaskAppear,
             boolean _removeWithTaskOrganizer) {
-        super(atmService.mWindowManager);
+        super(atmService, null /* fragmentToken */, _createdByOrganizer);
 
-        mAtmService = atmService;
-        mTaskSupervisor = atmService.mTaskSupervisor;
-        mRootWindowContainer = mAtmService.mRootWindowContainer;
         mTaskId = _taskId;
         mUserId = _userId;
         mResizeMode = resizeMode;
@@ -875,7 +643,6 @@
                 : new PersistedTaskSnapshotData();
         // Tasks have no set orientation value (including SCREEN_ORIENTATION_UNSPECIFIED).
         setOrientation(SCREEN_ORIENTATION_UNSET);
-        mRemoteToken = new RemoteToken(this);
         affinityIntent = _affinityIntent;
         affinity = _affinity;
         rootAffinity = _rootAffinity;
@@ -913,7 +680,6 @@
         mHandler = new ActivityTaskHandler(mTaskSupervisor.mLooper);
         mCurrentUser = mAtmService.mAmInternal.getCurrentUserId();
 
-        mCreatedByOrganizer = _createdByOrganizer;
         mLaunchCookie = _launchCookie;
         mDeferTaskAppear = _deferTaskAppear;
         mRemoveWithTaskOrganizer = _removeWithTaskOrganizer;
@@ -1466,64 +1232,80 @@
         mRootWindowContainer.updateUIDsPresentOnDisplay();
     }
 
-    void cleanUpActivityReferences(ActivityRecord r) {
-        // mPausingActivity is set at leaf task
-        if (mPausingActivity != null && mPausingActivity == r) {
-            mPausingActivity = null;
+    /** Returns the currently topmost resumed activity. */
+    @Nullable
+    ActivityRecord getTopResumedActivity() {
+        if (!isLeafTask()) {
+            for (int i = mChildren.size() - 1; i >= 0; --i) {
+                ActivityRecord resumedActivity = mChildren.get(i).asTask().getTopResumedActivity();
+                if (resumedActivity != null) {
+                    return resumedActivity;
+                }
+            }
         }
 
-        if (mResumedActivity != null && mResumedActivity == r) {
-            setResumedActivity(null, "cleanUpActivityReferences");
+        final ActivityRecord taskResumedActivity = getResumedActivity();
+        ActivityRecord topResumedActivity = null;
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final WindowContainer child = mChildren.get(i);
+            if (child.asTaskFragment() != null) {
+                final ActivityRecord[] resumedActivity = new ActivityRecord[1];
+                child.asTaskFragment().forAllLeafTaskFragments(fragment -> {
+                    if (fragment.getResumedActivity() != null) {
+                        resumedActivity[0] = fragment.getResumedActivity();
+                        return true;
+                    }
+                    return false;
+                });
+                topResumedActivity = resumedActivity[0];
+            } else if (taskResumedActivity != null
+                    && child.asActivityRecord() == taskResumedActivity) {
+                topResumedActivity = taskResumedActivity;
+            }
+            if (topResumedActivity != null) {
+                return topResumedActivity;
+            }
         }
-
-        final WindowContainer parent = getParent();
-        if (parent != null && parent.asTask() != null) {
-            parent.asTask().cleanUpActivityReferences(r);
-            return;
-        }
-        r.removeTimeouts();
-        mExitingActivities.remove(r);
-    }
-
-    /** @return the currently resumed activity. */
-    ActivityRecord getResumedActivity() {
-        if (isLeafTask()) {
-            return mResumedActivity;
-        }
-
-        final Task task = getTask(t -> t.mResumedActivity != null, true /* traverseTopToBottom */);
-        return task != null ? task.mResumedActivity : null;
-    }
-
-    @VisibleForTesting
-    void setPausingActivity(ActivityRecord pausing) {
-        mPausingActivity = pausing;
+        return null;
     }
 
     /**
-     * @return the currently pausing activity of this task or the topmost pausing activity of the
-     * child tasks
+     * Returns the currently topmost pausing activity.
      */
-    ActivityRecord getPausingActivity() {
-        if (isLeafTask()) {
-            return mPausingActivity;
+    @Nullable
+    ActivityRecord getTopPausingActivity() {
+        if (!isLeafTask()) {
+            for (int i = mChildren.size() - 1; i >= 0; --i) {
+                ActivityRecord pausingActivity = mChildren.get(i).asTask().getTopPausingActivity();
+                if (pausingActivity != null) {
+                    return pausingActivity;
+                }
+            }
         }
 
-        final Task task = getTask(t -> t.mPausingActivity != null, true /* traverseTopToBottom */);
-        return task != null ? task.mPausingActivity : null;
-    }
-
-    void setResumedActivity(ActivityRecord r, String reason) {
-        warnForNonLeafTask("setResumedActivity");
-        if (mResumedActivity == r) {
-            return;
+        final ActivityRecord taskPausingActivity = getPausingActivity();
+        ActivityRecord topPausingActivity = null;
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final WindowContainer child = mChildren.get(i);
+            if (child.asTaskFragment() != null) {
+                final ActivityRecord[] pausingActivity = new ActivityRecord[1];
+                child.asTaskFragment().forAllLeafTaskFragments(fragment -> {
+                    if (fragment.getPausingActivity() != null) {
+                        pausingActivity[0] = fragment.getPausingActivity();
+                        return true;
+                    }
+                    return false;
+                });
+                topPausingActivity = pausingActivity[0];
+            } else if (taskPausingActivity != null
+                    && child.asActivityRecord() == taskPausingActivity) {
+                topPausingActivity = taskPausingActivity;
+            }
+            if (topPausingActivity != null) {
+                return topPausingActivity;
+            }
         }
-
-        if (ActivityTaskManagerDebugConfig.DEBUG_ROOT_TASK) Slog.d(TAG_ROOT_TASK,
-                "setResumedActivity task:" + this + " + from: "
-                + mResumedActivity + " to:" + r + " reason:" + reason);
-        mResumedActivity = r;
-        mTaskSupervisor.updateTopResumedActivityIfNeeded();
+        return null;
     }
 
     void updateTaskMovement(boolean toTop, int position) {
@@ -1562,11 +1344,6 @@
                 mTaskId, mUserId);
     }
 
-    void setAdjacentTask(Task adjacent) {
-        mAdjacentTask = adjacent;
-        adjacent.mAdjacentTask = this;
-    }
-
     void setTaskToAffiliateWith(Task taskToAffiliateWith) {
         closeRecentsChain();
         mAffiliatedTaskId = taskToAffiliateWith.mAffiliatedTaskId;
@@ -1675,22 +1452,7 @@
     }
 
     @Override
-    public int getActivityType() {
-        final int applicationType = super.getActivityType();
-        if (applicationType != ACTIVITY_TYPE_UNDEFINED || !hasChild()) {
-            return applicationType;
-        }
-        return getTopChild().getActivityType();
-    }
-
-    @Override
     void addChild(WindowContainer child, int index) {
-        // If this task had any child before we added this one.
-        boolean hadChild = hasChild();
-        // getActivityType() looks at the top child, so we need to read the type before adding
-        // a new child in case the new child is on top and UNDEFINED.
-        final int activityType = getActivityType();
-
         index = getAdjustedChildPosition(child, index);
         super.addChild(child, index);
 
@@ -1706,10 +1468,17 @@
         // now that this record is in a new task.
         mRootWindowContainer.updateUIDsPresentOnDisplay();
 
-        final ActivityRecord r = child.asActivityRecord();
-        if (r == null) return;
+        // Only pass minimum dimensions for pure TaskFragment. Task's minimum dimensions must be
+        // passed from Task constructor.
+        final TaskFragment childTaskFrag = child.asTaskFragment();
+        if (childTaskFrag != null && childTaskFrag.asTask() == null) {
+            childTaskFrag.setMinDimensions(mMinWidth, mMinHeight);
+        }
+    }
 
-        r.inHistory = true;
+    /** Called when an {@link ActivityRecord} is added as a descendant */
+    void onDescendantActivityAdded(boolean hadChild, int activityType, ActivityRecord r) {
+        warnForNonLeafTask("onDescendantActivityAdded");
 
         // Only set this based on the first activity
         if (!hadChild) {
@@ -1736,10 +1505,6 @@
         updateEffectiveIntent();
     }
 
-    void addChild(ActivityRecord r) {
-        addChild(r, Integer.MAX_VALUE /* add on top */);
-    }
-
     @Override
     void removeChild(WindowContainer child) {
         removeChild(child, "removeChild");
@@ -1985,32 +1750,6 @@
                 && supportsMultiWindowInDisplayArea(tda);
     }
 
-    boolean supportsMultiWindow() {
-        return supportsMultiWindowInDisplayArea(getDisplayArea());
-    }
-
-    /**
-     * @return whether this task supports multi-window if it is in the given
-     *         {@link TaskDisplayArea}.
-     */
-    boolean supportsMultiWindowInDisplayArea(@Nullable TaskDisplayArea tda) {
-        if (!mAtmService.mSupportsMultiWindow) {
-            return false;
-        }
-        if (tda == null) {
-            Slog.w(TAG_TASKS, "Can't find TaskDisplayArea to determine support for multi"
-                    + " window. Task id=" + mTaskId + " attached=" + isAttached());
-            return false;
-        }
-
-        if (!isResizeable() && !tda.supportsNonResizableMultiWindow()) {
-            // Not support non-resizable in multi window.
-            return false;
-        }
-
-        return tda.supportsActivityMinWidthHeightMultiWindow(mMinWidth, mMinHeight);
-    }
-
     /**
      * Check whether this task can be launched on the specified display.
      *
@@ -2146,60 +1885,6 @@
         }
     }
 
-    void adjustForMinimalTaskDimensions(@NonNull Rect bounds, @NonNull Rect previousBounds,
-            @NonNull Configuration parentConfig) {
-        int minWidth = mMinWidth;
-        int minHeight = mMinHeight;
-        // If the task has no requested minimal size, we'd like to enforce a minimal size
-        // so that the user can not render the task too small to manipulate. We don't need
-        // to do this for the root pinned task as the bounds are controlled by the system.
-        if (!inPinnedWindowingMode()) {
-            final int defaultMinSizeDp = mRootWindowContainer.mDefaultMinSizeOfResizeableTaskDp;
-            final float density = (float) parentConfig.densityDpi / DisplayMetrics.DENSITY_DEFAULT;
-            final int defaultMinSize = (int) (defaultMinSizeDp * density);
-
-            if (minWidth == INVALID_MIN_SIZE) {
-                minWidth = defaultMinSize;
-            }
-            if (minHeight == INVALID_MIN_SIZE) {
-                minHeight = defaultMinSize;
-            }
-        }
-        if (bounds.isEmpty()) {
-            // If inheriting parent bounds, check if parent bounds adhere to minimum size. If they
-            // do, we can just skip.
-            final Rect parentBounds = parentConfig.windowConfiguration.getBounds();
-            if (parentBounds.width() >= minWidth && parentBounds.height() >= minHeight) {
-                return;
-            }
-            bounds.set(parentBounds);
-        }
-        final boolean adjustWidth = minWidth > bounds.width();
-        final boolean adjustHeight = minHeight > bounds.height();
-        if (!(adjustWidth || adjustHeight)) {
-            return;
-        }
-
-        if (adjustWidth) {
-            if (!previousBounds.isEmpty() && bounds.right == previousBounds.right) {
-                bounds.left = bounds.right - minWidth;
-            } else {
-                // Either left bounds match, or neither match, or the previous bounds were
-                // fullscreen and we default to keeping left.
-                bounds.right = bounds.left + minWidth;
-            }
-        }
-        if (adjustHeight) {
-            if (!previousBounds.isEmpty() && bounds.bottom == previousBounds.bottom) {
-                bounds.top = bounds.bottom - minHeight;
-            } else {
-                // Either top bounds match, or neither match, or the previous bounds were
-                // fullscreen and we default to keeping top.
-                bounds.bottom = bounds.top + minHeight;
-            }
-        }
-    }
-
     void setLastNonFullscreenBounds(Rect bounds) {
         if (mLastNonFullscreenBounds == null) {
             mLastNonFullscreenBounds = new Rect(bounds);
@@ -2208,32 +1893,6 @@
         }
     }
 
-    /**
-     * This should be called when an child activity changes state. This should only
-     * be called from
-     * {@link ActivityRecord#setState(ActivityState, String)} .
-     * @param record The {@link ActivityRecord} whose state has changed.
-     * @param state The new state.
-     * @param reason The reason for the change.
-     */
-    void onActivityStateChanged(ActivityRecord record, ActivityState state, String reason) {
-        warnForNonLeafTask("onActivityStateChanged");
-        if (record == mResumedActivity && state != RESUMED) {
-            setResumedActivity(null, reason + " - onActivityStateChanged");
-        }
-
-        if (state == RESUMED) {
-            if (ActivityTaskManagerDebugConfig.DEBUG_ROOT_TASK) {
-                Slog.v(TAG_ROOT_TASK, "set resumed activity to:" + record + " reason:" + reason);
-            }
-            setResumedActivity(record, reason + " - onActivityStateChanged");
-            if (record == mRootWindowContainer.getTopResumedActivity()) {
-                mAtmService.setResumedActivityUncheckLocked(record, reason);
-            }
-            mTaskSupervisor.mRecentTasks.add(record.getTask());
-        }
-    }
-
     private void onConfigurationChangedInner(Configuration newParentConfig) {
         // Check if the new configuration supports persistent bounds (eg. is Freeform) and if so
         // restore the last recorded non-fullscreen bounds.
@@ -2526,400 +2185,6 @@
         mTaskSupervisor.mLaunchParamsPersister.saveTask(this, display);
     }
 
-    /**
-     * Adjust bounds to stay within root task bounds.
-     *
-     * Since bounds might be outside of root task bounds, this method tries to move the bounds in
-     * a way that keep them unchanged, but be contained within the root task bounds.
-     *
-     * @param bounds Bounds to be adjusted.
-     * @param rootTaskBounds Bounds within which the other bounds should remain.
-     * @param overlapPxX The amount of px required to be visible in the X dimension.
-     * @param overlapPxY The amount of px required to be visible in the Y dimension.
-     */
-    private static void fitWithinBounds(Rect bounds, Rect rootTaskBounds, int overlapPxX,
-            int overlapPxY) {
-        if (rootTaskBounds == null || rootTaskBounds.isEmpty() || rootTaskBounds.contains(bounds)) {
-            return;
-        }
-
-        // For each side of the parent (eg. left), check if the opposing side of the window (eg.
-        // right) is at least overlap pixels away. If less, offset the window by that difference.
-        int horizontalDiff = 0;
-        // If window is smaller than overlap, use it's smallest dimension instead
-        int overlapLR = Math.min(overlapPxX, bounds.width());
-        if (bounds.right < (rootTaskBounds.left + overlapLR)) {
-            horizontalDiff = overlapLR - (bounds.right - rootTaskBounds.left);
-        } else if (bounds.left > (rootTaskBounds.right - overlapLR)) {
-            horizontalDiff = -(overlapLR - (rootTaskBounds.right - bounds.left));
-        }
-        int verticalDiff = 0;
-        int overlapTB = Math.min(overlapPxY, bounds.width());
-        if (bounds.bottom < (rootTaskBounds.top + overlapTB)) {
-            verticalDiff = overlapTB - (bounds.bottom - rootTaskBounds.top);
-        } else if (bounds.top > (rootTaskBounds.bottom - overlapTB)) {
-            verticalDiff = -(overlapTB - (rootTaskBounds.bottom - bounds.top));
-        }
-        bounds.offset(horizontalDiff, verticalDiff);
-    }
-
-    /**
-     * Intersects inOutBounds with intersectBounds-intersectInsets. If inOutBounds is larger than
-     * intersectBounds on a side, then the respective side will not be intersected.
-     *
-     * The assumption is that if inOutBounds is initially larger than intersectBounds, then the
-     * inset on that side is no-longer applicable. This scenario happens when a task's minimal
-     * bounds are larger than the provided parent/display bounds.
-     *
-     * @param inOutBounds the bounds to intersect.
-     * @param intersectBounds the bounds to intersect with.
-     * @param intersectInsets insets to apply to intersectBounds before intersecting.
-     */
-    static void intersectWithInsetsIfFits(
-            Rect inOutBounds, Rect intersectBounds, Rect intersectInsets) {
-        if (inOutBounds.right <= intersectBounds.right) {
-            inOutBounds.right =
-                    Math.min(intersectBounds.right - intersectInsets.right, inOutBounds.right);
-        }
-        if (inOutBounds.bottom <= intersectBounds.bottom) {
-            inOutBounds.bottom =
-                    Math.min(intersectBounds.bottom - intersectInsets.bottom, inOutBounds.bottom);
-        }
-        if (inOutBounds.left >= intersectBounds.left) {
-            inOutBounds.left =
-                    Math.max(intersectBounds.left + intersectInsets.left, inOutBounds.left);
-        }
-        if (inOutBounds.top >= intersectBounds.top) {
-            inOutBounds.top =
-                    Math.max(intersectBounds.top + intersectInsets.top, inOutBounds.top);
-        }
-    }
-
-    /**
-     * Gets bounds with non-decor and stable insets applied respectively.
-     *
-     * If bounds overhangs the display, those edges will not get insets. See
-     * {@link #intersectWithInsetsIfFits}
-     *
-     * @param outNonDecorBounds where to place bounds with non-decor insets applied.
-     * @param outStableBounds where to place bounds with stable insets applied.
-     * @param bounds the bounds to inset.
-     */
-    private void calculateInsetFrames(Rect outNonDecorBounds, Rect outStableBounds, Rect bounds,
-            DisplayInfo displayInfo) {
-        outNonDecorBounds.set(bounds);
-        outStableBounds.set(bounds);
-        final Task rootTask = getRootTask();
-        if (rootTask == null || rootTask.mDisplayContent == null) {
-            return;
-        }
-        mTmpBounds.set(0, 0, displayInfo.logicalWidth, displayInfo.logicalHeight);
-
-        final DisplayPolicy policy = rootTask.mDisplayContent.getDisplayPolicy();
-        policy.getNonDecorInsetsLw(displayInfo.rotation, displayInfo.logicalWidth,
-                displayInfo.logicalHeight, displayInfo.displayCutout, mTmpInsets);
-        intersectWithInsetsIfFits(outNonDecorBounds, mTmpBounds, mTmpInsets);
-
-        policy.convertNonDecorInsetsToStableInsets(mTmpInsets, displayInfo.rotation);
-        intersectWithInsetsIfFits(outStableBounds, mTmpBounds, mTmpInsets);
-    }
-
-    /**
-     * Forces the app bounds related configuration can be computed by
-     * {@link #computeConfigResourceOverrides(Configuration, Configuration, DisplayInfo,
-     * ActivityRecord.CompatDisplayInsets)}.
-     */
-    private static void invalidateAppBoundsConfig(@NonNull Configuration inOutConfig) {
-        final Rect appBounds = inOutConfig.windowConfiguration.getAppBounds();
-        if (appBounds != null) {
-            appBounds.setEmpty();
-        }
-        inOutConfig.screenWidthDp = Configuration.SCREEN_WIDTH_DP_UNDEFINED;
-        inOutConfig.screenHeightDp = Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
-    }
-
-    void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
-            @NonNull Configuration parentConfig, @Nullable DisplayInfo overrideDisplayInfo) {
-        if (overrideDisplayInfo != null) {
-            // Make sure the screen related configs can be computed by the provided display info.
-            inOutConfig.screenLayout = Configuration.SCREENLAYOUT_UNDEFINED;
-            invalidateAppBoundsConfig(inOutConfig);
-        }
-        computeConfigResourceOverrides(inOutConfig, parentConfig, overrideDisplayInfo,
-                null /* compatInsets */);
-    }
-
-    void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
-            @NonNull Configuration parentConfig) {
-        computeConfigResourceOverrides(inOutConfig, parentConfig, null /* overrideDisplayInfo */,
-                null /* compatInsets */);
-    }
-
-    void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
-            @NonNull Configuration parentConfig,
-            @Nullable ActivityRecord.CompatDisplayInsets compatInsets) {
-        if (compatInsets != null) {
-            // Make sure the app bounds can be computed by the compat insets.
-            invalidateAppBoundsConfig(inOutConfig);
-        }
-        computeConfigResourceOverrides(inOutConfig, parentConfig, null /* overrideDisplayInfo */,
-                compatInsets);
-    }
-
-    /**
-     * Calculates configuration values used by the client to get resources. This should be run
-     * using app-facing bounds (bounds unmodified by animations or transient interactions).
-     *
-     * This assumes bounds are non-empty/null. For the null-bounds case, the caller is likely
-     * configuring an "inherit-bounds" window which means that all configuration settings would
-     * just be inherited from the parent configuration.
-     **/
-    void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
-            @NonNull Configuration parentConfig, @Nullable DisplayInfo overrideDisplayInfo,
-            @Nullable ActivityRecord.CompatDisplayInsets compatInsets) {
-        int windowingMode = inOutConfig.windowConfiguration.getWindowingMode();
-        if (windowingMode == WINDOWING_MODE_UNDEFINED) {
-            windowingMode = parentConfig.windowConfiguration.getWindowingMode();
-        }
-
-        float density = inOutConfig.densityDpi;
-        if (density == Configuration.DENSITY_DPI_UNDEFINED) {
-            density = parentConfig.densityDpi;
-        }
-        density *= DisplayMetrics.DENSITY_DEFAULT_SCALE;
-
-        // The bounds may have been overridden at this level. If the parent cannot cover these
-        // bounds, the configuration is still computed according to the override bounds.
-        final boolean insideParentBounds;
-
-        final Rect parentBounds = parentConfig.windowConfiguration.getBounds();
-        final Rect resolvedBounds = inOutConfig.windowConfiguration.getBounds();
-        if (resolvedBounds == null || resolvedBounds.isEmpty()) {
-            mTmpFullBounds.set(parentBounds);
-            insideParentBounds = true;
-        } else {
-            mTmpFullBounds.set(resolvedBounds);
-            insideParentBounds = parentBounds.contains(resolvedBounds);
-        }
-
-        // Non-null compatibility insets means the activity prefers to keep its original size, so
-        // out bounds doesn't need to be restricted by the parent or current display
-        final boolean customContainerPolicy = compatInsets != null;
-
-        Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
-        if (outAppBounds == null || outAppBounds.isEmpty()) {
-            // App-bounds hasn't been overridden, so calculate a value for it.
-            inOutConfig.windowConfiguration.setAppBounds(mTmpFullBounds);
-            outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
-
-            if (!customContainerPolicy && windowingMode != WINDOWING_MODE_FREEFORM) {
-                final Rect containingAppBounds;
-                if (insideParentBounds) {
-                    containingAppBounds = parentConfig.windowConfiguration.getAppBounds();
-                } else {
-                    // Restrict appBounds to display non-decor rather than parent because the
-                    // override bounds are beyond the parent. Otherwise, it won't match the
-                    // overridden bounds.
-                    final TaskDisplayArea displayArea = getDisplayArea();
-                    containingAppBounds = displayArea != null
-                            ? displayArea.getWindowConfiguration().getAppBounds() : null;
-                }
-                if (containingAppBounds != null && !containingAppBounds.isEmpty()) {
-                    outAppBounds.intersect(containingAppBounds);
-                }
-            }
-        }
-
-        if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED
-                || inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
-            if (!customContainerPolicy && WindowConfiguration.isFloating(windowingMode)) {
-                mTmpNonDecorBounds.set(mTmpFullBounds);
-                mTmpStableBounds.set(mTmpFullBounds);
-            } else if (!customContainerPolicy
-                    && (overrideDisplayInfo != null || getDisplayContent() != null)) {
-                final DisplayInfo di = overrideDisplayInfo != null
-                        ? overrideDisplayInfo
-                        : getDisplayContent().getDisplayInfo();
-
-                // For calculating screenWidthDp, screenWidthDp, we use the stable inset screen
-                // area, i.e. the screen area without the system bars.
-                // The non decor inset are areas that could never be removed in Honeycomb. See
-                // {@link WindowManagerPolicy#getNonDecorInsetsLw}.
-                calculateInsetFrames(mTmpNonDecorBounds, mTmpStableBounds, mTmpFullBounds, di);
-            } else {
-                // Apply the given non-decor and stable insets to calculate the corresponding bounds
-                // for screen size of configuration.
-                int rotation = inOutConfig.windowConfiguration.getRotation();
-                if (rotation == ROTATION_UNDEFINED) {
-                    rotation = parentConfig.windowConfiguration.getRotation();
-                }
-                if (rotation != ROTATION_UNDEFINED && customContainerPolicy) {
-                    mTmpNonDecorBounds.set(mTmpFullBounds);
-                    mTmpStableBounds.set(mTmpFullBounds);
-                    compatInsets.getBoundsByRotation(mTmpBounds, rotation);
-                    intersectWithInsetsIfFits(mTmpNonDecorBounds, mTmpBounds,
-                            compatInsets.mNonDecorInsets[rotation]);
-                    intersectWithInsetsIfFits(mTmpStableBounds, mTmpBounds,
-                            compatInsets.mStableInsets[rotation]);
-                    outAppBounds.set(mTmpNonDecorBounds);
-                } else {
-                    // Set to app bounds because it excludes decor insets.
-                    mTmpNonDecorBounds.set(outAppBounds);
-                    mTmpStableBounds.set(outAppBounds);
-                }
-            }
-
-            if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
-                final int overrideScreenWidthDp = (int) (mTmpStableBounds.width() / density);
-                inOutConfig.screenWidthDp = (insideParentBounds && !customContainerPolicy)
-                        ? Math.min(overrideScreenWidthDp, parentConfig.screenWidthDp)
-                        : overrideScreenWidthDp;
-            }
-            if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
-                final int overrideScreenHeightDp = (int) (mTmpStableBounds.height() / density);
-                inOutConfig.screenHeightDp = (insideParentBounds && !customContainerPolicy)
-                        ? Math.min(overrideScreenHeightDp, parentConfig.screenHeightDp)
-                        : overrideScreenHeightDp;
-            }
-
-            if (inOutConfig.smallestScreenWidthDp
-                    == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) {
-                if (WindowConfiguration.isFloating(windowingMode)) {
-                    // For floating tasks, calculate the smallest width from the bounds of the task
-                    inOutConfig.smallestScreenWidthDp = (int) (
-                            Math.min(mTmpFullBounds.width(), mTmpFullBounds.height()) / density);
-                }
-                // otherwise, it will just inherit
-            }
-        }
-
-        if (inOutConfig.orientation == ORIENTATION_UNDEFINED) {
-            inOutConfig.orientation = (inOutConfig.screenWidthDp <= inOutConfig.screenHeightDp)
-                    ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
-        }
-        if (inOutConfig.screenLayout == Configuration.SCREENLAYOUT_UNDEFINED) {
-            // For calculating screen layout, we need to use the non-decor inset screen area for the
-            // calculation for compatibility reasons, i.e. screen area without system bars that
-            // could never go away in Honeycomb.
-            int compatScreenWidthDp = (int) (mTmpNonDecorBounds.width() / density);
-            int compatScreenHeightDp = (int) (mTmpNonDecorBounds.height() / density);
-            // Use overrides if provided. If both overrides are provided, mTmpNonDecorBounds is
-            // undefined so it can't be used.
-            if (inOutConfig.screenWidthDp != Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
-                compatScreenWidthDp = inOutConfig.screenWidthDp;
-            }
-            if (inOutConfig.screenHeightDp != Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
-                compatScreenHeightDp = inOutConfig.screenHeightDp;
-            }
-            // Reducing the screen layout starting from its parent config.
-            inOutConfig.screenLayout = computeScreenLayoutOverride(parentConfig.screenLayout,
-                    compatScreenWidthDp, compatScreenHeightDp);
-        }
-    }
-
-    /** Computes LONG, SIZE and COMPAT parts of {@link Configuration#screenLayout}. */
-    static int computeScreenLayoutOverride(int sourceScreenLayout, int screenWidthDp,
-            int screenHeightDp) {
-        sourceScreenLayout = sourceScreenLayout
-                & (Configuration.SCREENLAYOUT_LONG_MASK | Configuration.SCREENLAYOUT_SIZE_MASK);
-        final int longSize = Math.max(screenWidthDp, screenHeightDp);
-        final int shortSize = Math.min(screenWidthDp, screenHeightDp);
-        return Configuration.reduceScreenLayout(sourceScreenLayout, longSize, shortSize);
-    }
-
-    @Override
-    void resolveOverrideConfiguration(Configuration newParentConfig) {
-        mTmpBounds.set(getResolvedOverrideConfiguration().windowConfiguration.getBounds());
-        super.resolveOverrideConfiguration(newParentConfig);
-
-        int windowingMode =
-                getResolvedOverrideConfiguration().windowConfiguration.getWindowingMode();
-        final int parentWindowingMode = newParentConfig.windowConfiguration.getWindowingMode();
-
-        // Resolve override windowing mode to fullscreen for home task (even on freeform
-        // display), or split-screen if in split-screen mode.
-        if (getActivityType() == ACTIVITY_TYPE_HOME && windowingMode == WINDOWING_MODE_UNDEFINED) {
-            windowingMode = WindowConfiguration.isSplitScreenWindowingMode(parentWindowingMode)
-                    ? parentWindowingMode : WINDOWING_MODE_FULLSCREEN;
-            getResolvedOverrideConfiguration().windowConfiguration.setWindowingMode(windowingMode);
-        }
-
-        // Do not allow tasks not support multi window to be in a multi-window mode, unless it is in
-        // pinned windowing mode.
-        if (!supportsMultiWindow()) {
-            final int candidateWindowingMode =
-                    windowingMode != WINDOWING_MODE_UNDEFINED ? windowingMode : parentWindowingMode;
-            if (WindowConfiguration.inMultiWindowMode(candidateWindowingMode)
-                    && candidateWindowingMode != WINDOWING_MODE_PINNED) {
-                getResolvedOverrideConfiguration().windowConfiguration.setWindowingMode(
-                        WINDOWING_MODE_FULLSCREEN);
-            }
-        }
-
-        if (isLeafTask()) {
-            resolveLeafOnlyOverrideConfigs(newParentConfig, mTmpBounds /* previousBounds */);
-        }
-        computeConfigResourceOverrides(getResolvedOverrideConfiguration(), newParentConfig);
-    }
-
-    private void resolveLeafOnlyOverrideConfigs(Configuration newParentConfig,
-            Rect previousBounds) {
-
-        int windowingMode =
-                getResolvedOverrideConfiguration().windowConfiguration.getWindowingMode();
-        if (windowingMode == WINDOWING_MODE_UNDEFINED) {
-            windowingMode = newParentConfig.windowConfiguration.getWindowingMode();
-        }
-        // Commit the resolved windowing mode so the canSpecifyOrientation won't get the old
-        // mode that may cause the bounds to be miscalculated, e.g. letterboxed.
-        getConfiguration().windowConfiguration.setWindowingMode(windowingMode);
-        Rect outOverrideBounds =
-                getResolvedOverrideConfiguration().windowConfiguration.getBounds();
-
-        if (windowingMode == WINDOWING_MODE_FULLSCREEN) {
-            // Use empty bounds to indicate "fill parent".
-            outOverrideBounds.setEmpty();
-            // The bounds for fullscreen mode shouldn't be adjusted by minimal size. Otherwise if
-            // the parent or display is smaller than the size, the content may be cropped.
-            return;
-        }
-
-        adjustForMinimalTaskDimensions(outOverrideBounds, previousBounds, newParentConfig);
-        if (windowingMode == WINDOWING_MODE_FREEFORM) {
-            computeFreeformBounds(outOverrideBounds, newParentConfig);
-            return;
-        }
-    }
-
-    /** Computes bounds for {@link WindowConfiguration#WINDOWING_MODE_FREEFORM}. */
-    private void computeFreeformBounds(@NonNull Rect outBounds,
-            @NonNull Configuration newParentConfig) {
-        // by policy, make sure the window remains within parent somewhere
-        final float density =
-                ((float) newParentConfig.densityDpi) / DisplayMetrics.DENSITY_DEFAULT;
-        final Rect parentBounds =
-                new Rect(newParentConfig.windowConfiguration.getBounds());
-        final DisplayContent display = getDisplayContent();
-        if (display != null) {
-            // If a freeform window moves below system bar, there is no way to move it again
-            // by touch. Because its caption is covered by system bar. So we exclude them
-            // from root task bounds. and then caption will be shown inside stable area.
-            final Rect stableBounds = new Rect();
-            display.getStableRect(stableBounds);
-            parentBounds.intersect(stableBounds);
-        }
-
-        fitWithinBounds(outBounds, parentBounds,
-                (int) (density * WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP),
-                (int) (density * WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP));
-
-        // Prevent to overlap caption with stable insets.
-        final int offsetTop = parentBounds.top - outBounds.top;
-        if (offsetTop > 0) {
-            outBounds.offset(0, offsetTop);
-        }
-    }
-
     Rect updateOverrideConfigurationFromLaunchBounds() {
         // If the task is controlled by another organized task, do not set override
         // configurations and let its parent (organized task) to control it;
@@ -2968,22 +2233,14 @@
         }
     }
 
-    int getDisplayId() {
-        final DisplayContent dc = getDisplayContent();
-        return dc != null ? dc.mDisplayId : INVALID_DISPLAY;
-    }
-
     /** @return Id of root task. */
     int getRootTaskId() {
         return getRootTask().mTaskId;
     }
 
+    @Nullable
     Task getRootTask() {
-        final WindowContainer parent = getParent();
-        if (parent == null) return this;
-
-        final Task parentTask = parent.asTask();
-        return parentTask == null ? this : parentTask.getRootTask();
+        return getRootTaskFragment().asTask();
     }
 
     /** @return the first organized task. */
@@ -3098,12 +2355,12 @@
         // and focused application if needed.
         focusableTask.moveToFront(myReason);
         // Top display focused root task is changed, update top resumed activity if needed.
-        if (rootTask.getResumedActivity() != null) {
+        if (rootTask.getTopResumedActivity() != null) {
             mTaskSupervisor.updateTopResumedActivityIfNeeded();
             // Set focused app directly because if the next focused activity is already resumed
             // (e.g. the next top activity is on a different display), there won't have activity
             // state change to update it.
-            mAtmService.setResumedActivityUncheckLocked(rootTask.getResumedActivity(), reason);
+            mAtmService.setResumedActivityUncheckLocked(rootTask.getTopResumedActivity(), reason);
         }
         return rootTask;
     }
@@ -3152,17 +2409,16 @@
 
         // Figure-out min/max possible position depending on if child can show for current user.
         int minPosition = (canShowChild) ? computeMinUserPosition(0, size) : 0;
-        int maxPosition = (canShowChild) ? size - 1 : computeMaxUserPosition(size - 1);
-        if (!hasChild(wc)) {
-            // Increase the maxPosition because children size will grow once wc is added.
-            ++maxPosition;
+        int maxPosition = minPosition;
+        if (size > 0) {
+            maxPosition = (canShowChild) ? size - 1 : computeMaxUserPosition(size - 1);
         }
 
         // Factor in always-on-top children in max possible position.
         if (!wc.isAlwaysOnTop()) {
             // We want to place all non-always-on-top containers below always-on-top ones.
             while (maxPosition > minPosition) {
-                if (!mChildren.get(maxPosition - 1).isAlwaysOnTop()) break;
+                if (!mChildren.get(maxPosition).isAlwaysOnTop()) break;
                 --maxPosition;
             }
         }
@@ -3173,6 +2429,12 @@
         } else if (suggestedPosition == POSITION_TOP && maxPosition >= (size - 1)) {
             return POSITION_TOP;
         }
+
+        // Increase the maxPosition because children size will grow once wc is added.
+        if (!hasChild(wc)) {
+            ++maxPosition;
+        }
+
         // Reset position based on minimum/maximum possible positions.
         return Math.min(Math.max(suggestedPosition, minPosition), maxPosition);
     }
@@ -3554,18 +2816,6 @@
         mForceShowForAllUsers = forceShowForAllUsers;
     }
 
-    @Override
-    public boolean isAttached() {
-        final TaskDisplayArea taskDisplayArea = getDisplayArea();
-        return taskDisplayArea != null && !taskDisplayArea.isRemoved();
-    }
-
-    @Override
-    @Nullable
-    TaskDisplayArea getDisplayArea() {
-        return (TaskDisplayArea) super.getDisplayArea();
-    }
-
     /**
      * When we are in a floating root task (Freeform, Pinned, ...) we calculate
      * insets differently. However if we are animating to the fullscreen root task
@@ -3576,45 +2826,6 @@
         return getWindowConfiguration().tasksAreFloating() && !mPreserveNonFloatingState;
     }
 
-    /**
-     * Returns true if the root task is translucent and can have other contents visible behind it if
-     * needed. A root task is considered translucent if it don't contain a visible or
-     * starting (about to be visible) activity that is fullscreen (opaque).
-     * @param starting The currently starting activity or null if there is none.
-     */
-    @VisibleForTesting
-    boolean isTranslucent(ActivityRecord starting) {
-        if (!isAttached() || isForceHidden()) {
-            return true;
-        }
-        final PooledPredicate p = PooledLambda.obtainPredicate(Task::isOpaqueActivity,
-                PooledLambda.__(ActivityRecord.class), starting);
-        final ActivityRecord opaque = getActivity(p);
-        p.recycle();
-        return opaque == null;
-    }
-
-    private static boolean isOpaqueActivity(ActivityRecord r, ActivityRecord starting) {
-        if (r.finishing) {
-            // We don't factor in finishing activities when determining translucency since
-            // they will be gone soon.
-            return false;
-        }
-
-        if (!r.visibleIgnoringKeyguard && r != starting) {
-            // Also ignore invisible activities that are not the currently starting
-            // activity (about to be visible).
-            return false;
-        }
-
-        if (r.occludesParent()) {
-            // Root task isn't translucent if it has at least one fullscreen activity
-            // that is visible.
-            return true;
-        }
-        return false;
-    }
-
     /** Returns the top-most activity that occludes the given one, or {@code null} if none. */
     @Nullable
     ActivityRecord getOccludingActivityAbove(ActivityRecord activity) {
@@ -3708,19 +2919,6 @@
         return activity != null ? activity.findMainWindow() : null;
     }
 
-    ActivityRecord topRunningActivity() {
-        return topRunningActivity(false /* focusableOnly */);
-    }
-
-    ActivityRecord topRunningActivity(boolean focusableOnly) {
-        // Split into 2 to avoid object creation due to variable capture.
-        if (focusableOnly) {
-            return getActivity((r) -> r.canBeTopRunning() && r.isFocusable());
-        } else {
-            return getActivity(ActivityRecord::canBeTopRunning);
-        }
-    }
-
     ActivityRecord topRunningNonDelayedActivityLocked(ActivityRecord notTop) {
         final PooledPredicate p = PooledLambda.obtainPredicate(Task::isTopRunningNonDelayed
                 , PooledLambda.__(ActivityRecord.class), notTop);
@@ -3775,12 +2973,6 @@
         });
     }
 
-    boolean isTopActivityFocusable() {
-        final ActivityRecord r = topRunningActivity();
-        return r != null ? r.isFocusable()
-                : (isFocusable() && getWindowConfiguration().canReceiveKeys());
-    }
-
     boolean isFocusableAndVisible() {
         return isTopActivityFocusable() && shouldBeVisible(null /* starting */);
     }
@@ -3896,6 +3088,41 @@
         return false;
     }
 
+    /** Iterates through all leaf task fragments and the leaf tasks. */
+    void forAllLeafTasksAndLeafTaskFragments(final Consumer<TaskFragment> callback,
+            boolean traverseTopToBottom) {
+        forAllLeafTasks(task -> {
+            if (task.isLeafTaskFragment()) {
+                callback.accept(task);
+                return;
+            }
+
+            // A leaf task that may contains both activities and task fragments.
+            boolean consumed = false;
+            if (traverseTopToBottom) {
+                for (int i = task.mChildren.size() - 1; i >= 0; --i) {
+                    final WindowContainer child = mChildren.get(i);
+                    if (child.asTaskFragment() != null) {
+                        child.forAllLeafTaskFragments(callback, traverseTopToBottom);
+                    } else if (child.asActivityRecord() != null && !consumed) {
+                        callback.accept(task);
+                        consumed = true;
+                    }
+                }
+            } else {
+                for (int i = 0; i < task.mChildren.size(); i++) {
+                    final WindowContainer child = mChildren.get(i);
+                    if (child.asTaskFragment() != null) {
+                        child.forAllLeafTaskFragments(callback, traverseTopToBottom);
+                    } else if (child.asActivityRecord() != null && !consumed) {
+                        callback.accept(task);
+                        consumed = true;
+                    }
+                }
+            }
+        }, traverseTopToBottom);
+    }
+
     @Override
     boolean forAllRootTasks(Function<Task, Boolean> callback, boolean traverseTopToBottom) {
         return isRootTask() ? callback.apply(this) : false;
@@ -4066,6 +3293,9 @@
         info.userId = isLeafTask() ? mUserId : mCurrentUser;
         info.taskId = mTaskId;
         info.displayId = getDisplayId();
+        if (tda != null) {
+            info.displayAreaFeatureId = tda.mFeatureId;
+        }
         info.isRunning = getTopNonFinishingActivity() != null;
         final Intent baseIntent = getBaseIntent();
         // Make a copy of base intent because this is like a snapshot info.
@@ -4124,6 +3354,7 @@
                 : INVALID_TASK_ID;
         info.isFocused = isFocused();
         info.isVisible = hasVisibleChildren();
+        info.isSleeping = shouldSleepActivities();
         ActivityRecord topRecord = getTopNonFinishingActivity();
         info.mTopActivityLocusId = topRecord != null ? topRecord.getLocusId() : null;
     }
@@ -4191,184 +3422,6 @@
         return this;
     }
 
-    /**
-     * Returns true if the task should be visible.
-     *
-     * @param starting The currently starting activity or null if there is none.
-     */
-    boolean shouldBeVisible(ActivityRecord starting) {
-        return getVisibility(starting) != TASK_VISIBILITY_INVISIBLE;
-    }
-
-    /**
-     * Returns true if the task should be visible.
-     *
-     * @param starting The currently starting activity or null if there is none.
-     */
-    @TaskVisibility
-    int getVisibility(ActivityRecord starting) {
-        if (!isAttached() || isForceHidden()) {
-            return TASK_VISIBILITY_INVISIBLE;
-        }
-
-        if (isTopActivityLaunchedBehind()) {
-            return TASK_VISIBILITY_VISIBLE;
-        }
-
-        boolean gotRootSplitScreenTask = false;
-        boolean gotOpaqueSplitScreenPrimary = false;
-        boolean gotOpaqueSplitScreenSecondary = false;
-        boolean gotTranslucentFullscreen = false;
-        boolean gotTranslucentSplitScreenPrimary = false;
-        boolean gotTranslucentSplitScreenSecondary = false;
-        boolean shouldBeVisible = true;
-
-        // This root task is only considered visible if all its parent root tasks are considered
-        // visible, so check the visibility of all ancestor root task first.
-        final WindowContainer parent = getParent();
-        if (parent.asTask() != null) {
-            final int parentVisibility = parent.asTask().getVisibility(starting);
-            if (parentVisibility == TASK_VISIBILITY_INVISIBLE) {
-                // Can't be visible if parent isn't visible
-                return TASK_VISIBILITY_INVISIBLE;
-            } else if (parentVisibility == TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT) {
-                // Parent is behind a translucent container so the highest visibility this container
-                // can get is that.
-                gotTranslucentFullscreen = true;
-            }
-        }
-
-        final List<Task> adjacentTasks = new ArrayList<>();
-        final int windowingMode = getWindowingMode();
-        final boolean isAssistantType = isActivityTypeAssistant();
-        for (int i = parent.getChildCount() - 1; i >= 0; --i) {
-            final WindowContainer wc = parent.getChildAt(i);
-            final Task other = wc.asTask();
-            if (other == null) continue;
-
-            final boolean hasRunningActivities = other.topRunningActivity() != null;
-            if (other == this) {
-                // Should be visible if there is no other stack occluding it, unless it doesn't
-                // have any running activities, not starting one and not home stack.
-                shouldBeVisible = hasRunningActivities || isInTask(starting) != null
-                        || isActivityTypeHome();
-                break;
-            }
-
-            if (!hasRunningActivities) {
-                continue;
-            }
-
-            final int otherWindowingMode = other.getWindowingMode();
-
-            if (otherWindowingMode == WINDOWING_MODE_FULLSCREEN) {
-                if (other.isTranslucent(starting)) {
-                    // Can be visible behind a translucent fullscreen stack.
-                    gotTranslucentFullscreen = true;
-                    continue;
-                }
-                return TASK_VISIBILITY_INVISIBLE;
-            } else if (otherWindowingMode == WINDOWING_MODE_MULTI_WINDOW
-                    && other.matchParentBounds()) {
-                if (other.isTranslucent(starting)) {
-                    // Can be visible behind a translucent task.
-                    gotTranslucentFullscreen = true;
-                    continue;
-                }
-                // Multi-window task that matches parent bounds would occlude other children.
-                return TASK_VISIBILITY_INVISIBLE;
-            } else if (otherWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
-                    && !gotOpaqueSplitScreenPrimary) {
-                gotRootSplitScreenTask = true;
-                gotTranslucentSplitScreenPrimary = other.isTranslucent(starting);
-                gotOpaqueSplitScreenPrimary = !gotTranslucentSplitScreenPrimary;
-                if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
-                        && gotOpaqueSplitScreenPrimary) {
-                    // Can not be visible behind another opaque stack in split-screen-primary mode.
-                    return TASK_VISIBILITY_INVISIBLE;
-                }
-            } else if (otherWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
-                    && !gotOpaqueSplitScreenSecondary) {
-                gotRootSplitScreenTask = true;
-                gotTranslucentSplitScreenSecondary = other.isTranslucent(starting);
-                gotOpaqueSplitScreenSecondary = !gotTranslucentSplitScreenSecondary;
-                if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
-                        && gotOpaqueSplitScreenSecondary) {
-                    // Can not be visible behind another opaque stack in split-screen-secondary mode.
-                    return TASK_VISIBILITY_INVISIBLE;
-                }
-            }
-            if (gotOpaqueSplitScreenPrimary && gotOpaqueSplitScreenSecondary) {
-                // Can not be visible if we are in split-screen windowing mode and both halves of
-                // the screen are opaque.
-                return TASK_VISIBILITY_INVISIBLE;
-            }
-            if (isAssistantType && gotRootSplitScreenTask) {
-                // Assistant stack can't be visible behind split-screen. In addition to this not
-                // making sense, it also works around an issue here we boost the z-order of the
-                // assistant window surfaces in window manager whenever it is visible.
-                return TASK_VISIBILITY_INVISIBLE;
-            }
-            if (other.mAdjacentTask != null) {
-                if (adjacentTasks.contains(other.mAdjacentTask)) {
-                    if (other.isTranslucent(starting)
-                            || other.mAdjacentTask.isTranslucent(starting)) {
-                        // Can be visible behind a translucent adjacent tasks.
-                        gotTranslucentFullscreen = true;
-                        continue;
-                    }
-                    // Can not be visible behind adjacent tasks.
-                    return TASK_VISIBILITY_INVISIBLE;
-                } else {
-                    adjacentTasks.add(other);
-                }
-            }
-        }
-
-        if (!shouldBeVisible) {
-            return TASK_VISIBILITY_INVISIBLE;
-        }
-
-        // Handle cases when there can be a translucent split-screen stack on top.
-        switch (windowingMode) {
-            case WINDOWING_MODE_FULLSCREEN:
-                if (gotTranslucentSplitScreenPrimary || gotTranslucentSplitScreenSecondary) {
-                    // At least one of the split-screen stacks that covers this one is translucent.
-                    // When in split mode, home task will be reparented to the secondary split while
-                    // leaving tasks not supporting split below. Due to
-                    // TaskDisplayArea#assignRootTaskOrdering always adjusts home surface layer to
-                    // the bottom, this makes sure tasks not in split roots won't occlude home task
-                    // unexpectedly.
-                    return TASK_VISIBILITY_INVISIBLE;
-                }
-                break;
-            case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY:
-                if (gotTranslucentSplitScreenPrimary) {
-                    // Covered by translucent primary split-screen on top.
-                    return TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT;
-                }
-                break;
-            case WINDOWING_MODE_SPLIT_SCREEN_SECONDARY:
-                if (gotTranslucentSplitScreenSecondary) {
-                    // Covered by translucent secondary split-screen on top.
-                    return TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT;
-                }
-                break;
-        }
-
-        // Lastly - check if there is a translucent fullscreen stack on top.
-        return gotTranslucentFullscreen ? TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT
-                : TASK_VISIBILITY_VISIBLE;
-    }
-
-    private boolean isTopActivityLaunchedBehind() {
-        final ActivityRecord top = topRunningActivity();
-        if (top != null && top.mLaunchTaskBehind) {
-            return true;
-        }
-        return false;
-    }
-
     ActivityRecord isInTask(ActivityRecord r) {
         if (r == null) {
             return null;
@@ -4551,7 +3604,7 @@
             // Increment the total number of non-finishing activities
             numActivities++;
 
-            if (top == null || (top.isState(ActivityState.INITIALIZING))) {
+            if (top == null || (top.isState(INITIALIZING))) {
                 top = r;
                 // Reset the number of running activities until we hit the first non-initializing
                 // activity
@@ -5294,9 +4347,7 @@
         return super.isAlwaysOnTop();
     }
 
-    /**
-     * Returns whether this task is currently forced to be hidden for any reason.
-     */
+    @Override
     protected boolean isForceHidden() {
         return mForceHiddenFlags != 0;
     }
@@ -5449,8 +4500,10 @@
             mAtmService.continueWindowLayout();
         }
 
-        mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
-        mRootWindowContainer.resumeFocusedTasksTopActivities();
+        if (!mTaskSupervisor.isRootVisibilityUpdateDeferred()) {
+            mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
+            mRootWindowContainer.resumeFocusedTasksTopActivities();
+        }
     }
 
     void resumeNextFocusAfterReparent() {
@@ -5582,19 +4635,6 @@
         r.completeResumeLocked();
     }
 
-    void awakeFromSleepingLocked() {
-        if (!isLeafTask()) {
-            forAllLeafTasks((task) -> task.awakeFromSleepingLocked(),
-                    true /* traverseTopToBottom */);
-            return;
-        }
-
-        if (mPausingActivity != null) {
-            Slog.d(TAG, "awakeFromSleepingLocked: previously pausing activity didn't pause");
-            mPausingActivity.activityPaused(true);
-        }
-    }
-
     void checkReadyForSleep() {
         if (shouldSleepActivities() && goToSleepIfPossible(false /* shuttingDown */)) {
             mTaskSupervisor.checkReadyForSleepLocked(true /* allowDelay */);
@@ -5613,302 +4653,13 @@
      * the process of going to sleep (checkReadyForSleep will be called when that process finishes).
      */
     boolean goToSleepIfPossible(boolean shuttingDown) {
-        if (!isLeafTask()) {
-            final int[] sleepInProgress = {0};
-            forAllLeafTasks((t) -> {
-                if (!t.goToSleepIfPossible(shuttingDown)) {
-                    sleepInProgress[0]++;
-                }
-            }, true);
-            return sleepInProgress[0] == 0;
-        }
-
-        boolean shouldSleep = true;
-        if (mResumedActivity != null) {
-            // Still have something resumed; can't sleep until it is paused.
-            ProtoLog.v(WM_DEBUG_STATES, "Sleep needs to pause %s", mResumedActivity);
-            if (DEBUG_USER_LEAVING) Slog.v(TAG_USER_LEAVING,
-                    "Sleep => pause with userLeaving=false");
-
-            startPausingLocked(false /* userLeaving */, true /* uiSleeping */, null /* resuming */,
-                    "sleep");
-            shouldSleep = false ;
-        } else if (mPausingActivity != null) {
-            // Still waiting for something to pause; can't sleep yet.
-            ProtoLog.v(WM_DEBUG_STATES, "Sleep still waiting to pause %s", mPausingActivity);
-            shouldSleep = false;
-        }
-
-        if (!shuttingDown) {
-            if (containsActivityFromRootTask(mTaskSupervisor.mStoppingActivities)) {
-                // Still need to tell some activities to stop; can't sleep yet.
-                ProtoLog.v(WM_DEBUG_STATES, "Sleep still need to stop %d activities",
-                        mTaskSupervisor.mStoppingActivities.size());
-
-                mTaskSupervisor.scheduleIdle();
-                shouldSleep = false;
+        final int[] sleepInProgress = {0};
+        forAllLeafTasksAndLeafTaskFragments(taskFragment -> {
+            if (!taskFragment.sleepIfPossible(shuttingDown)) {
+                sleepInProgress[0]++;
             }
-        }
-
-        if (shouldSleep) {
-            ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
-                    !PRESERVE_WINDOWS);
-        }
-
-        return shouldSleep;
-    }
-
-    private boolean containsActivityFromRootTask(List<ActivityRecord> rs) {
-        for (ActivityRecord r : rs) {
-            if (r.getRootTask() == this) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    final boolean startPausingLocked(boolean uiSleeping, ActivityRecord resuming, String reason) {
-        return startPausingLocked(mTaskSupervisor.mUserLeaving, uiSleeping, resuming, reason);
-    }
-
-    /**
-     * Start pausing the currently resumed activity.  It is an error to call this if there
-     * is already an activity being paused or there is no resumed activity.
-     *
-     * @param userLeaving True if this should result in an onUserLeaving to the current activity.
-     * @param uiSleeping True if this is happening with the user interface going to sleep (the
-     * screen turning off).
-     * @param resuming The activity we are currently trying to resume or null if this is not being
-     *                 called as part of resuming the top activity, so we shouldn't try to instigate
-     *                 a resume here if not null.
-     * @param reason The reason of pausing the activity.
-     * @return Returns true if an activity now is in the PAUSING state, and we are waiting for
-     * it to tell us when it is done.
-     */
-    final boolean startPausingLocked(boolean userLeaving, boolean uiSleeping,
-            ActivityRecord resuming, String reason) {
-        if (!isLeafTask()) {
-            final int[] pausing = {0};
-            forAllLeafTasks((t) -> {
-                if (t.startPausingLocked(userLeaving, uiSleeping, resuming, reason)) {
-                    pausing[0]++;
-                }
-            }, true /* traverseTopToBottom */);
-            return pausing[0] > 0;
-        }
-
-        if (mPausingActivity != null) {
-            Slog.wtf(TAG, "Going to pause when pause is already pending for " + mPausingActivity
-                    + " state=" + mPausingActivity.getState());
-            if (!shouldSleepActivities()) {
-                // Avoid recursion among check for sleep and complete pause during sleeping.
-                // Because activity will be paused immediately after resume, just let pause
-                // be completed by the order of activity paused from clients.
-                completePauseLocked(false, resuming);
-            }
-        }
-        ActivityRecord prev = mResumedActivity;
-
-        if (prev == null) {
-            if (resuming == null) {
-                Slog.wtf(TAG, "Trying to pause when nothing is resumed");
-                mRootWindowContainer.resumeFocusedTasksTopActivities();
-            }
-            return false;
-        }
-
-        if (prev == resuming) {
-            Slog.wtf(TAG, "Trying to pause activity that is in process of being resumed");
-            return false;
-        }
-
-        ProtoLog.v(WM_DEBUG_STATES, "Moving to PAUSING: %s", prev);
-        mPausingActivity = prev;
-        mLastPausedActivity = prev;
-        if (prev.isNoHistory() && !mTaskSupervisor.mNoHistoryActivities.contains(prev)) {
-            mTaskSupervisor.mNoHistoryActivities.add(prev);
-        }
-        prev.setState(PAUSING, "startPausingLocked");
-        prev.getTask().touchActiveTime();
-
-        mAtmService.updateCpuStats();
-
-        boolean pauseImmediately = false;
-        boolean shouldAutoPip = false;
-        if (resuming != null && (resuming.info.flags & FLAG_RESUME_WHILE_PAUSING) != 0) {
-            // If the flag RESUME_WHILE_PAUSING is set, then continue to schedule the previous
-            // activity to be paused, while at the same time resuming the new resume activity
-            // only if the previous activity can't go into Pip since we want to give Pip
-            // activities a chance to enter Pip before resuming the next activity.
-            final boolean lastResumedCanPip = prev != null && prev.checkEnterPictureInPictureState(
-                    "shouldResumeWhilePausing", userLeaving);
-            if (lastResumedCanPip && prev.pictureInPictureArgs.isAutoEnterEnabled()) {
-                shouldAutoPip = true;
-            } else if (!lastResumedCanPip) {
-                pauseImmediately = true;
-            } else {
-                // The previous activity may still enter PIP even though it did not allow auto-PIP.
-            }
-        }
-
-        boolean didAutoPip = false;
-        if (prev.attachedToProcess()) {
-            if (shouldAutoPip) {
-                ProtoLog.d(WM_DEBUG_STATES, "Auto-PIP allowed, entering PIP mode "
-                        + "directly: %s", prev);
-
-                didAutoPip = mAtmService.enterPictureInPictureMode(prev, prev.pictureInPictureArgs);
-                mPausingActivity = null;
-            } else {
-                ProtoLog.v(WM_DEBUG_STATES, "Enqueueing pending pause: %s", prev);
-                try {
-                    EventLogTags.writeWmPauseActivity(prev.mUserId, System.identityHashCode(prev),
-                            prev.shortComponentName, "userLeaving=" + userLeaving, reason);
-
-                    mAtmService.getLifecycleManager().scheduleTransaction(prev.app.getThread(),
-                            prev.appToken, PauseActivityItem.obtain(prev.finishing, userLeaving,
-                                    prev.configChangeFlags, pauseImmediately));
-                } catch (Exception e) {
-                    // Ignore exception, if process died other code will cleanup.
-                    Slog.w(TAG, "Exception thrown during pause", e);
-                    mPausingActivity = null;
-                    mLastPausedActivity = null;
-                    mTaskSupervisor.mNoHistoryActivities.remove(prev);
-                }
-            }
-        } else {
-            mPausingActivity = null;
-            mLastPausedActivity = null;
-            mTaskSupervisor.mNoHistoryActivities.remove(prev);
-        }
-
-        // If we are not going to sleep, we want to ensure the device is
-        // awake until the next activity is started.
-        if (!uiSleeping && !mAtmService.isSleepingOrShuttingDownLocked()) {
-            mTaskSupervisor.acquireLaunchWakelock();
-        }
-
-        // If already entered PIP mode, no need to keep pausing.
-        if (mPausingActivity != null && !didAutoPip) {
-            // Have the window manager pause its key dispatching until the new
-            // activity has started.  If we're pausing the activity just because
-            // the screen is being turned off and the UI is sleeping, don't interrupt
-            // key dispatch; the same activity will pick it up again on wakeup.
-            if (!uiSleeping) {
-                prev.pauseKeyDispatchingLocked();
-            } else {
-                ProtoLog.v(WM_DEBUG_STATES, "Key dispatch not paused for screen off");
-            }
-
-            if (pauseImmediately) {
-                // If the caller said they don't want to wait for the pause, then complete
-                // the pause now.
-                completePauseLocked(false, resuming);
-                return false;
-
-            } else {
-                prev.schedulePauseTimeout();
-                return true;
-            }
-
-        } else {
-            // This activity either failed to schedule the pause or it entered PIP mode,
-            // so just treat it as being paused now.
-            ProtoLog.v(WM_DEBUG_STATES, "Activity not running or entered PiP, resuming next.");
-            if (resuming == null) {
-                mRootWindowContainer.resumeFocusedTasksTopActivities();
-            }
-            return false;
-        }
-    }
-
-    @VisibleForTesting
-    void completePauseLocked(boolean resumeNext, ActivityRecord resuming) {
-        // Complete the pausing process of a pausing activity, so it doesn't make sense to
-        // operate on non-leaf tasks.
-        warnForNonLeafTask("completePauseLocked");
-
-        ActivityRecord prev = mPausingActivity;
-        ProtoLog.v(WM_DEBUG_STATES, "Complete pause: %s", prev);
-
-        if (prev != null) {
-            prev.setWillCloseOrEnterPip(false);
-            final boolean wasStopping = prev.isState(STOPPING);
-            prev.setState(PAUSED, "completePausedLocked");
-            if (prev.finishing) {
-                // We will update the activity visibility later, no need to do in
-                // completeFinishing(). Updating visibility here might also making the next
-                // activities to be resumed, and could result in wrong app transition due to
-                // lack of previous activity information.
-                ProtoLog.v(WM_DEBUG_STATES, "Executing finish of activity: %s", prev);
-                prev = prev.completeFinishing(false /* updateVisibility */,
-                        "completePausedLocked");
-            } else if (prev.hasProcess()) {
-                ProtoLog.v(WM_DEBUG_STATES, "Enqueue pending stop if needed: %s "
-                        + "wasStopping=%b visibleRequested=%b",  prev,  wasStopping,
-                        prev.mVisibleRequested);
-                if (prev.deferRelaunchUntilPaused) {
-                    // Complete the deferred relaunch that was waiting for pause to complete.
-                    ProtoLog.v(WM_DEBUG_STATES, "Re-launching after pause: %s", prev);
-                    prev.relaunchActivityLocked(prev.preserveWindowOnDeferredRelaunch);
-                } else if (wasStopping) {
-                    // We are also stopping, the stop request must have gone soon after the pause.
-                    // We can't clobber it, because the stop confirmation will not be handled.
-                    // We don't need to schedule another stop, we only need to let it happen.
-                    prev.setState(STOPPING, "completePausedLocked");
-                } else if (!prev.mVisibleRequested || shouldSleepOrShutDownActivities()) {
-                    // Clear out any deferred client hide we might currently have.
-                    prev.setDeferHidingClient(false);
-                    // If we were visible then resumeTopActivities will release resources before
-                    // stopping.
-                    prev.addToStopping(true /* scheduleIdle */, false /* idleDelayed */,
-                            "completePauseLocked");
-                }
-            } else {
-                ProtoLog.v(WM_DEBUG_STATES, "App died during pause, not stopping: %s", prev);
-                prev = null;
-            }
-            // It is possible the activity was freezing the screen before it was paused.
-            // In that case go ahead and remove the freeze this activity has on the screen
-            // since it is no longer visible.
-            if (prev != null) {
-                prev.stopFreezingScreenLocked(true /*force*/);
-            }
-            mPausingActivity = null;
-        }
-
-        if (resumeNext) {
-            final Task topRootTask = mRootWindowContainer.getTopDisplayFocusedRootTask();
-            if (topRootTask != null && !topRootTask.shouldSleepOrShutDownActivities()) {
-                mRootWindowContainer.resumeFocusedTasksTopActivities(topRootTask, prev, null);
-            } else {
-                checkReadyForSleep();
-                final ActivityRecord top =
-                        topRootTask != null ? topRootTask.topRunningActivity() : null;
-                if (top == null || (prev != null && top != prev)) {
-                    // If there are no more activities available to run, do resume anyway to start
-                    // something. Also if the top activity on the root task is not the just paused
-                    // activity, we need to go ahead and resume it to ensure we complete an
-                    // in-flight app switch.
-                    mRootWindowContainer.resumeFocusedTasksTopActivities();
-                }
-            }
-        }
-
-        if (prev != null) {
-            prev.resumeKeyDispatchingLocked();
-        }
-
-        mRootWindowContainer.ensureActivitiesVisible(resuming, 0, !PRESERVE_WINDOWS);
-
-        // Notify when the task stack has changed, but only if visibilities changed (not just
-        // focus). Also if there is an active root pinned task - we always want to notify it about
-        // task stack changes, because its positioning may depend on it.
-        if (mTaskSupervisor.mAppVisibilitiesChangedSinceLastPause
-                || (getDisplayArea() != null && getDisplayArea().hasPinnedTask())) {
-            mAtmService.getTaskChangeNotificationController().notifyTaskStackChanged();
-            mTaskSupervisor.mAppVisibilitiesChangedSinceLastPause = false;
-        }
+        }, true /* traverseTopToBottom */);
+        return sleepInProgress[0] == 0;
     }
 
     boolean isTopRootTaskInDisplayArea() {
@@ -5931,10 +4682,10 @@
      *                 The activity is either starting or resuming.
      *                 Caller should ensure starting activity is visible.
      * @param preserveWindows Flag indicating whether windows should be preserved when updating
-     *                        configuration in {@link mEnsureActivitiesVisibleHelper}.
+     *                        configuration in {@link EnsureActivitiesVisibleHelper}.
      * @param configChanges Parts of the configuration that changed for this activity for evaluating
      *                      if the screen should be frozen as part of
-     *                      {@link mEnsureActivitiesVisibleHelper}.
+     *                      {@link EnsureActivitiesVisibleHelper}.
      *
      */
     void ensureActivitiesVisible(@Nullable ActivityRecord starting, int configChanges,
@@ -5950,21 +4701,22 @@
      *                 The activity is either starting or resuming.
      *                 Caller should ensure starting activity is visible.
      * @param notifyClients Flag indicating whether the visibility updates should be sent to the
-     *                      clients in {@link mEnsureActivitiesVisibleHelper}.
+     *                      clients in {@link EnsureActivitiesVisibleHelper}.
      * @param preserveWindows Flag indicating whether windows should be preserved when updating
-     *                        configuration in {@link mEnsureActivitiesVisibleHelper}.
+     *                        configuration in {@link EnsureActivitiesVisibleHelper}.
      * @param configChanges Parts of the configuration that changed for this activity for evaluating
      *                      if the screen should be frozen as part of
-     *                      {@link mEnsureActivitiesVisibleHelper}.
+     *                      {@link EnsureActivitiesVisibleHelper}.
      */
     // TODO: Should be re-worked based on the fact that each task as a root task in most cases.
     void ensureActivitiesVisible(@Nullable ActivityRecord starting, int configChanges,
             boolean preserveWindows, boolean notifyClients) {
         mTaskSupervisor.beginActivityVisibilityUpdate();
         try {
-            forAllLeafTasks(task -> task.mEnsureActivitiesVisibleHelper.process(
-                    starting, configChanges, preserveWindows, notifyClients),
-                    true /* traverseTopToBottom */);
+            forAllLeafTasks(task -> {
+                task.updateActivityVisibilities(starting, configChanges, preserveWindows,
+                        notifyClients);
+            }, true /* traverseTopToBottom */);
 
             // Notify WM shell that task visibilities may have changed
             forAllTasks(task -> task.dispatchTaskInfoChangedIfNeeded(/* force */ false),
@@ -6039,25 +4791,6 @@
         }
     }
 
-    /** @see ActivityRecord#cancelInitializing() */
-    void cancelInitializingActivities() {
-        // We don't want to clear starting window for activities that aren't behind fullscreen
-        // activities as we need to display their starting window until they are done initializing.
-        checkBehindFullscreenActivity(null /* toCheck */, ActivityRecord::cancelInitializing);
-    }
-
-    /**
-     * If an activity {@param toCheck} is given, this method returns {@code true} if the activity
-     * is occluded by any fullscreen activity. If there is no {@param toCheck} and the handling
-     * function {@param handleBehindFullscreenActivity} is given, this method will pass all occluded
-     * activities to the function.
-     */
-    boolean checkBehindFullscreenActivity(ActivityRecord toCheck,
-            Consumer<ActivityRecord> handleBehindFullscreenActivity) {
-        return mCheckBehindFullscreenActivityHelper.process(
-                toCheck, handleBehindFullscreenActivity);
-    }
-
     /**
      * Ensure that the top activity in the root task is resumed.
      *
@@ -6098,7 +4831,8 @@
                     if (!child.isTopActivityFocusable()) {
                         continue;
                     }
-                    if (child.getVisibility(null /* starting */) != TASK_VISIBILITY_VISIBLE) {
+                    if (child.getVisibility(null /* starting */)
+                            != TASK_FRAGMENT_VISIBILITY_VISIBLE) {
                         break;
                     }
 
@@ -6145,383 +4879,25 @@
             return false;
         }
 
-        // Find the next top-most activity to resume in this root task that is not finishing and is
-        // focusable. If it is not focusable, we will fall into the case below to resume the
-        // top activity in the next focusable task.
-        ActivityRecord next = topRunningActivity(true /* focusableOnly */);
-
-        final boolean hasRunningActivity = next != null;
-
-        // TODO: Maybe this entire condition can get removed?
-        if (hasRunningActivity && !isAttached()) {
-            return false;
-        }
-
         mRootWindowContainer.cancelInitializingActivities();
 
-        if (!hasRunningActivity) {
-            // There are no activities left in the root task, let's look somewhere else.
+        final ActivityRecord topActivity = topRunningActivity(true /* focusableOnly */);
+        if (topActivity == null) {
+            // There are no activities left in this task, let's look somewhere else.
             return resumeNextFocusableActivityWhenRootTaskIsEmpty(prev, options);
         }
 
-        next.delayedResume = false;
-        final TaskDisplayArea taskDisplayArea = getDisplayArea();
-
-        // If the top activity is the resumed one, nothing to do.
-        if (mResumedActivity == next && next.isState(RESUMED)
-                && taskDisplayArea.allResumedActivitiesComplete()) {
-            // Make sure we have executed any pending transitions, since there
-            // should be nothing left to do at this point.
-            executeAppTransition(options);
-            // For devices that are not in fullscreen mode (e.g. freeform windows), it's possible
-            // we still want to check if the visibility of other windows have changed (e.g. bringing
-            // a fullscreen window forward to cover another freeform activity.)
-            if (taskDisplayArea.inMultiWindowMode()) {
-                taskDisplayArea.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
-                        false /* preserveWindows */, true /* notifyClients */);
-            }
-            ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivityLocked: Top activity "
-                    + "resumed %s", next);
-            return false;
-        }
-
-        if (!next.canResumeByCompat()) {
-            return false;
-        }
-
-        // If we are currently pausing an activity, then don't do anything until that is done.
-        final boolean allPausedComplete = mRootWindowContainer.allPausedActivitiesComplete();
-        if (!allPausedComplete) {
-            ProtoLog.v(WM_DEBUG_STATES,
-                    "resumeTopActivityLocked: Skip resume: some activity pausing.");
-
-            return false;
-        }
-
-        // If we are sleeping, and there is no resumed activity, and the top activity is paused,
-        // well that is the state we want.
-        if (mLastPausedActivity == next && shouldSleepOrShutDownActivities()) {
-            // Make sure we have executed any pending transitions, since there
-            // should be nothing left to do at this point.
-            executeAppTransition(options);
-            ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivityLocked: Going to sleep and"
-                    + " all paused");
-            return false;
-        }
-
-        // Make sure that the user who owns this activity is started.  If not,
-        // we will just leave it as is because someone should be bringing
-        // another user's activities to the top of the stack.
-        if (!mAtmService.mAmInternal.hasStartedUserState(next.mUserId)) {
-            Slog.w(TAG, "Skipping resume of top activity " + next
-                    + ": user " + next.mUserId + " is stopped");
-            return false;
-        }
-
-        // The activity may be waiting for stop, but that is no longer
-        // appropriate for it.
-        mTaskSupervisor.mStoppingActivities.remove(next);
-
-        if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Resuming " + next);
-
-        mTaskSupervisor.setLaunchSource(next.info.applicationInfo.uid);
-
-        ActivityRecord lastResumed = null;
-        final Task lastFocusedRootTask = taskDisplayArea.getLastFocusedRootTask();
-        if (lastFocusedRootTask != null && lastFocusedRootTask != getRootTask()) {
-            // So, why aren't we using prev here??? See the param comment on the method. prev
-            // doesn't represent the last resumed activity. However, the last focus stack does if
-            // it isn't null.
-            lastResumed = lastFocusedRootTask.getResumedActivity();
-        }
-
-        boolean pausing = !deferPause && taskDisplayArea.pauseBackTasks(next);
-        if (mResumedActivity != null) {
-            ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivityLocked: Pausing %s", mResumedActivity);
-            pausing |= startPausingLocked(false /* uiSleeping */, next,
-                    "resumeTopActivityInnerLocked");
-        }
-        if (pausing) {
-            ProtoLog.v(WM_DEBUG_STATES, "resumeTopActivityLocked: Skip resume: need to"
-                    + " start pausing");
-            // At this point we want to put the upcoming activity's process
-            // at the top of the LRU list, since we know we will be needing it
-            // very soon and it would be a waste to let it get killed if it
-            // happens to be sitting towards the end.
-            if (next.attachedToProcess()) {
-                next.app.updateProcessInfo(false /* updateServiceConnectionActivities */,
-                        true /* activityChange */, false /* updateOomAdj */,
-                        false /* addPendingTopUid */);
-            } else if (!next.isProcessRunning()) {
-                // Since the start-process is asynchronous, if we already know the process of next
-                // activity isn't running, we can start the process earlier to save the time to wait
-                // for the current activity to be paused.
-                final boolean isTop = this == taskDisplayArea.getFocusedRootTask();
-                mAtmService.startProcessAsync(next, false /* knownToBeDead */, isTop,
-                        isTop ? "pre-top-activity" : "pre-activity");
-            }
-            if (lastResumed != null) {
-                lastResumed.setWillCloseOrEnterPip(true);
-            }
-            return true;
-        } else if (mResumedActivity == next && next.isState(RESUMED)
-                && taskDisplayArea.allResumedActivitiesComplete()) {
-            // It is possible for the activity to be resumed when we paused back stacks above if the
-            // next activity doesn't have to wait for pause to complete.
-            // So, nothing else to-do except:
-            // Make sure we have executed any pending transitions, since there
-            // should be nothing left to do at this point.
-            executeAppTransition(options);
-            ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivityLocked: Top activity resumed "
-                    + "(dontWaitForPause) %s", next);
-            return true;
-        }
-
-        // If the most recent activity was noHistory but was only stopped rather
-        // than stopped+finished because the device went to sleep, we need to make
-        // sure to finish it as we're making a new activity topmost.
-        if (shouldSleepActivities()) {
-            mTaskSupervisor.finishNoHistoryActivitiesIfNeeded(next);
-        }
-
-        if (prev != null && prev != next && next.nowVisible) {
-
-            // The next activity is already visible, so hide the previous
-            // activity's windows right now so we can show the new one ASAP.
-            // We only do this if the previous is finishing, which should mean
-            // it is on top of the one being resumed so hiding it quickly
-            // is good.  Otherwise, we want to do the normal route of allowing
-            // the resumed activity to be shown so we can decide if the
-            // previous should actually be hidden depending on whether the
-            // new one is found to be full-screen or not.
-            if (prev.finishing) {
-                prev.setVisibility(false);
-                if (DEBUG_SWITCH) Slog.v(TAG_SWITCH,
-                        "Not waiting for visible to hide: " + prev
-                                + ", nowVisible=" + next.nowVisible);
-            } else {
-                if (DEBUG_SWITCH) Slog.v(TAG_SWITCH,
-                        "Previous already visible but still waiting to hide: " + prev
-                                + ", nowVisible=" + next.nowVisible);
+        final boolean[] resumed = new boolean[1];
+        final TaskFragment topFragment = topActivity.getTaskFragment();
+        resumed[0] = topFragment.resumeTopActivity(prev, options, deferPause);
+        forAllLeafTaskFragments(f -> {
+            if (topFragment == f) {
+                return;
             }
 
-        }
-
-        // Launching this app's activity, make sure the app is no longer
-        // considered stopped.
-        try {
-            mTaskSupervisor.getActivityMetricsLogger()
-                    .notifyBeforePackageUnstopped(next.packageName);
-            mAtmService.getPackageManager().setPackageStoppedState(
-                    next.packageName, false, next.mUserId); /* TODO: Verify if correct userid */
-        } catch (RemoteException e1) {
-        } catch (IllegalArgumentException e) {
-            Slog.w(TAG, "Failed trying to unstop package "
-                    + next.packageName + ": " + e);
-        }
-
-        // We are starting up the next activity, so tell the window manager
-        // that the previous one will be hidden soon.  This way it can know
-        // to ignore it when computing the desired screen orientation.
-        boolean anim = true;
-        final DisplayContent dc = taskDisplayArea.mDisplayContent;
-        if (prev != null) {
-            if (prev.finishing) {
-                if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION,
-                        "Prepare close transition: prev=" + prev);
-                if (mTaskSupervisor.mNoAnimActivities.contains(prev)) {
-                    anim = false;
-                    dc.prepareAppTransition(TRANSIT_NONE);
-                } else {
-                    dc.prepareAppTransition(TRANSIT_CLOSE);
-                }
-                prev.setVisibility(false);
-            } else {
-                if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION,
-                        "Prepare open transition: prev=" + prev);
-                if (mTaskSupervisor.mNoAnimActivities.contains(next)) {
-                    anim = false;
-                    dc.prepareAppTransition(TRANSIT_NONE);
-                } else {
-                    dc.prepareAppTransition(TRANSIT_OPEN,
-                            next.mLaunchTaskBehind ? TRANSIT_FLAG_OPEN_BEHIND : 0);
-                }
-            }
-        } else {
-            if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare open transition: no previous");
-            if (mTaskSupervisor.mNoAnimActivities.contains(next)) {
-                anim = false;
-                dc.prepareAppTransition(TRANSIT_NONE);
-            } else {
-                dc.prepareAppTransition(TRANSIT_OPEN);
-            }
-        }
-
-        if (anim) {
-            next.applyOptionsAnimation();
-        } else {
-            next.abortAndClearOptionsAnimation();
-        }
-
-        mTaskSupervisor.mNoAnimActivities.clear();
-
-        if (next.attachedToProcess()) {
-            if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Resume running: " + next
-                    + " stopped=" + next.stopped
-                    + " visibleRequested=" + next.mVisibleRequested);
-
-            // If the previous activity is translucent, force a visibility update of
-            // the next activity, so that it's added to WM's opening app list, and
-            // transition animation can be set up properly.
-            // For example, pressing Home button with a translucent activity in focus.
-            // Launcher is already visible in this case. If we don't add it to opening
-            // apps, maybeUpdateTransitToWallpaper() will fail to identify this as a
-            // TRANSIT_WALLPAPER_OPEN animation, and run some funny animation.
-            final boolean lastActivityTranslucent = lastFocusedRootTask != null
-                    && (lastFocusedRootTask.inMultiWindowMode()
-                    || (lastFocusedRootTask.mLastPausedActivity != null
-                    && !lastFocusedRootTask.mLastPausedActivity.occludesParent()));
-
-            // This activity is now becoming visible.
-            if (!next.mVisibleRequested || next.stopped || lastActivityTranslucent) {
-                next.setVisibility(true);
-            }
-
-            // schedule launch ticks to collect information about slow apps.
-            next.startLaunchTickingLocked();
-
-            ActivityRecord lastResumedActivity =
-                    lastFocusedRootTask == null ? null : lastFocusedRootTask.getResumedActivity();
-            final ActivityState lastState = next.getState();
-
-            mAtmService.updateCpuStats();
-
-            ProtoLog.v(WM_DEBUG_STATES, "Moving to RESUMED: %s (in existing)", next);
-
-            next.setState(RESUMED, "resumeTopActivityInnerLocked");
-
-            // Have the window manager re-evaluate the orientation of
-            // the screen based on the new activity order.
-            boolean notUpdated = true;
-
-            // Activity should also be visible if set mLaunchTaskBehind to true (see
-            // ActivityRecord#shouldBeVisibleIgnoringKeyguard()).
-            if (shouldBeVisible(next)) {
-                // We have special rotation behavior when here is some active activity that
-                // requests specific orientation or Keyguard is locked. Make sure all activity
-                // visibilities are set correctly as well as the transition is updated if needed
-                // to get the correct rotation behavior. Otherwise the following call to update
-                // the orientation may cause incorrect configurations delivered to client as a
-                // result of invisible window resize.
-                // TODO: Remove this once visibilities are set correctly immediately when
-                // starting an activity.
-                notUpdated = !mRootWindowContainer.ensureVisibilityAndConfig(next, getDisplayId(),
-                        true /* markFrozenIfConfigChanged */, false /* deferResume */);
-            }
-
-            if (notUpdated) {
-                // The configuration update wasn't able to keep the existing
-                // instance of the activity, and instead started a new one.
-                // We should be all done, but let's just make sure our activity
-                // is still at the top and schedule another run if something
-                // weird happened.
-                ActivityRecord nextNext = topRunningActivity();
-                ProtoLog.i(WM_DEBUG_STATES, "Activity config changed during resume: "
-                        + "%s, new next: %s", next, nextNext);
-                if (nextNext != next) {
-                    // Do over!
-                    mTaskSupervisor.scheduleResumeTopActivities();
-                }
-                if (!next.mVisibleRequested || next.stopped) {
-                    next.setVisibility(true);
-                }
-                next.completeResumeLocked();
-                return true;
-            }
-
-            try {
-                final ClientTransaction transaction =
-                        ClientTransaction.obtain(next.app.getThread(), next.appToken);
-                // Deliver all pending results.
-                ArrayList<ResultInfo> a = next.results;
-                if (a != null) {
-                    final int N = a.size();
-                    if (!next.finishing && N > 0) {
-                        if (DEBUG_RESULTS) Slog.v(TAG_RESULTS,
-                                "Delivering results to " + next + ": " + a);
-                        transaction.addCallback(ActivityResultItem.obtain(a));
-                    }
-                }
-
-                if (next.newIntents != null) {
-                    transaction.addCallback(
-                            NewIntentItem.obtain(next.newIntents, true /* resume */));
-                }
-
-                // Well the app will no longer be stopped.
-                // Clear app token stopped state in window manager if needed.
-                next.notifyAppResumed(next.stopped);
-
-                EventLogTags.writeWmResumeActivity(next.mUserId, System.identityHashCode(next),
-                        next.getTask().mTaskId, next.shortComponentName);
-
-                mAtmService.getAppWarningsLocked().onResumeActivity(next);
-                next.app.setPendingUiCleanAndForceProcessStateUpTo(mAtmService.mTopProcessState);
-                next.abortAndClearOptionsAnimation();
-                transaction.setLifecycleStateRequest(
-                        ResumeActivityItem.obtain(next.app.getReportedProcState(),
-                                dc.isNextTransitionForward()));
-                mAtmService.getLifecycleManager().scheduleTransaction(transaction);
-
-                ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivityLocked: Resumed %s", next);
-            } catch (Exception e) {
-                // Whoops, need to restart this activity!
-                ProtoLog.v(WM_DEBUG_STATES, "Resume failed; resetting state to %s: "
-                        + "%s", lastState, next);
-                next.setState(lastState, "resumeTopActivityInnerLocked");
-
-                // lastResumedActivity being non-null implies there is a lastStack present.
-                if (lastResumedActivity != null) {
-                    lastResumedActivity.setState(RESUMED, "resumeTopActivityInnerLocked");
-                }
-
-                Slog.i(TAG, "Restarting because process died: " + next);
-                if (!next.hasBeenLaunched) {
-                    next.hasBeenLaunched = true;
-                } else if (SHOW_APP_STARTING_PREVIEW && lastFocusedRootTask != null
-                        && lastFocusedRootTask.isTopRootTaskInDisplayArea()) {
-                    next.showStartingWindow(false /* taskSwitch */);
-                }
-                mTaskSupervisor.startSpecificActivity(next, true, false);
-                return true;
-            }
-
-            // From this point on, if something goes wrong there is no way
-            // to recover the activity.
-            try {
-                next.completeResumeLocked();
-            } catch (Exception e) {
-                // If any exception gets thrown, toss away this
-                // activity and try the next one.
-                Slog.w(TAG, "Exception thrown during resume of " + next, e);
-                next.finishIfPossible("resume-exception", true /* oomAdj */);
-                return true;
-            }
-        } else {
-            // Whoops, need to restart this activity!
-            if (!next.hasBeenLaunched) {
-                next.hasBeenLaunched = true;
-            } else {
-                if (SHOW_APP_STARTING_PREVIEW) {
-                    next.showStartingWindow(false /* taskSwich */);
-                }
-                if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Restarting: " + next);
-            }
-            ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivityLocked: Restarting %s", next);
-            mTaskSupervisor.startSpecificActivity(next, true, true);
-        }
-
-        return true;
+            resumed[0] |= f.resumeTopActivity(prev, options, deferPause);
+        }, true);
+        return resumed[0];
     }
 
     /**
@@ -6601,7 +4977,6 @@
         // Slot the activity into the history root task and proceed
         ProtoLog.i(WM_DEBUG_ADD_REMOVE, "Adding activity %s to task %s "
                         + "callers: %s", r, task, new RuntimeException("here").fillInStackTrace());
-        task.positionChildAtTop(r);
 
         // The transition animation and starting window are not needed if {@code allowMoveToFront}
         // is false, because the activity won't be visible.
@@ -7202,13 +5577,6 @@
         return true;
     }
 
-    /**
-     * Ensures all visible activities at or below the input activity have the right configuration.
-     */
-    void ensureVisibleActivitiesConfiguration(ActivityRecord start, boolean preserveWindow) {
-        mEnsureVisibleActivitiesConfigHelper.process(start, preserveWindow);
-    }
-
     // TODO: Can only be called from special methods in ActivityTaskSupervisor.
     // Need to consolidate those calls points into this resize method so anyone can call directly.
     void resize(Rect displayedBounds, boolean preserveWindows, boolean deferResume) {
@@ -7262,114 +5630,35 @@
         }
     }
 
-    /**
-     * Reset local parameters because an app's activity died.
-     * @param app The app of the activity that died.
-     * @return {@code true} if the process of the pausing activity is died.
-     */
-    boolean handleAppDied(WindowProcessController app) {
-        warnForNonLeafTask("handleAppDied");
-        boolean isPausingDied = false;
-        if (mPausingActivity != null && mPausingActivity.app == app) {
-            ProtoLog.v(WM_DEBUG_STATES, "App died while pausing: %s",
-                    mPausingActivity);
-            mPausingActivity = null;
-            isPausingDied = true;
-        }
-        if (mLastPausedActivity != null && mLastPausedActivity.app == app) {
-            if (mLastPausedActivity.isNoHistory()) {
-                mTaskSupervisor.mNoHistoryActivities.remove(mLastPausedActivity);
-            }
-            mLastPausedActivity = null;
-        }
-        return isPausingDied;
-    }
-
     boolean dump(FileDescriptor fd, PrintWriter pw, boolean dumpAll, boolean dumpClient,
             String dumpPackage, final boolean needSep) {
-        Runnable headerPrinter = () -> {
-            if (needSep) {
-                pw.println();
-            }
-            pw.println("  RootTask #" + getRootTaskId()
-                    + ": type=" + activityTypeToString(getActivityType())
-                    + " mode=" + windowingModeToString(getWindowingMode()));
-            pw.println("  isSleeping=" + shouldSleepActivities());
-            pw.println("  mBounds=" + getRequestedOverrideBounds());
-            pw.println("  mCreatedByOrganizer=" + mCreatedByOrganizer);
-        };
-
-        boolean printed = false;
-
-        if (dumpPackage == null) {
-            // If we are not filtering by package, we want to print absolutely everything,
-            // so always print the header even if there are no tasks/activities inside.
-            headerPrinter.run();
-            headerPrinter = null;
-            printed = true;
-        }
-
-        printed |= printThisActivity(pw, getPausingActivity(), dumpPackage, false,
-                "    mPausingActivity: ", null);
-        printed |= printThisActivity(pw, getResumedActivity(), dumpPackage, false,
-                "    mResumedActivity: ", null);
-        if (dumpAll) {
-            printed |= printThisActivity(pw, mLastPausedActivity, dumpPackage, false,
-                    "    mLastPausedActivity: ", null);
-        }
-
-        printed |= dumpActivities(fd, pw, dumpAll, dumpClient, dumpPackage, false, headerPrinter);
-
-        return printed;
+        return dump("  ", fd, pw, dumpAll, dumpClient, dumpPackage, needSep, null /* header */);
     }
 
-    private boolean dumpActivities(FileDescriptor fd, PrintWriter pw, boolean dumpAll,
-            boolean dumpClient, String dumpPackage, boolean needSep, Runnable header) {
-        if (!hasChild()) {
-            return false;
+    @Override
+    void dumpInner(String prefix, PrintWriter pw, boolean dumpAll, String dumpPackage) {
+        pw.print(prefix); pw.print("* "); pw.println(this);
+        pw.println(prefix + "  mBounds=" + getRequestedOverrideBounds());
+        pw.println(prefix + "  mCreatedByOrganizer=" + mCreatedByOrganizer);
+        if (mLastNonFullscreenBounds != null) {
+            pw.print(prefix); pw.print("  mLastNonFullscreenBounds=");
+            pw.println(mLastNonFullscreenBounds);
         }
-        final AtomicBoolean printedHeader = new AtomicBoolean(false);
-        final AtomicBoolean printed = new AtomicBoolean(false);
-        forAllLeafTasks((task) -> {
-            final String prefix = "    ";
-            Runnable headerPrinter = () -> {
-                printed.set(true);
-                if (!printedHeader.get()) {
-                    if (needSep) {
-                        pw.println("");
-                    }
-                    if (header != null) {
-                        header.run();
-                    }
-                    printedHeader.set(true);
-                }
-                pw.print(prefix); pw.print("* "); pw.println(task);
-                pw.print(prefix); pw.print("  mBounds=");
-                pw.println(task.getRequestedOverrideBounds());
-                pw.print(prefix); pw.print("  mMinWidth="); pw.print(task.mMinWidth);
-                pw.print(" mMinHeight="); pw.println(task.mMinHeight);
-                if (mLastNonFullscreenBounds != null) {
-                    pw.print(prefix);
-                    pw.print("  mLastNonFullscreenBounds=");
-                    pw.println(task.mLastNonFullscreenBounds);
-                }
-                task.dump(pw, prefix + "  ");
-            };
-            if (dumpPackage == null) {
-                // If we are not filtering by package, we want to print absolutely everything,
-                // so always print the header even if there are no activities inside.
-                headerPrinter.run();
-                headerPrinter = null;
+        if (dumpAll) {
+            printThisActivity(pw, mLastPausedActivity, dumpPackage, false,
+                    prefix + "  mLastPausedActivity: ", null);
+        }
+        if (isLeafTask()) {
+            pw.println(prefix + "  isSleeping=" + shouldSleepActivities());
+            printThisActivity(pw, getTopPausingActivity(), dumpPackage, false,
+                    prefix + "  topPausingActivity=", null);
+            printThisActivity(pw, getTopResumedActivity(), dumpPackage, false,
+                    prefix + "  topResumedActivity=", null);
+            if (mMinWidth != INVALID_MIN_SIZE || mMinHeight != INVALID_MIN_SIZE) {
+                pw.print(prefix); pw.print("  mMinWidth="); pw.print(mMinWidth);
+                pw.print(" mMinHeight="); pw.println(mMinHeight);
             }
-            final ArrayList<ActivityRecord> activities = new ArrayList<>();
-            // Add activities by traversing the hierarchy from bottom to top, since activities
-            // are dumped in reverse order in {@link ActivityTaskSupervisor#dumpHistoryList()}.
-            task.forAllActivities((Consumer<ActivityRecord>) activities::add,
-                    false /* traverseTopToBottom */);
-            dumpHistoryList(fd, pw, activities, prefix, "Hist", true, !dumpAll, dumpClient,
-                    dumpPackage, false, headerPrinter, task);
-        }, true /* traverseTopToBottom */);
-        return printed.get();
+        }
     }
 
     ArrayList<ActivityRecord> getDumpActivitiesLocked(String name) {
@@ -7754,7 +6043,8 @@
 
     /** Returns true if a removal action is still being deferred. */
     boolean handleCompleteDeferredRemoval() {
-        if (isAnimating(TRANSITION | CHILDREN)) {
+        if (isAnimating(TRANSITION | CHILDREN)
+                || mAtmService.getTransitionController().inTransition(this)) {
             return true;
         }
 
@@ -7769,6 +6059,7 @@
         return mAnimatingActivityRegistry;
     }
 
+    @Override
     void executeAppTransition(ActivityOptions options) {
         mDisplayContent.executeAppTransition();
         ActivityOptions.abort(options);
@@ -7791,10 +6082,6 @@
         return display != null ? display.isSleeping() : mAtmService.isSleepingLocked();
     }
 
-    boolean shouldSleepOrShutDownActivities() {
-        return shouldSleepActivities() || mAtmService.mShuttingDown;
-    }
-
     private Rect getRawBounds() {
         return super.getBounds();
     }
@@ -7813,14 +6100,12 @@
         }
 
         final long token = proto.start(fieldId);
-        super.dumpDebug(proto, WINDOW_CONTAINER, logLevel);
 
         proto.write(TaskProto.ID, mTaskId);
-        proto.write(DISPLAY_ID, getDisplayId());
         proto.write(ROOT_TASK_ID, getRootTaskId());
 
-        if (mResumedActivity != null) {
-            mResumedActivity.writeIdentifierToProto(proto, RESUMED_ACTIVITY);
+        if (getTopResumedActivity() != null) {
+            getTopResumedActivity().writeIdentifierToProto(proto, RESUMED_ACTIVITY);
         }
         if (realActivity != null) {
             proto.write(REAL_ACTIVITY, realActivity.flattenToShortString());
@@ -7828,11 +6113,7 @@
         if (origActivity != null) {
             proto.write(ORIG_ACTIVITY, origActivity.flattenToShortString());
         }
-        proto.write(ACTIVITY_TYPE, getActivityType());
         proto.write(RESIZE_MODE, mResizeMode);
-        proto.write(MIN_WIDTH, mMinWidth);
-        proto.write(MIN_HEIGHT, mMinHeight);
-
         proto.write(FILLS_PARENT, matchParentBounds());
         getRawBounds().dumpDebug(proto, BOUNDS);
 
@@ -7849,6 +6130,8 @@
         proto.write(AFFINITY, affinity);
         proto.write(HAS_CHILD_PIP_ACTIVITY, mChildPipActivity != null);
 
+        super.dumpDebug(proto, TASK_FRAGMENT, logLevel);
+
         proto.end(token);
     }
 
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index d450dbf..10c16ff 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -34,11 +34,10 @@
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
+import static com.android.server.wm.ActivityRecord.State.RESUMED;
 import static com.android.server.wm.ActivityTaskManagerService.TAG_ROOT_TASK;
 import static com.android.server.wm.DisplayContent.alwaysCreateRootTask;
-import static com.android.server.wm.Task.ActivityState.RESUMED;
-import static com.android.server.wm.Task.TASK_VISIBILITY_VISIBLE;
+import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ROOT_TASK;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
@@ -469,7 +468,7 @@
         // Update the top resumed activity because the preferred top focusable task may be changed.
         mAtmService.mTaskSupervisor.updateTopResumedActivityIfNeeded();
 
-        final ActivityRecord r = child.getResumedActivity();
+        final ActivityRecord r = child.getTopResumedActivity();
         if (r != null && r == mRootWindowContainer.getTopResumedActivity()) {
             mAtmService.setResumedActivityUncheckLocked(r, "positionChildAt");
         }
@@ -1231,7 +1230,7 @@
                                 + adjacentFlagRootTask);
             }
 
-            if (adjacentFlagRootTask.mAdjacentTask == null) {
+            if (adjacentFlagRootTask.getAdjacentTaskFragment() == null) {
                 throw new UnsupportedOperationException(
                         "Can't set non-adjacent root as launch adjacent flag root tr="
                                 + adjacentFlagRootTask);
@@ -1269,8 +1268,8 @@
             // If the adjacent launch is coming from the same root, launch to adjacent root instead.
             if (sourceTask != null
                     && sourceTask.getRootTask().mTaskId == mLaunchAdjacentFlagRootTask.mTaskId
-                    && mLaunchAdjacentFlagRootTask.mAdjacentTask != null) {
-                return mLaunchAdjacentFlagRootTask.mAdjacentTask;
+                    && mLaunchAdjacentFlagRootTask.getAdjacentTaskFragment() != null) {
+                return mLaunchAdjacentFlagRootTask.getAdjacentTaskFragment().asTask();
             } else {
                 return mLaunchAdjacentFlagRootTask;
             }
@@ -1280,8 +1279,10 @@
             if (mLaunchRootTasks.get(i).contains(windowingMode, activityType)) {
                 final Task launchRootTask = mLaunchRootTasks.get(i).task;
                 // Return the focusable root task for improving the UX with staged split screen.
-                final Task adjacentRootTask = launchRootTask != null
-                        ? launchRootTask.mAdjacentTask : null;
+                final TaskFragment adjacentTaskFragment = launchRootTask != null
+                        ? launchRootTask.getAdjacentTaskFragment() : null;
+                final Task adjacentRootTask =
+                        adjacentTaskFragment != null ? adjacentTaskFragment.asTask() : null;
                 if (adjacentRootTask != null && adjacentRootTask.isFocusedRootTaskOnDisplay()) {
                     return adjacentRootTask;
                 } else {
@@ -1373,11 +1374,11 @@
         }
         // TODO(b/111541062): Move this into Task#getResumedActivity()
         // Check if the focused root task has the resumed activity
-        ActivityRecord resumedActivity = focusedRootTask.getResumedActivity();
+        ActivityRecord resumedActivity = focusedRootTask.getTopResumedActivity();
         if (resumedActivity == null || resumedActivity.app == null) {
             // If there is no registered resumed activity in the root task or it is not running -
             // try to use previously resumed one.
-            resumedActivity = focusedRootTask.getPausingActivity();
+            resumedActivity = focusedRootTask.getTopPausingActivity();
             if (resumedActivity == null || resumedActivity.app == null) {
                 // If previously resumed activity doesn't work either - find the topmost running
                 // activity that can be focused.
@@ -1404,7 +1405,7 @@
         // Clear last paused activity if focused root task changed while sleeping, so that the
         // top activity of current focused task can be resumed.
         if (mDisplayContent.isSleeping()) {
-            currentFocusedTask.mLastPausedActivity = null;
+            currentFocusedTask.clearLastPausedActivity();
         }
 
         mLastFocusedRootTask = prevFocusedTask;
@@ -1425,7 +1426,7 @@
                 continue;
             }
 
-            final ActivityRecord r = mChildren.get(i).asTask().getResumedActivity();
+            final ActivityRecord r = mChildren.get(i).asTask().getTopResumedActivity();
             if (r != null && !r.isState(RESUMED)) {
                 return false;
             }
@@ -1451,18 +1452,30 @@
      */
     boolean pauseBackTasks(ActivityRecord resuming) {
         final int[] someActivityPaused = {0};
-        forAllLeafTasks((task) -> {
-            final ActivityRecord resumedActivity = task.getResumedActivity();
-            if (resumedActivity != null
-                    && (task.getVisibility(resuming) != TASK_VISIBILITY_VISIBLE
-                    || !task.isTopActivityFocusable())) {
-                ProtoLog.d(WM_DEBUG_STATES, "pauseBackTasks: task=%s "
-                        + "mResumedActivity=%s", task, resumedActivity);
-                if (task.startPausingLocked(false /* uiSleeping*/,
-                        resuming, "pauseBackTasks")) {
-                    someActivityPaused[0]++;
+        forAllLeafTasks(leafTask -> {
+            // Check if the direct child resumed activity in the leaf task needed to be paused if
+            // the leaf task is not a leaf task fragment.
+            if (!leafTask.isLeafTaskFragment()) {
+                final ActivityRecord top = topRunningActivity();
+                final ActivityRecord resumedActivity = leafTask.getResumedActivity();
+                if (resumedActivity != null && top.getTaskFragment() != leafTask) {
+                    // Pausing the resumed activity because it is occluded by other task fragment.
+                    if (leafTask.startPausing(false /* uiSleeping*/, resuming, "pauseBackTasks")) {
+                        someActivityPaused[0]++;
+                    }
                 }
             }
+
+            leafTask.forAllLeafTaskFragments((taskFrag) -> {
+                final ActivityRecord resumedActivity = taskFrag.getResumedActivity();
+                if (resumedActivity != null
+                        && (taskFrag.getVisibility(resuming) != TASK_FRAGMENT_VISIBILITY_VISIBLE
+                        || !taskFrag.isTopActivityFocusable())) {
+                    if (taskFrag.startPausing(false /* uiSleeping*/, resuming, "pauseBackTasks")) {
+                        someActivityPaused[0]++;
+                    }
+                }
+            }, true /* traverseTopToBottom */);
         }, true /* traverseTopToBottom */);
         return someActivityPaused[0] > 0;
     }
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
new file mode 100644
index 0000000..25d3033
--- /dev/null
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -0,0 +1,2117 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
+import static android.os.UserHandle.USER_NULL;
+import static android.view.Display.INVALID_DISPLAY;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
+import static android.view.WindowManager.TRANSIT_NONE;
+import static android.view.WindowManager.TRANSIT_OPEN;
+
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
+import static com.android.server.wm.ActivityRecord.State.PAUSED;
+import static com.android.server.wm.ActivityRecord.State.PAUSING;
+import static com.android.server.wm.ActivityRecord.State.RESUMED;
+import static com.android.server.wm.ActivityRecord.State.STOPPING;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RESULTS;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TRANSITION;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RESULTS;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_SWITCH;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_TRANSITION;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
+import static com.android.server.wm.ActivityTaskSupervisor.printThisActivity;
+import static com.android.server.wm.IdentifierProto.HASH_CODE;
+import static com.android.server.wm.IdentifierProto.TITLE;
+import static com.android.server.wm.IdentifierProto.USER_ID;
+import static com.android.server.wm.TaskFragmentProto.ACTIVITY_TYPE;
+import static com.android.server.wm.TaskFragmentProto.DISPLAY_ID;
+import static com.android.server.wm.TaskFragmentProto.MIN_HEIGHT;
+import static com.android.server.wm.TaskFragmentProto.MIN_WIDTH;
+import static com.android.server.wm.TaskFragmentProto.WINDOW_CONTAINER;
+import static com.android.server.wm.WindowContainerChildProto.TASK_FRAGMENT;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityOptions;
+import android.app.ResultInfo;
+import android.app.WindowConfiguration;
+import android.app.servertransaction.ActivityResultItem;
+import android.app.servertransaction.ClientTransaction;
+import android.app.servertransaction.NewIntentItem;
+import android.app.servertransaction.PauseActivityItem;
+import android.app.servertransaction.ResumeActivityItem;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.DisplayMetrics;
+import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
+import android.view.DisplayInfo;
+import android.window.ITaskFragmentOrganizer;
+import android.window.TaskFragmentInfo;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.util.function.pooled.PooledFunction;
+import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.internal.util.function.pooled.PooledPredicate;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+/**
+ * A basic container that can be used to contain activities or other {@link TaskFragment}, which
+ * also able to manage the activity lifecycle and updates the visibilities of the activities in it.
+ */
+class TaskFragment extends WindowContainer<WindowContainer> {
+    @IntDef(prefix = {"TASK_FRAGMENT_VISIBILITY"}, value = {
+            TASK_FRAGMENT_VISIBILITY_VISIBLE,
+            TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
+            TASK_FRAGMENT_VISIBILITY_INVISIBLE,
+    })
+    @interface TaskFragmentVisibility {}
+
+    /**
+     * TaskFragment is visible. No other TaskFragment(s) on top that fully or partially occlude it.
+     */
+    static final int TASK_FRAGMENT_VISIBILITY_VISIBLE = 0;
+
+    /** TaskFragment is partially occluded by other translucent TaskFragment(s) on top of it. */
+    static final int TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT = 1;
+
+    /** TaskFragment is completely invisible. */
+    static final int TASK_FRAGMENT_VISIBILITY_INVISIBLE = 2;
+
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskFragment" : TAG_ATM;
+    private static final String TAG_SWITCH = TAG + POSTFIX_SWITCH;
+    private static final String TAG_RESULTS = TAG + POSTFIX_RESULTS;
+    private static final String TAG_TRANSITION = TAG + POSTFIX_TRANSITION;
+
+    /** Set to false to disable the preview that is shown while a new activity is being started. */
+    static final boolean SHOW_APP_STARTING_PREVIEW = true;
+
+    /**
+     * Indicate that the minimal width/height should use the default value.
+     *
+     * @see #mMinWidth
+     * @see #mMinHeight
+     */
+    static final int INVALID_MIN_SIZE = -1;
+
+    final ActivityTaskManagerService mAtmService;
+    final ActivityTaskSupervisor mTaskSupervisor;
+    final RootWindowContainer mRootWindowContainer;
+    private final TaskFragmentOrganizerController mTaskFragmentOrganizerController;
+
+    /**
+     * Minimal width of this task fragment when it's resizeable. {@link #INVALID_MIN_SIZE} means it
+     * should use the default minimal width.
+     */
+    int mMinWidth;
+
+    /**
+     * Minimal height of this task fragment when it's resizeable. {@link #INVALID_MIN_SIZE} means it
+     * should use the default minimal height.
+     */
+    int mMinHeight;
+
+    /** Avoid reentrant of {@link #removeImmediately()}. */
+    private boolean mRemoving;
+
+    // The TaskFragment that adjacent to this one.
+    private TaskFragment mAdjacentTaskFragment;
+
+    /**
+     * When we are in the process of pausing an activity, before starting the
+     * next one, this variable holds the activity that is currently being paused.
+     *
+     * Only set at leaf task fragments.
+     */
+    @Nullable
+    private ActivityRecord mPausingActivity = null;
+
+    /**
+     * This is the last activity that we put into the paused state.  This is
+     * used to determine if we need to do an activity transition while sleeping,
+     * when we normally hold the top activity paused.
+     */
+    ActivityRecord mLastPausedActivity = null;
+
+    /**
+     * Current activity that is resumed, or null if there is none.
+     * Only set at leaf task fragments.
+     */
+    @Nullable
+    private ActivityRecord mResumedActivity = null;
+
+    /**
+     * This TaskFragment was created by an organizer which has the following implementations.
+     * <ul>
+     *     <li>The TaskFragment won't be removed when it is empty. Removal has to be an explicit
+     *     request from the organizer.</li>
+     *     <li>If this fragment is a Task object then unlike other non-root tasks, it's direct
+     *     children are visible to the organizer for ordering purposes.</li>
+     *     <li>A TaskFragment can be created by {@link android.window.TaskFragmentOrganizer}, and
+     *     a Task can be created by {@link android.window.TaskOrganizer}.</li>
+     * </ul>
+     */
+    @VisibleForTesting
+    boolean mCreatedByOrganizer;
+
+    /** Organizer that organizing this TaskFragment. */
+    @Nullable
+    private ITaskFragmentOrganizer mTaskFragmentOrganizer;
+
+    /** Client assigned unique token for this TaskFragment if this is created by an organizer. */
+    @Nullable
+    private IBinder mFragmentToken;
+
+    private final Rect mTmpInsets = new Rect();
+    private final Rect mTmpBounds = new Rect();
+    private final Rect mTmpFullBounds = new Rect();
+    private final Rect mTmpStableBounds = new Rect();
+    private final Rect mTmpNonDecorBounds = new Rect();
+
+    private final EnsureActivitiesVisibleHelper mEnsureActivitiesVisibleHelper =
+            new EnsureActivitiesVisibleHelper(this);
+    private final EnsureVisibleActivitiesConfigHelper mEnsureVisibleActivitiesConfigHelper =
+            new EnsureVisibleActivitiesConfigHelper();
+    private class EnsureVisibleActivitiesConfigHelper {
+        private boolean mUpdateConfig;
+        private boolean mPreserveWindow;
+        private boolean mBehindFullscreen;
+
+        void reset(boolean preserveWindow) {
+            mPreserveWindow = preserveWindow;
+            mUpdateConfig = false;
+            mBehindFullscreen = false;
+        }
+
+        void process(ActivityRecord start, boolean preserveWindow) {
+            if (start == null || !start.mVisibleRequested) {
+                return;
+            }
+            reset(preserveWindow);
+
+            final PooledFunction f = PooledLambda.obtainFunction(
+                    EnsureVisibleActivitiesConfigHelper::processActivity, this,
+                    PooledLambda.__(ActivityRecord.class));
+            forAllActivities(f, start, true /*includeBoundary*/, true /*traverseTopToBottom*/);
+            f.recycle();
+
+            if (mUpdateConfig) {
+                // Ensure the resumed state of the focus activity if we updated the configuration of
+                // any activity.
+                mRootWindowContainer.resumeFocusedTasksTopActivities();
+            }
+        }
+
+        boolean processActivity(ActivityRecord r) {
+            mUpdateConfig |= r.ensureActivityConfiguration(0 /*globalChanges*/, mPreserveWindow);
+            mBehindFullscreen |= r.occludesParent();
+            return mBehindFullscreen;
+        }
+    }
+
+    TaskFragment(ActivityTaskManagerService atmService, IBinder fragmentToken,
+            boolean createdByOrganizer) {
+        super(atmService.mWindowManager);
+
+        mAtmService = atmService;
+        mTaskSupervisor = mAtmService.mTaskSupervisor;
+        mRootWindowContainer = mAtmService.mRootWindowContainer;
+        mCreatedByOrganizer = createdByOrganizer;
+        mTaskFragmentOrganizerController =
+                mAtmService.mWindowOrganizerController.mTaskFragmentOrganizerController;
+        mFragmentToken = fragmentToken;
+        mRemoteToken = new RemoteToken(this);
+    }
+
+    void setAdjacentTaskFragment(TaskFragment taskFragment) {
+        mAdjacentTaskFragment = taskFragment;
+        taskFragment.mAdjacentTaskFragment = this;
+    }
+
+    void setTaskFragmentOrganizer(ITaskFragmentOrganizer organizer) {
+        mTaskFragmentOrganizer = organizer;
+    }
+
+    TaskFragment getAdjacentTaskFragment() {
+        return mAdjacentTaskFragment;
+    }
+
+    /** @return the currently resumed activity. */
+    ActivityRecord getResumedActivity() {
+        return mResumedActivity;
+    }
+
+    void setResumedActivity(ActivityRecord r, String reason) {
+        warnForNonLeafTaskFragment("setResumedActivity");
+        if (mResumedActivity == r) {
+            return;
+        }
+
+        if (ActivityTaskManagerDebugConfig.DEBUG_ROOT_TASK) {
+            Slog.d(TAG, "setResumedActivity taskFrag:" + this + " + from: "
+                    + mResumedActivity + " to:" + r + " reason:" + reason);
+        }
+        mResumedActivity = r;
+        mTaskSupervisor.updateTopResumedActivityIfNeeded();
+    }
+
+    @VisibleForTesting
+    void setPausingActivity(ActivityRecord pausing) {
+        mPausingActivity = pausing;
+    }
+
+    ActivityRecord getPausingActivity() {
+        return mPausingActivity;
+    }
+
+    int getDisplayId() {
+        final DisplayContent dc = getDisplayContent();
+        return dc != null ? dc.mDisplayId : INVALID_DISPLAY;
+    }
+
+    @Nullable
+    Task getTask() {
+        if (asTask() != null) {
+            return asTask();
+        }
+
+        TaskFragment parent = getParent() != null ? getParent().asTaskFragment() : null;
+        return parent != null ? parent.getTask() : null;
+    }
+
+    @Override
+    @Nullable
+    TaskDisplayArea getDisplayArea() {
+        return (TaskDisplayArea) super.getDisplayArea();
+    }
+
+    @Override
+    public boolean isAttached() {
+        final TaskDisplayArea taskDisplayArea = getDisplayArea();
+        return taskDisplayArea != null && !taskDisplayArea.isRemoved();
+    }
+
+    /**
+     * Returns the root {@link TaskFragment}, which is usually also a {@link Task}.
+     */
+    @NonNull
+    TaskFragment getRootTaskFragment() {
+        final WindowContainer parent = getParent();
+        if (parent == null) return this;
+
+        final TaskFragment parentTaskFragment = parent.asTaskFragment();
+        return parentTaskFragment == null ? this : parentTaskFragment.getRootTaskFragment();
+    }
+
+    @Override
+    TaskFragment asTaskFragment() {
+        return this;
+    }
+
+
+    /**
+     * Simply check and give warning logs if this is not operated on leaf {@link TaskFragment}.
+     */
+    private void warnForNonLeafTaskFragment(String func) {
+        if (!isLeafTaskFragment()) {
+            Slog.w(TAG, func + " on non-leaf task fragment " + this);
+        }
+    }
+
+    boolean hasDirectChildActivities() {
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            if (mChildren.get(i).asActivityRecord() != null) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    void cleanUpActivityReferences(@NonNull ActivityRecord r) {
+        if (mPausingActivity != null && mPausingActivity == r) {
+            mPausingActivity = null;
+        }
+
+        if (mResumedActivity != null && mResumedActivity == r) {
+            setResumedActivity(null, "cleanUpActivityReferences");
+        }
+        r.removeTimeouts();
+    }
+
+    /**
+     * Returns whether this TaskFragment is currently forced to be hidden for any reason.
+     */
+    protected boolean isForceHidden() {
+        return false;
+    }
+
+    boolean isLeafTaskFragment() {
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            if (mChildren.get(i).asTaskFragment() != null) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * This should be called when an child activity changes state. This should only
+     * be called from
+     * {@link ActivityRecord#setState(ActivityRecord.State, String)} .
+     * @param record The {@link ActivityRecord} whose state has changed.
+     * @param state The new state.
+     * @param reason The reason for the change.
+     */
+    void onActivityStateChanged(ActivityRecord record, ActivityRecord.State state,
+            String reason) {
+        warnForNonLeafTaskFragment("onActivityStateChanged");
+        if (record == mResumedActivity && state != RESUMED) {
+            setResumedActivity(null, reason + " - onActivityStateChanged");
+        }
+
+        if (state == RESUMED) {
+            if (ActivityTaskManagerDebugConfig.DEBUG_ROOT_TASK) {
+                Slog.v(TAG, "set resumed activity to:" + record + " reason:" + reason);
+            }
+            setResumedActivity(record, reason + " - onActivityStateChanged");
+            if (record == mRootWindowContainer.getTopResumedActivity()) {
+                mAtmService.setResumedActivityUncheckLocked(record, reason);
+            }
+            mTaskSupervisor.mRecentTasks.add(record.getTask());
+        }
+    }
+
+    /**
+     * Resets local parameters because an app's activity died.
+     * @param app The app of the activity that died.
+     * @return {@code true} if the process of the pausing activity is died.
+     */
+    boolean handleAppDied(WindowProcessController app) {
+        warnForNonLeafTaskFragment("handleAppDied");
+        boolean isPausingDied = false;
+        if (mPausingActivity != null && mPausingActivity.app == app) {
+            ProtoLog.v(WM_DEBUG_STATES, "App died while pausing: %s",
+                    mPausingActivity);
+            mPausingActivity = null;
+            isPausingDied = true;
+        }
+        if (mLastPausedActivity != null && mLastPausedActivity.app == app) {
+            if (mLastPausedActivity.isNoHistory()) {
+                mTaskSupervisor.mNoHistoryActivities.remove(mLastPausedActivity);
+            }
+            mLastPausedActivity = null;
+        }
+        return isPausingDied;
+    }
+
+    void awakeFromSleeping() {
+        if (mPausingActivity != null) {
+            Slog.d(TAG, "awakeFromSleeping: previously pausing activity didn't pause");
+            mPausingActivity.activityPaused(true);
+        }
+    }
+
+    /**
+     * Tries to put the activities in the task fragment to sleep.
+     *
+     * If the task fragment is not in a state where its activities can be put to sleep, this
+     * function will start any necessary actions to move the task fragment into such a state.
+     * It is expected that this function get called again when those actions complete.
+     *
+     * @param shuttingDown {@code true} when the called because the device is shutting down.
+     * @return true if the root task finished going to sleep, false if the root task only started
+     * the process of going to sleep (checkReadyForSleep will be called when that process finishes).
+     */
+    boolean sleepIfPossible(boolean shuttingDown) {
+        boolean shouldSleep = true;
+        if (mResumedActivity != null) {
+            // Still have something resumed; can't sleep until it is paused.
+            ProtoLog.v(WM_DEBUG_STATES, "Sleep needs to pause %s", mResumedActivity);
+            startPausing(false /* userLeaving */, true /* uiSleeping */, null /* resuming */,
+                    "sleep");
+            shouldSleep = false;
+        } else if (mPausingActivity != null) {
+            // Still waiting for something to pause; can't sleep yet.
+            ProtoLog.v(WM_DEBUG_STATES, "Sleep still waiting to pause %s", mPausingActivity);
+            shouldSleep = false;
+        }
+
+        if (!shuttingDown) {
+            if (containsStoppingActivity()) {
+                // Still need to tell some activities to stop; can't sleep yet.
+                ProtoLog.v(WM_DEBUG_STATES, "Sleep still need to stop %d activities",
+                        mTaskSupervisor.mStoppingActivities.size());
+
+                mTaskSupervisor.scheduleIdle();
+                shouldSleep = false;
+            }
+        }
+
+        if (shouldSleep) {
+            updateActivityVisibilities(null /* starting */, 0 /* configChanges */,
+                    !PRESERVE_WINDOWS, true /* notifyClients */);
+        }
+
+        return shouldSleep;
+    }
+
+    private boolean containsStoppingActivity() {
+        for (int i = mTaskSupervisor.mStoppingActivities.size() - 1; i >= 0; --i) {
+            ActivityRecord r = mTaskSupervisor.mStoppingActivities.get(i);
+            if (r.getTaskFragment() == this) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns true if the TaskFragment is translucent and can have other contents visible behind
+     * it if needed. A TaskFragment is considered translucent if it don't contain a visible or
+     * starting (about to be visible) activity that is fullscreen (opaque).
+     * @param starting The currently starting activity or null if there is none.
+     */
+    @VisibleForTesting
+    boolean isTranslucent(ActivityRecord starting) {
+        if (!isAttached() || isForceHidden()) {
+            return true;
+        }
+        final PooledPredicate p = PooledLambda.obtainPredicate(TaskFragment::isOpaqueActivity,
+                PooledLambda.__(ActivityRecord.class), starting);
+        final ActivityRecord opaque = getActivity(p);
+        p.recycle();
+        return opaque == null;
+    }
+
+    private static boolean isOpaqueActivity(ActivityRecord r, ActivityRecord starting) {
+        if (r.finishing) {
+            // We don't factor in finishing activities when determining translucency since
+            // they will be gone soon.
+            return false;
+        }
+
+        if (!r.visibleIgnoringKeyguard && r != starting) {
+            // Also ignore invisible activities that are not the currently starting
+            // activity (about to be visible).
+            return false;
+        }
+
+        if (r.occludesParent()) {
+            // Root task isn't translucent if it has at least one fullscreen activity
+            // that is visible.
+            return true;
+        }
+        return false;
+    }
+
+    ActivityRecord topRunningActivity() {
+        return topRunningActivity(false /* focusableOnly */);
+    }
+
+    ActivityRecord topRunningActivity(boolean focusableOnly) {
+        // Split into 2 to avoid object creation due to variable capture.
+        if (focusableOnly) {
+            return getActivity((r) -> r.canBeTopRunning() && r.isFocusable());
+        } else {
+            return getActivity(ActivityRecord::canBeTopRunning);
+        }
+    }
+
+    boolean isTopActivityFocusable() {
+        final ActivityRecord r = topRunningActivity();
+        return r != null ? r.isFocusable()
+                : (isFocusable() && getWindowConfiguration().canReceiveKeys());
+    }
+
+    /**
+     * Returns the visibility state of this TaskFragment.
+     *
+     * @param starting The currently starting activity or null if there is none.
+     */
+    @TaskFragmentVisibility
+    int getVisibility(ActivityRecord starting) {
+        if (!isAttached() || isForceHidden()) {
+            return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
+        }
+
+        if (isTopActivityLaunchedBehind()) {
+            return TASK_FRAGMENT_VISIBILITY_VISIBLE;
+        }
+
+        boolean gotRootSplitScreenFragment = false;
+        boolean gotOpaqueSplitScreenPrimary = false;
+        boolean gotOpaqueSplitScreenSecondary = false;
+        boolean gotTranslucentFullscreen = false;
+        boolean gotTranslucentSplitScreenPrimary = false;
+        boolean gotTranslucentSplitScreenSecondary = false;
+        boolean shouldBeVisible = true;
+
+        // This TaskFragment is only considered visible if all its parent TaskFragments are
+        // considered visible, so check the visibility of all ancestor TaskFragment first.
+        final WindowContainer parent = getParent();
+        if (parent.asTaskFragment() != null) {
+            final int parentVisibility = parent.asTaskFragment().getVisibility(starting);
+            if (parentVisibility == TASK_FRAGMENT_VISIBILITY_INVISIBLE) {
+                // Can't be visible if parent isn't visible
+                return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
+            } else if (parentVisibility == TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT) {
+                // Parent is behind a translucent container so the highest visibility this container
+                // can get is that.
+                gotTranslucentFullscreen = true;
+            }
+        }
+
+        final List<TaskFragment> adjacentTaskFragments = new ArrayList<>();
+        final int windowingMode = getWindowingMode();
+        final boolean isAssistantType = isActivityTypeAssistant();
+        for (int i = parent.getChildCount() - 1; i >= 0; --i) {
+            final WindowContainer other = parent.getChildAt(i);
+            if (other == null) continue;
+
+            final boolean hasRunningActivities = hasRunningActivity(other);
+            if (other == this) {
+                // Should be visible if there is no other fragment occluding it, unless it doesn't
+                // have any running activities, not starting one and not home stack.
+                shouldBeVisible = hasRunningActivities
+                        || (starting != null && starting.isDescendantOf(this))
+                        || isActivityTypeHome();
+                break;
+            }
+
+            if (!hasRunningActivities) {
+                continue;
+            }
+
+            final int otherWindowingMode = other.getWindowingMode();
+            if (otherWindowingMode == WINDOWING_MODE_FULLSCREEN) {
+                if (isTranslucent(other, starting)) {
+                    // Can be visible behind a translucent fullscreen TaskFragment.
+                    gotTranslucentFullscreen = true;
+                    continue;
+                }
+                return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
+            } else if (otherWindowingMode == WINDOWING_MODE_MULTI_WINDOW
+                    && other.matchParentBounds()) {
+                if (isTranslucent(other, starting)) {
+                    // Can be visible behind a translucent TaskFragment.
+                    gotTranslucentFullscreen = true;
+                    continue;
+                }
+                // Multi-window TaskFragment that matches parent bounds would occlude other children
+                return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
+            } else if (otherWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+                    && !gotOpaqueSplitScreenPrimary) {
+                gotRootSplitScreenFragment = true;
+                gotTranslucentSplitScreenPrimary = isTranslucent(other, starting);
+                gotOpaqueSplitScreenPrimary = !gotTranslucentSplitScreenPrimary;
+                if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+                        && gotOpaqueSplitScreenPrimary) {
+                    // Can't be visible behind another opaque TaskFragment in split-screen-primary.
+                    return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
+                }
+            } else if (otherWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
+                    && !gotOpaqueSplitScreenSecondary) {
+                gotRootSplitScreenFragment = true;
+                gotTranslucentSplitScreenSecondary = isTranslucent(other, starting);
+                gotOpaqueSplitScreenSecondary = !gotTranslucentSplitScreenSecondary;
+                if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
+                        && gotOpaqueSplitScreenSecondary) {
+                    // Can't be visible behind another opaque TaskFragment in split-screen-secondary
+                    return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
+                }
+            }
+            if (gotOpaqueSplitScreenPrimary && gotOpaqueSplitScreenSecondary) {
+                // Can not be visible if we are in split-screen windowing mode and both halves of
+                // the screen are opaque.
+                return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
+            }
+            if (isAssistantType && gotRootSplitScreenFragment) {
+                // Assistant TaskFragment can't be visible behind split-screen. In addition to
+                // this not making sense, it also works around an issue here we boost the z-order
+                // of the assistant window surfaces in window manager whenever it is visible.
+                return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
+            }
+
+            final TaskFragment otherTaskFrag = other.asTaskFragment();
+            if (otherTaskFrag != null && otherTaskFrag.mAdjacentTaskFragment != null) {
+                if (adjacentTaskFragments.contains(otherTaskFrag.mAdjacentTaskFragment)) {
+                    if (otherTaskFrag.isTranslucent(starting)
+                            || otherTaskFrag.mAdjacentTaskFragment.isTranslucent(starting)) {
+                        // Can be visible behind a translucent adjacent TaskFragments.
+                        gotTranslucentFullscreen = true;
+                        continue;
+                    }
+                    // Can not be visible behind adjacent TaskFragments.
+                    return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
+                } else {
+                    adjacentTaskFragments.add(otherTaskFrag);
+                }
+            }
+
+        }
+
+        if (!shouldBeVisible) {
+            return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
+        }
+
+        // Handle cases when there can be a translucent split-screen TaskFragment on top.
+        switch (windowingMode) {
+            case WINDOWING_MODE_FULLSCREEN:
+                if (gotTranslucentSplitScreenPrimary || gotTranslucentSplitScreenSecondary) {
+                    // At least one of the split-screen TaskFragment that covers this one is
+                    // translucent.
+                    // When in split mode, home will be reparented to the secondary split while
+                    // leaving TaskFragments not supporting split below. Due to
+                    // TaskDisplayArea#assignRootTaskOrdering always adjusts home surface layer to
+                    // the bottom, this makes sure TaskFragments not in split roots won't occlude
+                    // home task unexpectedly.
+                    return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
+                }
+                break;
+            case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY:
+                if (gotTranslucentSplitScreenPrimary) {
+                    // Covered by translucent primary split-screen on top.
+                    return TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT;
+                }
+                break;
+            case WINDOWING_MODE_SPLIT_SCREEN_SECONDARY:
+                if (gotTranslucentSplitScreenSecondary) {
+                    // Covered by translucent secondary split-screen on top.
+                    return TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT;
+                }
+                break;
+        }
+
+        // Lastly - check if there is a translucent fullscreen TaskFragment on top.
+        return gotTranslucentFullscreen
+                ? TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT
+                : TASK_FRAGMENT_VISIBILITY_VISIBLE;
+    }
+
+    private static boolean hasRunningActivity(WindowContainer wc) {
+        if (wc.asTaskFragment() != null) {
+            return wc.asTaskFragment().topRunningActivity() != null;
+        }
+        return wc.asActivityRecord() != null && !wc.asActivityRecord().finishing;
+    }
+
+    private static boolean isTranslucent(WindowContainer wc, ActivityRecord starting) {
+        if (wc.asTaskFragment() != null) {
+            return wc.asTaskFragment().isTranslucent(starting);
+        } else if (wc.asActivityRecord() != null) {
+            return !wc.asActivityRecord().occludesParent();
+        }
+        return false;
+    }
+
+
+    private boolean isTopActivityLaunchedBehind() {
+        final ActivityRecord top = topRunningActivity();
+        return top != null && top.mLaunchTaskBehind;
+    }
+
+    final void updateActivityVisibilities(@Nullable ActivityRecord starting, int configChanges,
+            boolean preserveWindows, boolean notifyClients) {
+        mTaskSupervisor.beginActivityVisibilityUpdate();
+        try {
+            mEnsureActivitiesVisibleHelper.process(
+                    starting, configChanges, preserveWindows, notifyClients);
+        } finally {
+            mTaskSupervisor.endActivityVisibilityUpdate();
+        }
+    }
+
+    final boolean resumeTopActivity(ActivityRecord prev, ActivityOptions options,
+            boolean deferPause) {
+        ActivityRecord next = topRunningActivity(true /* focusableOnly */);
+        if (next == null || !next.canResumeByCompat()) {
+            return false;
+        }
+
+        next.delayedResume = false;
+        final TaskDisplayArea taskDisplayArea = getDisplayArea();
+
+        // If the top activity is the resumed one, nothing to do.
+        if (mResumedActivity == next && next.isState(RESUMED)
+                && taskDisplayArea.allResumedActivitiesComplete()) {
+            // Make sure we have executed any pending transitions, since there
+            // should be nothing left to do at this point.
+            executeAppTransition(options);
+            // For devices that are not in fullscreen mode (e.g. freeform windows), it's possible
+            // we still want to check if the visibility of other windows have changed (e.g. bringing
+            // a fullscreen window forward to cover another freeform activity.)
+            if (taskDisplayArea.inMultiWindowMode()) {
+                taskDisplayArea.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
+                        false /* preserveWindows */, true /* notifyClients */);
+            }
+            ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Top activity "
+                    + "resumed %s", next);
+            return false;
+        }
+
+        // If we are currently pausing an activity, then don't do anything until that is done.
+        final boolean allPausedComplete = mRootWindowContainer.allPausedActivitiesComplete();
+        if (!allPausedComplete) {
+            ProtoLog.v(WM_DEBUG_STATES,
+                    "resumeTopActivity: Skip resume: some activity pausing.");
+            return false;
+        }
+
+        // If we are sleeping, and there is no resumed activity, and the top activity is paused,
+        // well that is the state we want.
+        if (mLastPausedActivity == next && shouldSleepOrShutDownActivities()) {
+            // Make sure we have executed any pending transitions, since there
+            // should be nothing left to do at this point.
+            executeAppTransition(options);
+            ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Going to sleep and"
+                    + " all paused");
+            return false;
+        }
+
+        // Make sure that the user who owns this activity is started.  If not,
+        // we will just leave it as is because someone should be bringing
+        // another user's activities to the top of the stack.
+        if (!mAtmService.mAmInternal.hasStartedUserState(next.mUserId)) {
+            Slog.w(TAG, "Skipping resume of top activity " + next
+                    + ": user " + next.mUserId + " is stopped");
+            return false;
+        }
+
+        // The activity may be waiting for stop, but that is no longer
+        // appropriate for it.
+        mTaskSupervisor.mStoppingActivities.remove(next);
+
+        if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Resuming " + next);
+
+        mTaskSupervisor.setLaunchSource(next.info.applicationInfo.uid);
+
+        ActivityRecord lastResumed = null;
+        final Task lastFocusedRootTask = taskDisplayArea.getLastFocusedRootTask();
+        if (lastFocusedRootTask != null && lastFocusedRootTask != getRootTaskFragment().asTask()) {
+            // So, why aren't we using prev here??? See the param comment on the method. prev
+            // doesn't represent the last resumed activity. However, the last focus stack does if
+            // it isn't null.
+            lastResumed = lastFocusedRootTask.getTopResumedActivity();
+        }
+
+        boolean pausing = !deferPause && taskDisplayArea.pauseBackTasks(next);
+        if (mResumedActivity != null) {
+            ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Pausing %s", mResumedActivity);
+            pausing |= startPausing(mTaskSupervisor.mUserLeaving, false /* uiSleeping */,
+                    next, "resumeTopActivity");
+        }
+        if (pausing) {
+            ProtoLog.v(WM_DEBUG_STATES, "resumeTopActivity: Skip resume: need to"
+                    + " start pausing");
+            // At this point we want to put the upcoming activity's process
+            // at the top of the LRU list, since we know we will be needing it
+            // very soon and it would be a waste to let it get killed if it
+            // happens to be sitting towards the end.
+            if (next.attachedToProcess()) {
+                next.app.updateProcessInfo(false /* updateServiceConnectionActivities */,
+                        true /* activityChange */, false /* updateOomAdj */,
+                        false /* addPendingTopUid */);
+            } else if (!next.isProcessRunning()) {
+                // Since the start-process is asynchronous, if we already know the process of next
+                // activity isn't running, we can start the process earlier to save the time to wait
+                // for the current activity to be paused.
+                final boolean isTop = this == taskDisplayArea.getFocusedRootTask();
+                mAtmService.startProcessAsync(next, false /* knownToBeDead */, isTop,
+                        isTop ? "pre-top-activity" : "pre-activity");
+            }
+            if (lastResumed != null) {
+                lastResumed.setWillCloseOrEnterPip(true);
+            }
+            return true;
+        } else if (mResumedActivity == next && next.isState(RESUMED)
+                && taskDisplayArea.allResumedActivitiesComplete()) {
+            // It is possible for the activity to be resumed when we paused back stacks above if the
+            // next activity doesn't have to wait for pause to complete.
+            // So, nothing else to-do except:
+            // Make sure we have executed any pending transitions, since there
+            // should be nothing left to do at this point.
+            executeAppTransition(options);
+            ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Top activity resumed "
+                    + "(dontWaitForPause) %s", next);
+            return true;
+        }
+
+        // If the most recent activity was noHistory but was only stopped rather
+        // than stopped+finished because the device went to sleep, we need to make
+        // sure to finish it as we're making a new activity topmost.
+        if (shouldSleepActivities()) {
+            mTaskSupervisor.finishNoHistoryActivitiesIfNeeded(next);
+        }
+
+        if (prev != null && prev != next && next.nowVisible) {
+            // The next activity is already visible, so hide the previous
+            // activity's windows right now so we can show the new one ASAP.
+            // We only do this if the previous is finishing, which should mean
+            // it is on top of the one being resumed so hiding it quickly
+            // is good.  Otherwise, we want to do the normal route of allowing
+            // the resumed activity to be shown so we can decide if the
+            // previous should actually be hidden depending on whether the
+            // new one is found to be full-screen or not.
+            if (prev.finishing) {
+                prev.setVisibility(false);
+                if (DEBUG_SWITCH) {
+                    Slog.v(TAG_SWITCH, "Not waiting for visible to hide: " + prev
+                            + ", nowVisible=" + next.nowVisible);
+                }
+            } else {
+                if (DEBUG_SWITCH) {
+                    Slog.v(TAG_SWITCH, "Previous already visible but still waiting to hide: " + prev
+                            + ", nowVisible=" + next.nowVisible);
+                }
+            }
+        }
+
+        // Launching this app's activity, make sure the app is no longer
+        // considered stopped.
+        try {
+            mTaskSupervisor.getActivityMetricsLogger()
+                    .notifyBeforePackageUnstopped(next.packageName);
+            mAtmService.getPackageManager().setPackageStoppedState(
+                    next.packageName, false, next.mUserId); /* TODO: Verify if correct userid */
+        } catch (RemoteException e1) {
+        } catch (IllegalArgumentException e) {
+            Slog.w(TAG, "Failed trying to unstop package "
+                    + next.packageName + ": " + e);
+        }
+
+        // We are starting up the next activity, so tell the window manager
+        // that the previous one will be hidden soon.  This way it can know
+        // to ignore it when computing the desired screen orientation.
+        boolean anim = true;
+        final DisplayContent dc = taskDisplayArea.mDisplayContent;
+        if (prev != null) {
+            if (prev.finishing) {
+                if (DEBUG_TRANSITION) {
+                    Slog.v(TAG_TRANSITION, "Prepare close transition: prev=" + prev);
+                }
+                if (mTaskSupervisor.mNoAnimActivities.contains(prev)) {
+                    anim = false;
+                    dc.prepareAppTransition(TRANSIT_NONE);
+                } else {
+                    dc.prepareAppTransition(TRANSIT_CLOSE);
+                }
+                prev.setVisibility(false);
+            } else {
+                if (DEBUG_TRANSITION) {
+                    Slog.v(TAG_TRANSITION, "Prepare open transition: prev=" + prev);
+                }
+                if (mTaskSupervisor.mNoAnimActivities.contains(next)) {
+                    anim = false;
+                    dc.prepareAppTransition(TRANSIT_NONE);
+                } else {
+                    dc.prepareAppTransition(TRANSIT_OPEN,
+                            next.mLaunchTaskBehind ? TRANSIT_FLAG_OPEN_BEHIND : 0);
+                }
+            }
+        } else {
+            if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare open transition: no previous");
+            if (mTaskSupervisor.mNoAnimActivities.contains(next)) {
+                anim = false;
+                dc.prepareAppTransition(TRANSIT_NONE);
+            } else {
+                dc.prepareAppTransition(TRANSIT_OPEN);
+            }
+        }
+
+        if (anim) {
+            next.applyOptionsAnimation();
+        } else {
+            next.abortAndClearOptionsAnimation();
+        }
+
+        mTaskSupervisor.mNoAnimActivities.clear();
+
+        if (next.attachedToProcess()) {
+            if (DEBUG_SWITCH) {
+                Slog.v(TAG_SWITCH, "Resume running: " + next + " stopped=" + next.stopped
+                        + " visibleRequested=" + next.mVisibleRequested);
+            }
+
+            // If the previous activity is translucent, force a visibility update of
+            // the next activity, so that it's added to WM's opening app list, and
+            // transition animation can be set up properly.
+            // For example, pressing Home button with a translucent activity in focus.
+            // Launcher is already visible in this case. If we don't add it to opening
+            // apps, maybeUpdateTransitToWallpaper() will fail to identify this as a
+            // TRANSIT_WALLPAPER_OPEN animation, and run some funny animation.
+            final boolean lastActivityTranslucent = inMultiWindowMode()
+                    || mLastPausedActivity != null && !mLastPausedActivity.occludesParent();
+
+            // This activity is now becoming visible.
+            if (!next.mVisibleRequested || next.stopped || lastActivityTranslucent) {
+                next.setVisibility(true);
+            }
+
+            // schedule launch ticks to collect information about slow apps.
+            next.startLaunchTickingLocked();
+
+            ActivityRecord lastResumedActivity =
+                    lastFocusedRootTask == null ? null
+                            : lastFocusedRootTask.getTopResumedActivity();
+            final ActivityRecord.State lastState = next.getState();
+
+            mAtmService.updateCpuStats();
+
+            ProtoLog.v(WM_DEBUG_STATES, "Moving to RESUMED: %s (in existing)", next);
+
+            next.setState(RESUMED, "resumeTopActivity");
+
+            // Have the window manager re-evaluate the orientation of
+            // the screen based on the new activity order.
+            boolean notUpdated = true;
+
+            // Activity should also be visible if set mLaunchTaskBehind to true (see
+            // ActivityRecord#shouldBeVisibleIgnoringKeyguard()).
+            if (shouldBeVisible(next)) {
+                // We have special rotation behavior when here is some active activity that
+                // requests specific orientation or Keyguard is locked. Make sure all activity
+                // visibilities are set correctly as well as the transition is updated if needed
+                // to get the correct rotation behavior. Otherwise the following call to update
+                // the orientation may cause incorrect configurations delivered to client as a
+                // result of invisible window resize.
+                // TODO: Remove this once visibilities are set correctly immediately when
+                // starting an activity.
+                notUpdated = !mRootWindowContainer.ensureVisibilityAndConfig(next, getDisplayId(),
+                        true /* markFrozenIfConfigChanged */, false /* deferResume */);
+            }
+
+            if (notUpdated) {
+                // The configuration update wasn't able to keep the existing
+                // instance of the activity, and instead started a new one.
+                // We should be all done, but let's just make sure our activity
+                // is still at the top and schedule another run if something
+                // weird happened.
+                ActivityRecord nextNext = topRunningActivity();
+                ProtoLog.i(WM_DEBUG_STATES, "Activity config changed during resume: "
+                        + "%s, new next: %s", next, nextNext);
+                if (nextNext != next) {
+                    // Do over!
+                    mTaskSupervisor.scheduleResumeTopActivities();
+                }
+                if (!next.mVisibleRequested || next.stopped) {
+                    next.setVisibility(true);
+                }
+                next.completeResumeLocked();
+                return true;
+            }
+
+            try {
+                final ClientTransaction transaction =
+                        ClientTransaction.obtain(next.app.getThread(), next.appToken);
+                // Deliver all pending results.
+                ArrayList<ResultInfo> a = next.results;
+                if (a != null) {
+                    final int size = a.size();
+                    if (!next.finishing && size > 0) {
+                        if (DEBUG_RESULTS) {
+                            Slog.v(TAG_RESULTS, "Delivering results to " + next + ": " + a);
+                        }
+                        transaction.addCallback(ActivityResultItem.obtain(a));
+                    }
+                }
+
+                if (next.newIntents != null) {
+                    transaction.addCallback(
+                            NewIntentItem.obtain(next.newIntents, true /* resume */));
+                }
+
+                // Well the app will no longer be stopped.
+                // Clear app token stopped state in window manager if needed.
+                next.notifyAppResumed(next.stopped);
+
+                EventLogTags.writeWmResumeActivity(next.mUserId, System.identityHashCode(next),
+                        next.getTask().mTaskId, next.shortComponentName);
+
+                mAtmService.getAppWarningsLocked().onResumeActivity(next);
+                next.app.setPendingUiCleanAndForceProcessStateUpTo(mAtmService.mTopProcessState);
+                next.abortAndClearOptionsAnimation();
+                transaction.setLifecycleStateRequest(
+                        ResumeActivityItem.obtain(next.app.getReportedProcState(),
+                                dc.isNextTransitionForward()));
+                mAtmService.getLifecycleManager().scheduleTransaction(transaction);
+
+                ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Resumed %s", next);
+            } catch (Exception e) {
+                // Whoops, need to restart this activity!
+                ProtoLog.v(WM_DEBUG_STATES, "Resume failed; resetting state to %s: "
+                        + "%s", lastState, next);
+                next.setState(lastState, "resumeTopActivityInnerLocked");
+
+                // lastResumedActivity being non-null implies there is a lastStack present.
+                if (lastResumedActivity != null) {
+                    lastResumedActivity.setState(RESUMED, "resumeTopActivityInnerLocked");
+                }
+
+                Slog.i(TAG, "Restarting because process died: " + next);
+                if (!next.hasBeenLaunched) {
+                    next.hasBeenLaunched = true;
+                } else if (SHOW_APP_STARTING_PREVIEW && lastFocusedRootTask != null
+                        && lastFocusedRootTask.isTopRootTaskInDisplayArea()) {
+                    next.showStartingWindow(false /* taskSwitch */);
+                }
+                mTaskSupervisor.startSpecificActivity(next, true, false);
+                return true;
+            }
+
+            // From this point on, if something goes wrong there is no way
+            // to recover the activity.
+            try {
+                next.completeResumeLocked();
+            } catch (Exception e) {
+                // If any exception gets thrown, toss away this
+                // activity and try the next one.
+                Slog.w(TAG, "Exception thrown during resume of " + next, e);
+                next.finishIfPossible("resume-exception", true /* oomAdj */);
+                return true;
+            }
+        } else {
+            // Whoops, need to restart this activity!
+            if (!next.hasBeenLaunched) {
+                next.hasBeenLaunched = true;
+            } else {
+                if (SHOW_APP_STARTING_PREVIEW) {
+                    next.showStartingWindow(false /* taskSwich */);
+                }
+                if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Restarting: " + next);
+            }
+            ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Restarting %s", next);
+            mTaskSupervisor.startSpecificActivity(next, true, true);
+        }
+
+        return true;
+    }
+
+    boolean shouldSleepOrShutDownActivities() {
+        return shouldSleepActivities() || mAtmService.mShuttingDown;
+    }
+
+    /**
+     * Returns true if the TaskFragment should be visible.
+     *
+     * @param starting The currently starting activity or null if there is none.
+     */
+    boolean shouldBeVisible(ActivityRecord starting) {
+        return getVisibility(starting) != TASK_FRAGMENT_VISIBILITY_INVISIBLE;
+    }
+
+    final boolean startPausing(boolean uiSleeping, ActivityRecord resuming, String reason) {
+        return startPausing(mTaskSupervisor.mUserLeaving, uiSleeping, resuming, reason);
+    }
+
+    /**
+     * Start pausing the currently resumed activity.  It is an error to call this if there
+     * is already an activity being paused or there is no resumed activity.
+     *
+     * @param userLeaving True if this should result in an onUserLeaving to the current activity.
+     * @param uiSleeping True if this is happening with the user interface going to sleep (the
+     * screen turning off).
+     * @param resuming The activity we are currently trying to resume or null if this is not being
+     *                 called as part of resuming the top activity, so we shouldn't try to instigate
+     *                 a resume here if not null.
+     * @param reason The reason of pausing the activity.
+     * @return Returns true if an activity now is in the PAUSING state, and we are waiting for
+     * it to tell us when it is done.
+     */
+    boolean startPausing(boolean userLeaving, boolean uiSleeping, ActivityRecord resuming,
+            String reason) {
+        if (!hasDirectChildActivities()) {
+            return false;
+        }
+
+        ProtoLog.d(WM_DEBUG_STATES, "startPausing: taskFrag =%s " + "mResumedActivity=%s", this,
+                mResumedActivity);
+
+        if (mPausingActivity != null) {
+            Slog.wtf(TAG, "Going to pause when pause is already pending for " + mPausingActivity
+                    + " state=" + mPausingActivity.getState());
+            if (!shouldSleepActivities()) {
+                // Avoid recursion among check for sleep and complete pause during sleeping.
+                // Because activity will be paused immediately after resume, just let pause
+                // be completed by the order of activity paused from clients.
+                completePause(false, resuming);
+            }
+        }
+        ActivityRecord prev = mResumedActivity;
+
+        if (prev == null) {
+            if (resuming == null) {
+                Slog.wtf(TAG, "Trying to pause when nothing is resumed");
+                mRootWindowContainer.resumeFocusedTasksTopActivities();
+            }
+            return false;
+        }
+
+        if (prev == resuming) {
+            Slog.wtf(TAG, "Trying to pause activity that is in process of being resumed");
+            return false;
+        }
+
+        ProtoLog.v(WM_DEBUG_STATES, "Moving to PAUSING: %s", prev);
+        mPausingActivity = prev;
+        mLastPausedActivity = prev;
+        if (prev.isNoHistory() && !mTaskSupervisor.mNoHistoryActivities.contains(prev)) {
+            mTaskSupervisor.mNoHistoryActivities.add(prev);
+        }
+        prev.setState(PAUSING, "startPausingLocked");
+        prev.getTask().touchActiveTime();
+
+        mAtmService.updateCpuStats();
+
+        boolean pauseImmediately = false;
+        boolean shouldAutoPip = false;
+        if (resuming != null && (resuming.info.flags & FLAG_RESUME_WHILE_PAUSING) != 0) {
+            // If the flag RESUME_WHILE_PAUSING is set, then continue to schedule the previous
+            // activity to be paused, while at the same time resuming the new resume activity
+            // only if the previous activity can't go into Pip since we want to give Pip
+            // activities a chance to enter Pip before resuming the next activity.
+            final boolean lastResumedCanPip = prev != null && prev.checkEnterPictureInPictureState(
+                    "shouldResumeWhilePausing", userLeaving);
+            if (lastResumedCanPip && prev.pictureInPictureArgs.isAutoEnterEnabled()) {
+                shouldAutoPip = true;
+            } else if (!lastResumedCanPip) {
+                pauseImmediately = true;
+            } else {
+                // The previous activity may still enter PIP even though it did not allow auto-PIP.
+            }
+        }
+
+        boolean didAutoPip = false;
+        if (prev.attachedToProcess()) {
+            if (shouldAutoPip) {
+                ProtoLog.d(WM_DEBUG_STATES, "Auto-PIP allowed, entering PIP mode "
+                        + "directly: %s", prev);
+
+                didAutoPip = mAtmService.enterPictureInPictureMode(prev, prev.pictureInPictureArgs);
+                mPausingActivity = null;
+            } else {
+                ProtoLog.v(WM_DEBUG_STATES, "Enqueueing pending pause: %s", prev);
+                try {
+                    EventLogTags.writeWmPauseActivity(prev.mUserId, System.identityHashCode(prev),
+                            prev.shortComponentName, "userLeaving=" + userLeaving, reason);
+
+                    mAtmService.getLifecycleManager().scheduleTransaction(prev.app.getThread(),
+                            prev.appToken, PauseActivityItem.obtain(prev.finishing, userLeaving,
+                                    prev.configChangeFlags, pauseImmediately));
+                } catch (Exception e) {
+                    // Ignore exception, if process died other code will cleanup.
+                    Slog.w(TAG, "Exception thrown during pause", e);
+                    mPausingActivity = null;
+                    mLastPausedActivity = null;
+                    mTaskSupervisor.mNoHistoryActivities.remove(prev);
+                }
+            }
+        } else {
+            mPausingActivity = null;
+            mLastPausedActivity = null;
+            mTaskSupervisor.mNoHistoryActivities.remove(prev);
+        }
+
+        // If we are not going to sleep, we want to ensure the device is
+        // awake until the next activity is started.
+        if (!uiSleeping && !mAtmService.isSleepingOrShuttingDownLocked()) {
+            mTaskSupervisor.acquireLaunchWakelock();
+        }
+
+        // If already entered PIP mode, no need to keep pausing.
+        if (mPausingActivity != null && !didAutoPip) {
+            // Have the window manager pause its key dispatching until the new
+            // activity has started.  If we're pausing the activity just because
+            // the screen is being turned off and the UI is sleeping, don't interrupt
+            // key dispatch; the same activity will pick it up again on wakeup.
+            if (!uiSleeping) {
+                prev.pauseKeyDispatchingLocked();
+            } else {
+                ProtoLog.v(WM_DEBUG_STATES, "Key dispatch not paused for screen off");
+            }
+
+            if (pauseImmediately) {
+                // If the caller said they don't want to wait for the pause, then complete
+                // the pause now.
+                completePause(false, resuming);
+                return false;
+
+            } else {
+                prev.schedulePauseTimeout();
+                return true;
+            }
+
+        } else {
+            // This activity either failed to schedule the pause or it entered PIP mode,
+            // so just treat it as being paused now.
+            ProtoLog.v(WM_DEBUG_STATES, "Activity not running or entered PiP, resuming next.");
+            if (resuming == null) {
+                mRootWindowContainer.resumeFocusedTasksTopActivities();
+            }
+            return false;
+        }
+    }
+
+    @VisibleForTesting
+    void completePause(boolean resumeNext, ActivityRecord resuming) {
+        // Complete the pausing process of a pausing activity, so it doesn't make sense to
+        // operate on non-leaf tasks.
+        // warnForNonLeafTask("completePauseLocked");
+
+        ActivityRecord prev = mPausingActivity;
+        ProtoLog.v(WM_DEBUG_STATES, "Complete pause: %s", prev);
+
+        if (prev != null) {
+            prev.setWillCloseOrEnterPip(false);
+            final boolean wasStopping = prev.isState(STOPPING);
+            prev.setState(PAUSED, "completePausedLocked");
+            if (prev.finishing) {
+                // We will update the activity visibility later, no need to do in
+                // completeFinishing(). Updating visibility here might also making the next
+                // activities to be resumed, and could result in wrong app transition due to
+                // lack of previous activity information.
+                ProtoLog.v(WM_DEBUG_STATES, "Executing finish of activity: %s", prev);
+                prev = prev.completeFinishing(false /* updateVisibility */,
+                        "completePausedLocked");
+            } else if (prev.hasProcess()) {
+                ProtoLog.v(WM_DEBUG_STATES, "Enqueue pending stop if needed: %s "
+                                + "wasStopping=%b visibleRequested=%b",  prev,  wasStopping,
+                        prev.mVisibleRequested);
+                if (prev.deferRelaunchUntilPaused) {
+                    // Complete the deferred relaunch that was waiting for pause to complete.
+                    ProtoLog.v(WM_DEBUG_STATES, "Re-launching after pause: %s", prev);
+                    prev.relaunchActivityLocked(prev.preserveWindowOnDeferredRelaunch);
+                } else if (wasStopping) {
+                    // We are also stopping, the stop request must have gone soon after the pause.
+                    // We can't clobber it, because the stop confirmation will not be handled.
+                    // We don't need to schedule another stop, we only need to let it happen.
+                    prev.setState(STOPPING, "completePausedLocked");
+                } else if (!prev.mVisibleRequested || shouldSleepOrShutDownActivities()) {
+                    // Clear out any deferred client hide we might currently have.
+                    prev.setDeferHidingClient(false);
+                    // If we were visible then resumeTopActivities will release resources before
+                    // stopping.
+                    prev.addToStopping(true /* scheduleIdle */, false /* idleDelayed */,
+                            "completePauseLocked");
+                }
+            } else {
+                ProtoLog.v(WM_DEBUG_STATES, "App died during pause, not stopping: %s", prev);
+                prev = null;
+            }
+            // It is possible the activity was freezing the screen before it was paused.
+            // In that case go ahead and remove the freeze this activity has on the screen
+            // since it is no longer visible.
+            if (prev != null) {
+                prev.stopFreezingScreenLocked(true /*force*/);
+            }
+            mPausingActivity = null;
+        }
+
+        if (resumeNext) {
+            final Task topRootTask = mRootWindowContainer.getTopDisplayFocusedRootTask();
+            if (topRootTask != null && !topRootTask.shouldSleepOrShutDownActivities()) {
+                mRootWindowContainer.resumeFocusedTasksTopActivities(topRootTask, prev,
+                        null /* targetOptions */);
+            } else {
+                // checkReadyForSleep();
+                final ActivityRecord top =
+                        topRootTask != null ? topRootTask.topRunningActivity() : null;
+                if (top == null || (prev != null && top != prev)) {
+                    // If there are no more activities available to run, do resume anyway to start
+                    // something. Also if the top activity on the root task is not the just paused
+                    // activity, we need to go ahead and resume it to ensure we complete an
+                    // in-flight app switch.
+                    mRootWindowContainer.resumeFocusedTasksTopActivities();
+                }
+            }
+        }
+
+        if (prev != null) {
+            prev.resumeKeyDispatchingLocked();
+        }
+
+        mRootWindowContainer.ensureActivitiesVisible(resuming, 0, !PRESERVE_WINDOWS);
+
+        // Notify when the task stack has changed, but only if visibilities changed (not just
+        // focus). Also if there is an active root pinned task - we always want to notify it about
+        // task stack changes, because its positioning may depend on it.
+        if (mTaskSupervisor.mAppVisibilitiesChangedSinceLastPause
+                || (getDisplayArea() != null && getDisplayArea().hasPinnedTask())) {
+            mAtmService.getTaskChangeNotificationController().notifyTaskStackChanged();
+            mTaskSupervisor.mAppVisibilitiesChangedSinceLastPause = false;
+        }
+    }
+
+    @Override
+    void forAllTaskFragments(Consumer<TaskFragment> callback, boolean traverseTopToBottom) {
+        super.forAllTaskFragments(callback, traverseTopToBottom);
+        callback.accept(this);
+    }
+
+    @Override
+    void forAllLeafTaskFragments(Consumer<TaskFragment> callback, boolean traverseTopToBottom) {
+        final int count = mChildren.size();
+        boolean isLeafTaskFrag = true;
+        if (traverseTopToBottom) {
+            for (int i = count - 1; i >= 0; --i) {
+                final TaskFragment child = mChildren.get(i).asTaskFragment();
+                if (child != null) {
+                    isLeafTaskFrag = false;
+                    child.forAllLeafTaskFragments(callback, traverseTopToBottom);
+                }
+            }
+        } else {
+            for (int i = 0; i < count; i++) {
+                final TaskFragment child = mChildren.get(i).asTaskFragment();
+                if (child != null) {
+                    isLeafTaskFrag = false;
+                    child.forAllLeafTaskFragments(callback, traverseTopToBottom);
+                }
+            }
+        }
+        if (isLeafTaskFrag) callback.accept(this);
+    }
+
+    @Override
+    boolean forAllLeafTaskFragments(Function<TaskFragment, Boolean> callback) {
+        boolean isLeafTaskFrag = true;
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final TaskFragment child = mChildren.get(i).asTaskFragment();
+            if (child != null) {
+                isLeafTaskFrag = false;
+                if (child.forAllLeafTaskFragments(callback)) {
+                    return true;
+                }
+            }
+        }
+        if (isLeafTaskFrag) {
+            return callback.apply(this);
+        }
+        return false;
+    }
+
+    void addChild(ActivityRecord r) {
+        addChild(r, POSITION_TOP);
+    }
+
+    @Override
+    void addChild(WindowContainer child, int index) {
+        boolean isAddingActivity = child.asActivityRecord() != null;
+        final Task task = isAddingActivity ? getTask() : null;
+
+        // If this task had any child before we added this one.
+        boolean taskHadChild = task != null && task.hasChild();
+        // getActivityType() looks at the top child, so we need to read the type before adding
+        // a new child in case the new child is on top and UNDEFINED.
+        final int activityType = task != null ? task.getActivityType() : ACTIVITY_TYPE_UNDEFINED;
+
+        super.addChild(child, index);
+
+        if (isAddingActivity && task != null) {
+            child.asActivityRecord().inHistory = true;
+            task.onDescendantActivityAdded(taskHadChild, activityType, child.asActivityRecord());
+        }
+    }
+
+    void executeAppTransition(ActivityOptions options) {
+        // No app transition applied to the task fragment.
+    }
+
+    boolean shouldSleepActivities() {
+        return false;
+    }
+
+    @Override
+    void resolveOverrideConfiguration(Configuration newParentConfig) {
+        mTmpBounds.set(getResolvedOverrideConfiguration().windowConfiguration.getBounds());
+        super.resolveOverrideConfiguration(newParentConfig);
+
+        int windowingMode =
+                getResolvedOverrideConfiguration().windowConfiguration.getWindowingMode();
+        final int parentWindowingMode = newParentConfig.windowConfiguration.getWindowingMode();
+
+        // Resolve override windowing mode to fullscreen for home task (even on freeform
+        // display), or split-screen if in split-screen mode.
+        if (getActivityType() == ACTIVITY_TYPE_HOME && windowingMode == WINDOWING_MODE_UNDEFINED) {
+            windowingMode = WindowConfiguration.isSplitScreenWindowingMode(parentWindowingMode)
+                    ? parentWindowingMode : WINDOWING_MODE_FULLSCREEN;
+            getResolvedOverrideConfiguration().windowConfiguration.setWindowingMode(windowingMode);
+        }
+
+        // Do not allow tasks not support multi window to be in a multi-window mode, unless it is in
+        // pinned windowing mode.
+        if (!supportsMultiWindow()) {
+            final int candidateWindowingMode =
+                    windowingMode != WINDOWING_MODE_UNDEFINED ? windowingMode : parentWindowingMode;
+            if (WindowConfiguration.inMultiWindowMode(candidateWindowingMode)
+                    && candidateWindowingMode != WINDOWING_MODE_PINNED) {
+                getResolvedOverrideConfiguration().windowConfiguration.setWindowingMode(
+                        WINDOWING_MODE_FULLSCREEN);
+            }
+        }
+
+        if (isLeafTaskFragment()) {
+            resolveLeafOnlyOverrideConfigs(newParentConfig, mTmpBounds /* previousBounds */);
+        }
+        computeConfigResourceOverrides(getResolvedOverrideConfiguration(), newParentConfig);
+    }
+
+    boolean supportsMultiWindow() {
+        return supportsMultiWindowInDisplayArea(getDisplayArea());
+    }
+
+    /**
+     * @return whether this task supports multi-window if it is in the given
+     *         {@link TaskDisplayArea}.
+     */
+    boolean supportsMultiWindowInDisplayArea(@Nullable TaskDisplayArea tda) {
+        if (!mAtmService.mSupportsMultiWindow) {
+            return false;
+        }
+        final Task task = getTask();
+        if (task == null) {
+            return false;
+        }
+        if (tda == null) {
+            Slog.w(TAG, "Can't find TaskDisplayArea to determine support for multi"
+                    + " window. Task id=" + getTaskId() + " attached=" + isAttached());
+            return false;
+        }
+        if (!getTask().isResizeable() && !tda.supportsNonResizableMultiWindow()) {
+            // Not support non-resizable in multi window.
+            return false;
+        }
+
+        return tda.supportsActivityMinWidthHeightMultiWindow(mMinWidth, mMinHeight);
+    }
+
+    private int getTaskId() {
+        return getTask() != null ? getTask().mTaskId : INVALID_TASK_ID;
+    }
+
+    private void resolveLeafOnlyOverrideConfigs(Configuration newParentConfig,
+            Rect previousBounds) {
+
+        int windowingMode =
+                getResolvedOverrideConfiguration().windowConfiguration.getWindowingMode();
+        if (windowingMode == WINDOWING_MODE_UNDEFINED) {
+            windowingMode = newParentConfig.windowConfiguration.getWindowingMode();
+        }
+        // Commit the resolved windowing mode so the canSpecifyOrientation won't get the old
+        // mode that may cause the bounds to be miscalculated, e.g. letterboxed.
+        getConfiguration().windowConfiguration.setWindowingMode(windowingMode);
+        Rect outOverrideBounds = getResolvedOverrideConfiguration().windowConfiguration.getBounds();
+
+        if (windowingMode == WINDOWING_MODE_FULLSCREEN) {
+            // Use empty bounds to indicate "fill parent".
+            outOverrideBounds.setEmpty();
+            // The bounds for fullscreen mode shouldn't be adjusted by minimal size. Otherwise if
+            // the parent or display is smaller than the size, the content may be cropped.
+            return;
+        }
+
+        adjustForMinimalTaskDimensions(outOverrideBounds, previousBounds, newParentConfig);
+        if (windowingMode == WINDOWING_MODE_FREEFORM) {
+            computeFreeformBounds(outOverrideBounds, newParentConfig);
+            return;
+        }
+    }
+
+    /** Computes bounds for {@link WindowConfiguration#WINDOWING_MODE_FREEFORM}. */
+    private void computeFreeformBounds(@NonNull Rect outBounds,
+            @NonNull Configuration newParentConfig) {
+        // by policy, make sure the window remains within parent somewhere
+        final float density =
+                ((float) newParentConfig.densityDpi) / DisplayMetrics.DENSITY_DEFAULT;
+        final Rect parentBounds =
+                new Rect(newParentConfig.windowConfiguration.getBounds());
+        final DisplayContent display = getDisplayContent();
+        if (display != null) {
+            // If a freeform window moves below system bar, there is no way to move it again
+            // by touch. Because its caption is covered by system bar. So we exclude them
+            // from root task bounds. and then caption will be shown inside stable area.
+            final Rect stableBounds = new Rect();
+            display.getStableRect(stableBounds);
+            parentBounds.intersect(stableBounds);
+        }
+
+        fitWithinBounds(outBounds, parentBounds,
+                (int) (density * WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP),
+                (int) (density * WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP));
+
+        // Prevent to overlap caption with stable insets.
+        final int offsetTop = parentBounds.top - outBounds.top;
+        if (offsetTop > 0) {
+            outBounds.offset(0, offsetTop);
+        }
+    }
+
+    /**
+     * Adjusts bounds to stay within root task bounds.
+     *
+     * Since bounds might be outside of root task bounds, this method tries to move the bounds in
+     * a way that keep them unchanged, but be contained within the root task bounds.
+     *
+     * @param bounds Bounds to be adjusted.
+     * @param rootTaskBounds Bounds within which the other bounds should remain.
+     * @param overlapPxX The amount of px required to be visible in the X dimension.
+     * @param overlapPxY The amount of px required to be visible in the Y dimension.
+     */
+    private static void fitWithinBounds(Rect bounds, Rect rootTaskBounds, int overlapPxX,
+            int overlapPxY) {
+        if (rootTaskBounds == null || rootTaskBounds.isEmpty() || rootTaskBounds.contains(bounds)) {
+            return;
+        }
+
+        // For each side of the parent (eg. left), check if the opposing side of the window (eg.
+        // right) is at least overlap pixels away. If less, offset the window by that difference.
+        int horizontalDiff = 0;
+        // If window is smaller than overlap, use it's smallest dimension instead
+        int overlapLR = Math.min(overlapPxX, bounds.width());
+        if (bounds.right < (rootTaskBounds.left + overlapLR)) {
+            horizontalDiff = overlapLR - (bounds.right - rootTaskBounds.left);
+        } else if (bounds.left > (rootTaskBounds.right - overlapLR)) {
+            horizontalDiff = -(overlapLR - (rootTaskBounds.right - bounds.left));
+        }
+        int verticalDiff = 0;
+        int overlapTB = Math.min(overlapPxY, bounds.width());
+        if (bounds.bottom < (rootTaskBounds.top + overlapTB)) {
+            verticalDiff = overlapTB - (bounds.bottom - rootTaskBounds.top);
+        } else if (bounds.top > (rootTaskBounds.bottom - overlapTB)) {
+            verticalDiff = -(overlapTB - (rootTaskBounds.bottom - bounds.top));
+        }
+        bounds.offset(horizontalDiff, verticalDiff);
+    }
+
+    /**
+     * Ensures all visible activities at or below the input activity have the right configuration.
+     */
+    void ensureVisibleActivitiesConfiguration(ActivityRecord start, boolean preserveWindow) {
+        mEnsureVisibleActivitiesConfigHelper.process(start, preserveWindow);
+    }
+
+    void adjustForMinimalTaskDimensions(@NonNull Rect bounds, @NonNull Rect previousBounds,
+            @NonNull Configuration parentConfig) {
+        int minWidth = mMinWidth;
+        int minHeight = mMinHeight;
+        // If the task has no requested minimal size, we'd like to enforce a minimal size
+        // so that the user can not render the task fragment too small to manipulate. We don't need
+        // to do this for the root pinned task as the bounds are controlled by the system.
+        if (!inPinnedWindowingMode()) {
+            final int defaultMinSizeDp = mRootWindowContainer.mDefaultMinSizeOfResizeableTaskDp;
+            final float density = (float) parentConfig.densityDpi / DisplayMetrics.DENSITY_DEFAULT;
+            final int defaultMinSize = (int) (defaultMinSizeDp * density);
+
+            if (minWidth == INVALID_MIN_SIZE) {
+                minWidth = defaultMinSize;
+            }
+            if (minHeight == INVALID_MIN_SIZE) {
+                minHeight = defaultMinSize;
+            }
+        }
+        if (bounds.isEmpty()) {
+            // If inheriting parent bounds, check if parent bounds adhere to minimum size. If they
+            // do, we can just skip.
+            final Rect parentBounds = parentConfig.windowConfiguration.getBounds();
+            if (parentBounds.width() >= minWidth && parentBounds.height() >= minHeight) {
+                return;
+            }
+            bounds.set(parentBounds);
+        }
+        final boolean adjustWidth = minWidth > bounds.width();
+        final boolean adjustHeight = minHeight > bounds.height();
+        if (!(adjustWidth || adjustHeight)) {
+            return;
+        }
+
+        if (adjustWidth) {
+            if (!previousBounds.isEmpty() && bounds.right == previousBounds.right) {
+                bounds.left = bounds.right - minWidth;
+            } else {
+                // Either left bounds match, or neither match, or the previous bounds were
+                // fullscreen and we default to keeping left.
+                bounds.right = bounds.left + minWidth;
+            }
+        }
+        if (adjustHeight) {
+            if (!previousBounds.isEmpty() && bounds.bottom == previousBounds.bottom) {
+                bounds.top = bounds.bottom - minHeight;
+            } else {
+                // Either top bounds match, or neither match, or the previous bounds were
+                // fullscreen and we default to keeping top.
+                bounds.bottom = bounds.top + minHeight;
+            }
+        }
+    }
+
+    void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
+            @NonNull Configuration parentConfig) {
+        computeConfigResourceOverrides(inOutConfig, parentConfig, null /* overrideDisplayInfo */,
+                null /* compatInsets */);
+    }
+
+    void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
+            @NonNull Configuration parentConfig, @Nullable DisplayInfo overrideDisplayInfo) {
+        if (overrideDisplayInfo != null) {
+            // Make sure the screen related configs can be computed by the provided display info.
+            inOutConfig.screenLayout = Configuration.SCREENLAYOUT_UNDEFINED;
+            invalidateAppBoundsConfig(inOutConfig);
+        }
+        computeConfigResourceOverrides(inOutConfig, parentConfig, overrideDisplayInfo,
+                null /* compatInsets */);
+    }
+
+    void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
+            @NonNull Configuration parentConfig,
+            @Nullable ActivityRecord.CompatDisplayInsets compatInsets) {
+        if (compatInsets != null) {
+            // Make sure the app bounds can be computed by the compat insets.
+            invalidateAppBoundsConfig(inOutConfig);
+        }
+        computeConfigResourceOverrides(inOutConfig, parentConfig, null /* overrideDisplayInfo */,
+                compatInsets);
+    }
+
+    /**
+     * Forces the app bounds related configuration can be computed by
+     * {@link #computeConfigResourceOverrides(Configuration, Configuration, DisplayInfo,
+     * ActivityRecord.CompatDisplayInsets)}.
+     */
+    private static void invalidateAppBoundsConfig(@NonNull Configuration inOutConfig) {
+        final Rect appBounds = inOutConfig.windowConfiguration.getAppBounds();
+        if (appBounds != null) {
+            appBounds.setEmpty();
+        }
+        inOutConfig.screenWidthDp = Configuration.SCREEN_WIDTH_DP_UNDEFINED;
+        inOutConfig.screenHeightDp = Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
+    }
+
+    /**
+     * Calculates configuration values used by the client to get resources. This should be run
+     * using app-facing bounds (bounds unmodified by animations or transient interactions).
+     *
+     * This assumes bounds are non-empty/null. For the null-bounds case, the caller is likely
+     * configuring an "inherit-bounds" window which means that all configuration settings would
+     * just be inherited from the parent configuration.
+     **/
+    void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
+            @NonNull Configuration parentConfig, @Nullable DisplayInfo overrideDisplayInfo,
+            @Nullable ActivityRecord.CompatDisplayInsets compatInsets) {
+        int windowingMode = inOutConfig.windowConfiguration.getWindowingMode();
+        if (windowingMode == WINDOWING_MODE_UNDEFINED) {
+            windowingMode = parentConfig.windowConfiguration.getWindowingMode();
+        }
+
+        float density = inOutConfig.densityDpi;
+        if (density == Configuration.DENSITY_DPI_UNDEFINED) {
+            density = parentConfig.densityDpi;
+        }
+        density *= DisplayMetrics.DENSITY_DEFAULT_SCALE;
+
+        // The bounds may have been overridden at this level. If the parent cannot cover these
+        // bounds, the configuration is still computed according to the override bounds.
+        final boolean insideParentBounds;
+
+        final Rect parentBounds = parentConfig.windowConfiguration.getBounds();
+        final Rect resolvedBounds = inOutConfig.windowConfiguration.getBounds();
+        if (resolvedBounds == null || resolvedBounds.isEmpty()) {
+            mTmpFullBounds.set(parentBounds);
+            insideParentBounds = true;
+        } else {
+            mTmpFullBounds.set(resolvedBounds);
+            insideParentBounds = parentBounds.contains(resolvedBounds);
+        }
+
+        // Non-null compatibility insets means the activity prefers to keep its original size, so
+        // out bounds doesn't need to be restricted by the parent or current display
+        final boolean customContainerPolicy = compatInsets != null;
+
+        Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
+        if (outAppBounds == null || outAppBounds.isEmpty()) {
+            // App-bounds hasn't been overridden, so calculate a value for it.
+            inOutConfig.windowConfiguration.setAppBounds(mTmpFullBounds);
+            outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
+
+            if (!customContainerPolicy && windowingMode != WINDOWING_MODE_FREEFORM) {
+                final Rect containingAppBounds;
+                if (insideParentBounds) {
+                    containingAppBounds = parentConfig.windowConfiguration.getAppBounds();
+                } else {
+                    // Restrict appBounds to display non-decor rather than parent because the
+                    // override bounds are beyond the parent. Otherwise, it won't match the
+                    // overridden bounds.
+                    final TaskDisplayArea displayArea = getDisplayArea();
+                    containingAppBounds = displayArea != null
+                            ? displayArea.getWindowConfiguration().getAppBounds() : null;
+                }
+                if (containingAppBounds != null && !containingAppBounds.isEmpty()) {
+                    outAppBounds.intersect(containingAppBounds);
+                }
+            }
+        }
+
+        if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED
+                || inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
+            if (!customContainerPolicy && WindowConfiguration.isFloating(windowingMode)) {
+                mTmpNonDecorBounds.set(mTmpFullBounds);
+                mTmpStableBounds.set(mTmpFullBounds);
+            } else if (!customContainerPolicy
+                    && (overrideDisplayInfo != null || getDisplayContent() != null)) {
+                final DisplayInfo di = overrideDisplayInfo != null
+                        ? overrideDisplayInfo
+                        : getDisplayContent().getDisplayInfo();
+
+                // For calculating screenWidthDp, screenWidthDp, we use the stable inset screen
+                // area, i.e. the screen area without the system bars.
+                // The non decor inset are areas that could never be removed in Honeycomb. See
+                // {@link WindowManagerPolicy#getNonDecorInsetsLw}.
+                calculateInsetFrames(mTmpNonDecorBounds, mTmpStableBounds, mTmpFullBounds, di);
+            } else {
+                // Apply the given non-decor and stable insets to calculate the corresponding bounds
+                // for screen size of configuration.
+                int rotation = inOutConfig.windowConfiguration.getRotation();
+                if (rotation == ROTATION_UNDEFINED) {
+                    rotation = parentConfig.windowConfiguration.getRotation();
+                }
+                if (rotation != ROTATION_UNDEFINED && customContainerPolicy) {
+                    mTmpNonDecorBounds.set(mTmpFullBounds);
+                    mTmpStableBounds.set(mTmpFullBounds);
+                    compatInsets.getBoundsByRotation(mTmpBounds, rotation);
+                    intersectWithInsetsIfFits(mTmpNonDecorBounds, mTmpBounds,
+                            compatInsets.mNonDecorInsets[rotation]);
+                    intersectWithInsetsIfFits(mTmpStableBounds, mTmpBounds,
+                            compatInsets.mStableInsets[rotation]);
+                    outAppBounds.set(mTmpNonDecorBounds);
+                } else {
+                    // Set to app bounds because it excludes decor insets.
+                    mTmpNonDecorBounds.set(outAppBounds);
+                    mTmpStableBounds.set(outAppBounds);
+                }
+            }
+
+            if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
+                final int overrideScreenWidthDp = (int) (mTmpStableBounds.width() / density);
+                inOutConfig.screenWidthDp = (insideParentBounds && !customContainerPolicy)
+                        ? Math.min(overrideScreenWidthDp, parentConfig.screenWidthDp)
+                        : overrideScreenWidthDp;
+            }
+            if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
+                final int overrideScreenHeightDp = (int) (mTmpStableBounds.height() / density);
+                inOutConfig.screenHeightDp = (insideParentBounds && !customContainerPolicy)
+                        ? Math.min(overrideScreenHeightDp, parentConfig.screenHeightDp)
+                        : overrideScreenHeightDp;
+            }
+
+            if (inOutConfig.smallestScreenWidthDp
+                    == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) {
+                if (WindowConfiguration.isFloating(windowingMode)) {
+                    // For floating tasks, calculate the smallest width from the bounds of the task
+                    inOutConfig.smallestScreenWidthDp = (int) (
+                            Math.min(mTmpFullBounds.width(), mTmpFullBounds.height()) / density);
+                }
+                // otherwise, it will just inherit
+            }
+        }
+
+        if (inOutConfig.orientation == ORIENTATION_UNDEFINED) {
+            inOutConfig.orientation = (inOutConfig.screenWidthDp <= inOutConfig.screenHeightDp)
+                    ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
+        }
+        if (inOutConfig.screenLayout == Configuration.SCREENLAYOUT_UNDEFINED) {
+            // For calculating screen layout, we need to use the non-decor inset screen area for the
+            // calculation for compatibility reasons, i.e. screen area without system bars that
+            // could never go away in Honeycomb.
+            int compatScreenWidthDp = (int) (mTmpNonDecorBounds.width() / density);
+            int compatScreenHeightDp = (int) (mTmpNonDecorBounds.height() / density);
+            // Use overrides if provided. If both overrides are provided, mTmpNonDecorBounds is
+            // undefined so it can't be used.
+            if (inOutConfig.screenWidthDp != Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
+                compatScreenWidthDp = inOutConfig.screenWidthDp;
+            }
+            if (inOutConfig.screenHeightDp != Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
+                compatScreenHeightDp = inOutConfig.screenHeightDp;
+            }
+            // Reducing the screen layout starting from its parent config.
+            inOutConfig.screenLayout = computeScreenLayoutOverride(parentConfig.screenLayout,
+                    compatScreenWidthDp, compatScreenHeightDp);
+        }
+    }
+
+    /**
+     * Gets bounds with non-decor and stable insets applied respectively.
+     *
+     * If bounds overhangs the display, those edges will not get insets. See
+     * {@link #intersectWithInsetsIfFits}
+     *
+     * @param outNonDecorBounds where to place bounds with non-decor insets applied.
+     * @param outStableBounds where to place bounds with stable insets applied.
+     * @param bounds the bounds to inset.
+     */
+    void calculateInsetFrames(Rect outNonDecorBounds, Rect outStableBounds, Rect bounds,
+            DisplayInfo displayInfo) {
+        outNonDecorBounds.set(bounds);
+        outStableBounds.set(bounds);
+        final Task rootTask = getRootTaskFragment().asTask();
+        if (rootTask == null || rootTask.mDisplayContent == null) {
+            return;
+        }
+        mTmpBounds.set(0, 0, displayInfo.logicalWidth, displayInfo.logicalHeight);
+
+        final DisplayPolicy policy = rootTask.mDisplayContent.getDisplayPolicy();
+        policy.getNonDecorInsetsLw(displayInfo.rotation, displayInfo.logicalWidth,
+                displayInfo.logicalHeight, displayInfo.displayCutout, mTmpInsets);
+        intersectWithInsetsIfFits(outNonDecorBounds, mTmpBounds, mTmpInsets);
+
+        policy.convertNonDecorInsetsToStableInsets(mTmpInsets, displayInfo.rotation);
+        intersectWithInsetsIfFits(outStableBounds, mTmpBounds, mTmpInsets);
+    }
+
+    /**
+     * Intersects inOutBounds with intersectBounds-intersectInsets. If inOutBounds is larger than
+     * intersectBounds on a side, then the respective side will not be intersected.
+     *
+     * The assumption is that if inOutBounds is initially larger than intersectBounds, then the
+     * inset on that side is no-longer applicable. This scenario happens when a task's minimal
+     * bounds are larger than the provided parent/display bounds.
+     *
+     * @param inOutBounds the bounds to intersect.
+     * @param intersectBounds the bounds to intersect with.
+     * @param intersectInsets insets to apply to intersectBounds before intersecting.
+     */
+    static void intersectWithInsetsIfFits(
+            Rect inOutBounds, Rect intersectBounds, Rect intersectInsets) {
+        if (inOutBounds.right <= intersectBounds.right) {
+            inOutBounds.right =
+                    Math.min(intersectBounds.right - intersectInsets.right, inOutBounds.right);
+        }
+        if (inOutBounds.bottom <= intersectBounds.bottom) {
+            inOutBounds.bottom =
+                    Math.min(intersectBounds.bottom - intersectInsets.bottom, inOutBounds.bottom);
+        }
+        if (inOutBounds.left >= intersectBounds.left) {
+            inOutBounds.left =
+                    Math.max(intersectBounds.left + intersectInsets.left, inOutBounds.left);
+        }
+        if (inOutBounds.top >= intersectBounds.top) {
+            inOutBounds.top =
+                    Math.max(intersectBounds.top + intersectInsets.top, inOutBounds.top);
+        }
+    }
+
+    /** Computes LONG, SIZE and COMPAT parts of {@link Configuration#screenLayout}. */
+    static int computeScreenLayoutOverride(int sourceScreenLayout, int screenWidthDp,
+            int screenHeightDp) {
+        sourceScreenLayout = sourceScreenLayout
+                & (Configuration.SCREENLAYOUT_LONG_MASK | Configuration.SCREENLAYOUT_SIZE_MASK);
+        final int longSize = Math.max(screenWidthDp, screenHeightDp);
+        final int shortSize = Math.min(screenWidthDp, screenHeightDp);
+        return Configuration.reduceScreenLayout(sourceScreenLayout, longSize, shortSize);
+    }
+
+    @Override
+    public int getActivityType() {
+        final int applicationType = super.getActivityType();
+        if (applicationType != ACTIVITY_TYPE_UNDEFINED || !hasChild()) {
+            return applicationType;
+        }
+        return getTopChild().getActivityType();
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newParentConfig) {
+        super.onConfigurationChanged(newParentConfig);
+
+        if (mTaskFragmentOrganizer != null) {
+            // Parent config may have changed. The controller will check if there is any important
+            // config change for the organizer.
+            mTaskFragmentOrganizerController
+                    .onTaskFragmentParentInfoChanged(mTaskFragmentOrganizer, this);
+            mTaskFragmentOrganizerController
+                    .onTaskFragmentInfoChanged(mTaskFragmentOrganizer, this);
+        }
+    }
+
+    // TODO(b/190433129) call when TaskFragment is created from WCT#createTaskFragment
+    private void sendTaskFragmentAppeared() {
+        if (mTaskFragmentOrganizer != null) {
+            mTaskFragmentOrganizerController.onTaskFragmentAppeared(mTaskFragmentOrganizer, this);
+        }
+    }
+
+    private void sendTaskFragmentVanished() {
+        if (mTaskFragmentOrganizer != null) {
+            mTaskFragmentOrganizerController.onTaskFragmentVanished(mTaskFragmentOrganizer, this);
+        }
+    }
+
+    /**
+     * Returns a {@link TaskFragmentInfo} with information from this TaskFragment. Should not be
+     * called from {@link Task}.
+     */
+    TaskFragmentInfo getTaskFragmentInfo() {
+        return new TaskFragmentInfo(
+                mFragmentToken,
+                mRemoteToken.toWindowContainerToken(),
+                getConfiguration(),
+                getChildCount() == 0,
+                isVisible());
+    }
+
+    @Nullable
+    IBinder getFragmentToken() {
+        return mFragmentToken;
+    }
+
+    /** Clear {@link #mLastPausedActivity} for all {@link TaskFragment} children */
+    void clearLastPausedActivity() {
+        forAllTaskFragments(taskFragment -> taskFragment.mLastPausedActivity = null);
+    }
+
+    /**
+     * Sets {@link #mMinWidth} and {@link #mMinWidth} to this TaskFragment.
+     * It is usually set from the parent {@link Task} when adding the TaskFragment to the window
+     * hierarchy.
+     */
+    void setMinDimensions(int minWidth, int minHeight) {
+        if (asTask() != null) {
+            throw new UnsupportedOperationException("This method must not be used to Task. The "
+                    + " minimum dimension of Task should be passed from Task constructor.");
+        }
+        mMinWidth = minWidth;
+        mMinHeight = minHeight;
+    }
+
+    @Override
+    void removeImmediately() {
+        if (mRemoving) {
+            return;
+        }
+        mRemoving = true;
+        super.removeImmediately();
+        sendTaskFragmentVanished();
+        mRemoving = false;
+    }
+
+    boolean dump(String prefix, FileDescriptor fd, PrintWriter pw, boolean dumpAll,
+            boolean dumpClient, String dumpPackage, final boolean needSep, Runnable header) {
+        boolean printed = false;
+        Runnable headerPrinter = () -> {
+            if (needSep) {
+                pw.println();
+            }
+            if (header != null) {
+                header.run();
+            }
+
+            dumpInner(prefix, pw, dumpAll, dumpPackage);
+        };
+
+        if (dumpPackage == null) {
+            // If we are not filtering by package, we want to print absolutely everything,
+            // so always print the header even if there are no tasks/activities inside.
+            headerPrinter.run();
+            headerPrinter = null;
+            printed = true;
+        }
+
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            WindowContainer child = mChildren.get(i);
+            if (child.asTaskFragment() != null) {
+                printed |= child.asTaskFragment().dump(prefix + "      ", fd, pw, dumpAll,
+                        dumpClient, dumpPackage, needSep, headerPrinter);
+            } else if (child.asActivityRecord() != null) {
+                ActivityRecord.dumpActivity(fd, pw, i, child.asActivityRecord(), prefix + "      ",
+                        "Hist ", true, !dumpAll, dumpClient, dumpPackage, false, headerPrinter,
+                        getTask());
+            }
+        }
+
+        return printed;
+    }
+
+    void dumpInner(String prefix, PrintWriter pw, boolean dumpAll, String dumpPackage) {
+        pw.print(prefix); pw.print("* "); pw.println(this);
+        pw.println(prefix + "  mBounds=" + getRequestedOverrideBounds());
+        if (dumpAll) {
+            printThisActivity(pw, mLastPausedActivity, dumpPackage, false,
+                    prefix + "  mLastPausedActivity: ", null);
+        }
+    }
+
+    @Override
+    void writeIdentifierToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        proto.write(HASH_CODE, System.identityHashCode(this));
+        final ActivityRecord topActivity = topRunningActivity();
+        proto.write(USER_ID, topActivity != null ? topActivity.mUserId : USER_NULL);
+        proto.write(TITLE, topActivity != null ? topActivity.intent.getComponent()
+                .flattenToShortString() : "TaskFragment");
+        proto.end(token);
+    }
+
+    @Override
+    long getProtoFieldId() {
+        return TASK_FRAGMENT;
+    }
+
+    @Override
+    public void dumpDebug(ProtoOutputStream proto, long fieldId,
+            @WindowTraceLogLevel int logLevel) {
+        if (logLevel == WindowTraceLogLevel.CRITICAL && !isVisible()) {
+            return;
+        }
+
+        final long token = proto.start(fieldId);
+
+        super.dumpDebug(proto, WINDOW_CONTAINER, logLevel);
+
+        proto.write(DISPLAY_ID, getDisplayId());
+        proto.write(ACTIVITY_TYPE, getActivityType());
+        proto.write(MIN_WIDTH, mMinWidth);
+        proto.write(MIN_HEIGHT, mMinHeight);
+
+        proto.end(token);
+    }
+}
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
new file mode 100644
index 0000000..1423272
--- /dev/null
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
+import static com.android.server.wm.WindowOrganizerController.configurationsAreEqualForOrganizer;
+
+import android.content.res.Configuration;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.view.SurfaceControl;
+import android.window.ITaskFragmentOrganizer;
+import android.window.ITaskFragmentOrganizerController;
+import android.window.TaskFragmentAppearedInfo;
+import android.window.TaskFragmentInfo;
+
+import com.android.internal.protolog.common.ProtoLog;
+
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+/**
+ * Stores and manages the client {@link android.window.TaskFragmentOrganizer}.
+ */
+public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerController.Stub {
+    private static final String TAG = "TaskFragmentOrganizerController";
+
+    private final ActivityTaskManagerService mAtmService;
+    private final WindowManagerGlobalLock mGlobalLock;
+    private final Map<TaskFragment, TaskFragmentInfo> mLastSentTaskFragmentInfos =
+            new WeakHashMap<>();
+    private final Map<TaskFragment, Configuration> mLastSentTaskFragmentParentConfigs =
+            new WeakHashMap<>();
+    /**
+     * A Map which manages the relationship between
+     * {@link ITaskFragmentOrganizer} and {@link TaskFragmentOrganizerState}
+     */
+    private final ArrayMap<IBinder, TaskFragmentController> mTaskFragmentOrganizerControllers =
+            new ArrayMap<>();
+
+    TaskFragmentOrganizerController(ActivityTaskManagerService atm) {
+        mAtmService = atm;
+        mGlobalLock = atm.mGlobalLock;
+    }
+
+    /**
+     * A class to manage {@link ITaskFragmentOrganizer} and its organized
+     * {@link TaskFragment TaskFragments}.
+     */
+    private class TaskFragmentController implements IBinder.DeathRecipient {
+        private final ArrayList<TaskFragment> mOrganizedTaskFragments = new ArrayList<>();
+        private final ITaskFragmentOrganizer mOrganizer;
+
+        TaskFragmentController(ITaskFragmentOrganizer organizer) {
+            mOrganizer = organizer;
+            try {
+                mOrganizer.asBinder().linkToDeath(this, 0 /*flags*/);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "TaskFragmentOrganizer failed to register death recipient");
+            }
+        }
+
+        @Override
+        public void binderDied() {
+            synchronized (mGlobalLock) {
+                removeOrganizer(mOrganizer);
+            }
+        }
+
+        void addTaskFragment(TaskFragment taskFragment) {
+            if (!mOrganizedTaskFragments.contains(taskFragment)) {
+                mOrganizedTaskFragments.add(taskFragment);
+            }
+        }
+
+        void removeTaskFragment(TaskFragment taskFragment) {
+            mOrganizedTaskFragments.remove(taskFragment);
+        }
+
+        void dispose() {
+            mOrganizedTaskFragments.forEach(TaskFragment::removeImmediately);
+            mOrganizedTaskFragments.clear();
+            mOrganizer.asBinder().unlinkToDeath(this, 0 /*flags*/);
+        }
+    }
+
+    @Override
+    public void registerOrganizer(ITaskFragmentOrganizer organizer) {
+        final int pid = Binder.getCallingPid();
+        final long uid = Binder.getCallingUid();
+        synchronized (mGlobalLock) {
+            ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER,
+                    "Register task fragment organizer=%s uid=%d pid=%d",
+                    organizer.asBinder(), uid, pid);
+            if (mTaskFragmentOrganizerControllers.containsKey(organizer.asBinder())) {
+                throw new IllegalStateException(
+                        "Replacing existing organizer currently unsupported");
+            }
+            mTaskFragmentOrganizerControllers.put(organizer.asBinder(),
+                    new TaskFragmentController(organizer));
+        }
+    }
+
+    @Override
+    public void unregisterOrganizer(ITaskFragmentOrganizer organizer) {
+        validateAndGetController(organizer);
+        final int pid = Binder.getCallingPid();
+        final long uid = Binder.getCallingUid();
+        synchronized (mGlobalLock) {
+            ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER,
+                    "Unregister task fragment organizer=%s uid=%d pid=%d",
+                    organizer.asBinder(), uid, pid);
+            removeOrganizer(organizer);
+        }
+    }
+
+    void onTaskFragmentAppeared(ITaskFragmentOrganizer organizer, TaskFragment tf) {
+        final TaskFragmentController controller = validateAndGetController(organizer);
+
+        ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "TaskFragment appeared name=%s", tf.getName());
+        final TaskFragmentInfo info = tf.getTaskFragmentInfo();
+        final SurfaceControl outSurfaceControl = new SurfaceControl(tf.getSurfaceControl(),
+                "TaskFragmentOrganizerController.onTaskFragmentInfoAppeared");
+        controller.addTaskFragment(tf);
+        try {
+            organizer.onTaskFragmentAppeared(
+                    new TaskFragmentAppearedInfo(info, outSurfaceControl));
+            mLastSentTaskFragmentInfos.put(tf, info);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Exception sending onTaskFragmentAppeared callback", e);
+        }
+    }
+
+    void onTaskFragmentInfoChanged(ITaskFragmentOrganizer organizer, TaskFragment tf) {
+        validateAndGetController(organizer);
+
+        // Check if the info is different from the last reported info.
+        final TaskFragmentInfo info = tf.getTaskFragmentInfo();
+        final TaskFragmentInfo lastInfo = mLastSentTaskFragmentInfos.get(tf);
+        if (info.equalsForTaskFragmentOrganizer(lastInfo) && configurationsAreEqualForOrganizer(
+                info.getConfiguration(), lastInfo.getConfiguration())) {
+            return;
+        }
+
+        ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "TaskFragment info changed name=%s", tf.getName());
+        try {
+            organizer.onTaskFragmentInfoChanged(tf.getTaskFragmentInfo());
+            mLastSentTaskFragmentInfos.put(tf, info);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Exception sending onTaskFragmentInfoChanged callback", e);
+        }
+    }
+
+    void onTaskFragmentVanished(ITaskFragmentOrganizer organizer, TaskFragment tf) {
+        final TaskFragmentController controller = validateAndGetController(organizer);
+
+        ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "TaskFragment vanished name=%s", tf.getName());
+        try {
+            organizer.onTaskFragmentVanished(tf.getTaskFragmentInfo());
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Exception sending onTaskFragmentVanished callback", e);
+        }
+        mLastSentTaskFragmentInfos.remove(tf);
+        mLastSentTaskFragmentParentConfigs.remove(tf);
+        controller.removeTaskFragment(tf);
+    }
+
+    void onTaskFragmentParentInfoChanged(ITaskFragmentOrganizer organizer, TaskFragment tf) {
+        validateAndGetController(organizer);
+
+        // Check if the parent info is different from the last reported parent info.
+        if (tf.getParent() == null || tf.getParent().asTask() == null) {
+            mLastSentTaskFragmentParentConfigs.remove(tf);
+            return;
+        }
+        final Task parent = tf.getParent().asTask();
+        final Configuration parentConfig = parent.getConfiguration();
+        final Configuration lastParentConfig = mLastSentTaskFragmentParentConfigs.get(tf);
+        if (configurationsAreEqualForOrganizer(parentConfig, lastParentConfig)) {
+            return;
+        }
+
+        ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER,
+                "TaskFragment parent info changed name=%s parentTaskId=%d",
+                tf.getName(), parent.mTaskId);
+        try {
+            organizer.onTaskFragmentParentInfoChanged(tf.getFragmentToken(), parentConfig);
+            mLastSentTaskFragmentParentConfigs.put(tf, parentConfig);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Exception sending onTaskFragmentParentInfoChanged callback", e);
+        }
+    }
+
+    private void removeOrganizer(ITaskFragmentOrganizer organizer) {
+        final TaskFragmentController controller = validateAndGetController(organizer);
+        // remove all of the children of the organized TaskFragment
+        controller.dispose();
+        mTaskFragmentOrganizerControllers.remove(organizer.asBinder());
+    }
+
+    /**
+     * Makes sure that the organizer has been correctly registered to prevent any Sidecar
+     * implementation from organizing {@link TaskFragment} without registering first. In such case,
+     * we wouldn't register {@link DeathRecipient} for the organizer, and might not remove the
+     * {@link TaskFragment} after the organizer process died.
+     */
+    private TaskFragmentController validateAndGetController(ITaskFragmentOrganizer organizer) {
+        final TaskFragmentController controller =
+                mTaskFragmentOrganizerControllers.get(organizer.asBinder());
+        if (controller == null) {
+            throw new IllegalArgumentException(
+                    "TaskFragmentOrganizer has not been registered. Organizer=" + organizer);
+        }
+        return controller;
+    }
+}
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 2dc63ce..a507abd 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -23,15 +23,13 @@
 import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission;
 import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_STARTING_REVEAL;
-import static com.android.server.wm.WindowOrganizerController.CONTROLLABLE_CONFIGS;
-import static com.android.server.wm.WindowOrganizerController.CONTROLLABLE_WINDOW_CONFIGS;
+import static com.android.server.wm.WindowOrganizerController.configurationsAreEqualForOrganizer;
 
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.WindowConfiguration;
 import android.content.Intent;
-import android.content.pm.ActivityInfo;
 import android.content.pm.ParceledListSlice;
 import android.graphics.Rect;
 import android.os.Binder;
@@ -69,13 +67,6 @@
 class TaskOrganizerController extends ITaskOrganizerController.Stub {
     private static final String TAG = "TaskOrganizerController";
 
-    /**
-     * Masks specifying which configurations are important to report back to an organizer when
-     * changed.
-     */
-    private static final int REPORT_CONFIGS = CONTROLLABLE_CONFIGS;
-    private static final int REPORT_WINDOW_CONFIGS = CONTROLLABLE_WINDOW_CONFIGS;
-
     // The set of modes that are currently supports
     // TODO: Remove once the task organizer can support all modes
     @VisibleForTesting
@@ -389,6 +380,15 @@
                                 mOrganizer.mTaskOrganizer, t);
                     }
                 }
+                if (mService.getTransitionController().isShellTransitionsEnabled()) {
+                    // dispose is only called outside of transitions (eg during unregister). Since
+                    // we "migrate" surfaces when replacing organizers, visibility gets delegated
+                    // to transitions; however, since there is no transition at this point, we have
+                    // to manually show the surface here.
+                    if (t.mTaskOrganizer != null && t.getSurfaceControl() != null) {
+                        t.getSyncTransaction().show(t.getSurfaceControl());
+                    }
+                }
             }
 
             // Remove organizer state after removing tasks so we get a chance to send
@@ -481,7 +481,8 @@
         final int uid = Binder.getCallingUid();
         final long origId = Binder.clearCallingIdentity();
         try {
-            synchronized (mGlobalLock) {
+            final ArrayList<TaskAppearedInfo> taskInfos = new ArrayList<>();
+            final Runnable withGlobalLock = () -> {
                 ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Register task organizer=%s uid=%d",
                         organizer.asBinder(), uid);
                 if (!mTaskOrganizerStates.containsKey(organizer.asBinder())) {
@@ -490,10 +491,10 @@
                             new TaskOrganizerState(organizer, uid));
                 }
 
-                final ArrayList<TaskAppearedInfo> taskInfos = new ArrayList<>();
                 final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder());
                 mService.mRootWindowContainer.forAllTasks((task) -> {
-                    if (ArrayUtils.contains(UNSUPPORTED_WINDOWING_MODES, task.getWindowingMode())) {
+                    if (ArrayUtils.contains(UNSUPPORTED_WINDOWING_MODES,
+                            task.getWindowingMode())) {
                         return;
                     }
 
@@ -503,11 +504,19 @@
                     if (returnTask) {
                         SurfaceControl outSurfaceControl = state.addTaskWithoutCallback(task,
                                 "TaskOrganizerController.registerTaskOrganizer");
-                        taskInfos.add(new TaskAppearedInfo(task.getTaskInfo(), outSurfaceControl));
+                        taskInfos.add(
+                                new TaskAppearedInfo(task.getTaskInfo(), outSurfaceControl));
                     }
                 });
-                return new ParceledListSlice<>(taskInfos);
+            };
+            if (mService.getTransitionController().isShellTransitionsEnabled()) {
+                mService.getTransitionController().mRunningLock.runWhenIdle(1000, withGlobalLock);
+            } else {
+                synchronized (mGlobalLock) {
+                    withGlobalLock.run();
+                }
             }
+            return new ParceledListSlice<>(taskInfos);
         } finally {
             Binder.restoreCallingIdentity(origId);
         }
@@ -519,7 +528,7 @@
         final int uid = Binder.getCallingUid();
         final long origId = Binder.clearCallingIdentity();
         try {
-            synchronized (mGlobalLock) {
+            final Runnable withGlobalLock = () -> {
                 final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder());
                 if (state == null) {
                     return;
@@ -528,6 +537,13 @@
                         organizer.asBinder(), uid);
                 state.unlinkDeath();
                 state.dispose();
+            };
+            if (mService.getTransitionController().isShellTransitionsEnabled()) {
+                mService.getTransitionController().mRunningLock.runWhenIdle(1000, withGlobalLock);
+            } else {
+                synchronized (mGlobalLock) {
+                    withGlobalLock.run();
+                }
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -766,18 +782,9 @@
         mTmpTaskInfo.configuration.unset();
         task.fillTaskInfo(mTmpTaskInfo);
 
-        boolean changed = !mTmpTaskInfo.equalsForTaskOrganizer(lastInfo);
-        if (!changed) {
-            int cfgChanges = mTmpTaskInfo.configuration.diff(lastInfo.configuration);
-            final int winCfgChanges = (cfgChanges & ActivityInfo.CONFIG_WINDOW_CONFIGURATION) != 0
-                    ? (int) mTmpTaskInfo.configuration.windowConfiguration.diff(
-                            lastInfo.configuration.windowConfiguration,
-                            true /* compareUndefined */) : 0;
-            if ((winCfgChanges & REPORT_WINDOW_CONFIGS) == 0) {
-                cfgChanges &= ~ActivityInfo.CONFIG_WINDOW_CONFIGURATION;
-            }
-            changed = (cfgChanges & REPORT_CONFIGS) != 0;
-        }
+        boolean changed = !mTmpTaskInfo.equalsForTaskOrganizer(lastInfo)
+                || !configurationsAreEqualForOrganizer(
+                        mTmpTaskInfo.configuration, lastInfo.configuration);
         if (!(changed || force)) {
             // mTmpTaskInfo will be reused next time.
             return;
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 0cd09807..0a8a937 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -16,11 +16,13 @@
 
 package com.android.server.wm;
 
-
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE;
@@ -38,6 +40,9 @@
 import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
 import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
 
+import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_SPLASH_SCREEN;
+import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_WINDOWS_DRAWN;
+
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.app.ActivityManager;
@@ -124,9 +129,16 @@
     /** The final animation targets derived from participants after promotion. */
     private ArraySet<WindowContainer> mTargets = null;
 
+    private TransitionInfo.AnimationOptions mOverrideOptions;
+
     private @TransitionState int mState = STATE_COLLECTING;
     private boolean mReadyCalled = false;
 
+    // TODO(b/188595497): remove when not needed.
+    /** @see RecentsAnimationController#mNavigationBarAttachedToApp */
+    private boolean mNavBarAttachedToApp = false;
+    private int mNavBarDisplayId = INVALID_DISPLAY;
+
     Transition(@WindowManager.TransitionType int type, @WindowManager.TransitionFlags int flags,
             TransitionController controller, BLASTSyncEngine syncEngine) {
         mType = type;
@@ -136,6 +148,10 @@
         mSyncId = mSyncEngine.startSyncSet(this);
     }
 
+    void addFlag(int flag) {
+        mFlags |= flag;
+    }
+
     @VisibleForTesting
     int getSyncId() {
         return mSyncId;
@@ -207,6 +223,15 @@
     }
 
     /**
+     * Set animation options for collecting transition by ActivityRecord.
+     * @param options AnimationOptions captured from ActivityOptions
+     */
+    void setOverrideAnimation(TransitionInfo.AnimationOptions options) {
+        if (mSyncId < 0) return;
+        mOverrideOptions = options;
+    }
+
+    /**
      * Call this when all known changes related to this transition have been applied. Until
      * all participants have finished drawing, the transition can still collect participants.
      *
@@ -275,12 +300,28 @@
         }
 
         // Commit all going-invisible containers
+        boolean activitiesWentInvisible = false;
         for (int i = 0; i < mParticipants.size(); ++i) {
             final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
-            if (ar != null && !ar.isVisibleRequested()) {
-                ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
-                        "  Commit activity becoming invisible: %s", ar);
-                ar.commitVisibility(false /* visible */, false /* performLayout */);
+            if (ar != null) {
+                if (!ar.isVisibleRequested()) {
+                    // If activity is capable of entering PiP, give it a chance to enter it now.
+                    if (ar.getDeferHidingClient() && ar.getTask() != null) {
+                        mController.mAtm.mTaskSupervisor.mUserLeaving = true;
+                        ar.getTaskFragment().startPausing(false /* uiSleeping */,
+                                null /* resuming */, "finishTransition");
+                        mController.mAtm.mTaskSupervisor.mUserLeaving = false;
+                    }
+                    ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+                            "  Commit activity becoming invisible: %s", ar);
+                    ar.commitVisibility(false /* visible */, false /* performLayout */);
+                    activitiesWentInvisible = true;
+                }
+                if (mChanges.get(ar).mVisible != ar.isVisibleRequested()) {
+                    // Legacy dispatch relies on this (for now).
+                    ar.mEnteringAnimation = ar.isVisibleRequested();
+                }
+                mController.dispatchLegacyAppTransitionFinished(ar);
             }
             final WallpaperWindowToken wt = mParticipants.valueAt(i).asWallpaperToken();
             if (wt != null && !wt.isVisibleRequested()) {
@@ -289,6 +330,14 @@
                 wt.commitVisibility(false /* visible */);
             }
         }
+        if (activitiesWentInvisible) {
+            // Always schedule stop processing when transition finishes because activities don't
+            // stop while they are in a transition thus their stop could still be pending.
+            mController.mAtm.mTaskSupervisor
+                    .scheduleProcessStoppingAndFinishingActivitiesIfNeeded();
+        }
+
+        legacyRestoreNavigationBarFromApp();
     }
 
     void abort() {
@@ -297,6 +346,7 @@
         if (mState != STATE_COLLECTING) {
             throw new IllegalStateException("Too late to abort.");
         }
+        mController.dispatchLegacyAppTransitionCancelled();
         mState = STATE_ABORT;
         // Syncengine abort will call through to onTransactionReady()
         mSyncEngine.abort(mSyncId);
@@ -327,6 +377,7 @@
             mController.mAtm.mRootWindowContainer.getDisplayContent(displayId)
                     .getPendingTransaction().merge(transaction);
             mSyncId = -1;
+            mOverrideOptions = null;
             return;
         }
 
@@ -340,9 +391,15 @@
         // Resolve the animating targets from the participants
         mTargets = calculateTargets(mParticipants, mChanges);
         final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, mChanges);
+        info.setAnimationOptions(mOverrideOptions);
+
+        // TODO(b/188669821): Move to animation impl in shell.
+        handleLegacyRecentsStartBehavior(displayId, info);
 
         handleNonAppWindowsInTransition(displayId, mType, mFlags);
 
+        reportStartReasonsToLogger();
+
         // Manually show any activities that are visibleRequested. This is needed to properly
         // support simultaneous animation queueing/merging. Specifically, if transition A makes
         // an activity invisible, it's finishTransaction (which is applied *after* the animation)
@@ -360,6 +417,7 @@
         mFinishTransaction = mController.mAtm.mWindowManager.mTransactionFactory.get();
         buildFinishTransaction(mFinishTransaction, info.getRootLeash());
         if (mController.getTransitionPlayer() != null) {
+            mController.dispatchLegacyAppTransitionStarting(info);
             try {
                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                         "Calling onTransitionReady: %s", info);
@@ -375,6 +433,7 @@
             cleanUpOnFailure();
         }
         mSyncId = -1;
+        mOverrideOptions = null;
     }
 
     /**
@@ -394,6 +453,100 @@
         finishTransition();
     }
 
+    /** @see RecentsAnimationController#attachNavigationBarToApp */
+    private void handleLegacyRecentsStartBehavior(int displayId, TransitionInfo info) {
+        if ((mFlags & TRANSIT_FLAG_IS_RECENTS) == 0) {
+            return;
+        }
+        final DisplayContent dc =
+                mController.mAtm.mRootWindowContainer.getDisplayContent(displayId);
+        if (dc == null || !dc.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()
+                // Skip the case where the nav bar is controlled by fade rotation.
+                || dc.getFadeRotationAnimationController() != null) {
+            return;
+        }
+
+        WindowContainer topWC = null;
+        // Find the top-most non-home, closing app.
+        for (int i = 0; i < info.getChanges().size(); ++i) {
+            final TransitionInfo.Change c = info.getChanges().get(i);
+            if (c.getTaskInfo() == null || c.getTaskInfo().displayId != displayId
+                    || c.getTaskInfo().getActivityType() != ACTIVITY_TYPE_STANDARD
+                    || !(c.getMode() == TRANSIT_CLOSE || c.getMode() == TRANSIT_TO_BACK)) {
+                continue;
+            }
+            topWC = WindowContainer.fromBinder(c.getContainer().asBinder());
+            break;
+        }
+        if (topWC == null || topWC.inMultiWindowMode()) {
+            return;
+        }
+
+        final WindowState navWindow = dc.getDisplayPolicy().getNavigationBar();
+        if (navWindow == null || navWindow.mToken == null) {
+            return;
+        }
+        mNavBarAttachedToApp = true;
+        mNavBarDisplayId = displayId;
+        navWindow.mToken.cancelAnimation();
+        final SurfaceControl.Transaction t = navWindow.mToken.getPendingTransaction();
+        final SurfaceControl navSurfaceControl = navWindow.mToken.getSurfaceControl();
+        t.reparent(navSurfaceControl, topWC.getSurfaceControl());
+        t.show(navSurfaceControl);
+
+        final WindowContainer imeContainer = dc.getImeContainer();
+        if (imeContainer.isVisible()) {
+            t.setRelativeLayer(navSurfaceControl, imeContainer.getSurfaceControl(), 1);
+        } else {
+            // Place the nav bar on top of anything else in the top activity.
+            t.setLayer(navSurfaceControl, Integer.MAX_VALUE);
+        }
+        if (mController.mStatusBar != null) {
+            mController.mStatusBar.setNavigationBarLumaSamplingEnabled(displayId, false);
+        }
+    }
+
+    /** @see RecentsAnimationController#restoreNavigationBarFromApp */
+    void legacyRestoreNavigationBarFromApp() {
+        if (!mNavBarAttachedToApp) return;
+        mNavBarAttachedToApp = false;
+
+        if (mController.mStatusBar != null) {
+            mController.mStatusBar.setNavigationBarLumaSamplingEnabled(mNavBarDisplayId, true);
+        }
+
+        final DisplayContent dc =
+                mController.mAtm.mRootWindowContainer.getDisplayContent(mNavBarDisplayId);
+        final WindowState navWindow = dc.getDisplayPolicy().getNavigationBar();
+        if (navWindow == null) return;
+        navWindow.setSurfaceTranslationY(0);
+
+        final WindowToken navToken = navWindow.mToken;
+        if (navToken == null) return;
+        final SurfaceControl.Transaction t = dc.getPendingTransaction();
+        final WindowContainer parent = navToken.getParent();
+        t.setLayer(navToken.getSurfaceControl(), navToken.getLastLayer());
+
+        boolean animate = false;
+        // Search for the home task. If it is supposed to be visible, then the navbar is not at
+        // the bottom of the screen, so we need to animate it.
+        for (int i = 0; i < mTargets.size(); ++i) {
+            final Task task = mTargets.valueAt(i).asTask();
+            if (task == null || !task.isHomeOrRecentsRootTask()) continue;
+            animate = task.isVisibleRequested();
+            break;
+        }
+
+        if (animate) {
+            final NavBarFadeAnimationController controller =
+                    new NavBarFadeAnimationController(dc);
+            controller.fadeWindowToken(true);
+        } else {
+            // Reparent the SurfaceControl of nav bar token back.
+            t.reparent(navToken.getSurfaceControl(), parent.getSurfaceControl());
+        }
+    }
+
     private void handleNonAppWindowsInTransition(int displayId,
             @WindowManager.TransitionType int transit, int flags) {
         final DisplayContent dc =
@@ -427,6 +580,23 @@
         }
     }
 
+    private void reportStartReasonsToLogger() {
+        // Record transition start in metrics logger. We just assume everything is "DRAWN"
+        // at this point since splash-screen is a presentation (shell) detail.
+        ArrayMap<WindowContainer, Integer> reasons = new ArrayMap<>();
+        for (int i = mParticipants.size() - 1; i >= 0; --i) {
+            ActivityRecord r = mParticipants.valueAt(i).asActivityRecord();
+            if (r == null) continue;
+            // At this point, r is "ready", but if it's not "ALL ready" then it is probably only
+            // ready due to starting-window.
+            reasons.put(r, (r.mStartingData instanceof SplashScreenStartingData
+                    && !r.mLastAllReadyAtSync)
+                    ? APP_TRANSITION_SPLASH_SCREEN : APP_TRANSITION_WINDOWS_DRAWN);
+        }
+        mController.mAtm.mTaskSupervisor.getActivityMetricsLogger().notifyTransitionStarting(
+                reasons);
+    }
+
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder(64);
@@ -629,6 +799,8 @@
             if (reportIfNotTop(wc)) {
                 tmpList.add(wc);
             }
+            // Wallpaper must be the top (regardless of how nested it is in DisplayAreas).
+            boolean skipIntermediateReports = isWallpaper(wc);
             for (WindowContainer p = wc.getParent(); p != null; p = p.getParent()) {
                 if (!p.isAttached() || !changes.get(p).hasChanged(p)) {
                     // Again, we're skipping no-ops
@@ -637,7 +809,9 @@
                 if (participants.contains(p)) {
                     topParent = p;
                     break;
-                } else if (reportIfNotTop(p)) {
+                } else if (isWallpaper(p)) {
+                    skipIntermediateReports = true;
+                } else if (reportIfNotTop(p) && !skipIntermediateReports) {
                     tmpList.add(p);
                 }
             }
@@ -723,17 +897,11 @@
         }
 
         // Find the top-most shared ancestor of app targets
-        WindowContainer ancestor = null;
-        for (int i = appTargets.size() - 1; i >= 0; --i) {
-            final WindowContainer wc = appTargets.valueAt(i);
-            ancestor = wc;
-            break;
-        }
-        if (ancestor == null) {
+        if (appTargets.isEmpty()) {
             out.setRootLeash(new SurfaceControl(), 0, 0);
             return out;
         }
-        ancestor = ancestor.getParent();
+        WindowContainer ancestor = appTargets.valueAt(appTargets.size() - 1).getParent();
 
         // Go up ancestor parent chain until all targets are descendants.
         ancestorLoop:
@@ -805,6 +973,10 @@
         return out;
     }
 
+    boolean getLegacyIsReady() {
+        return mState == STATE_STARTED && mSyncId >= 0 && mSyncEngine.isReady(mSyncId);
+    }
+
     static Transition fromBinder(IBinder binder) {
         return (Transition) binder;
     }
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index cc63c49..3d7b34b 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -17,6 +17,13 @@
 package com.android.server.wm;
 
 import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
+import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
+import static android.view.WindowManager.TRANSIT_NONE;
 import static android.view.WindowManager.TRANSIT_OPEN;
 
 import android.annotation.NonNull;
@@ -24,14 +31,19 @@
 import android.app.ActivityManager;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
 import android.view.WindowManager;
 import android.window.IRemoteTransition;
 import android.window.ITransitionPlayer;
+import android.window.TransitionInfo;
 import android.window.TransitionRequestInfo;
 
 import com.android.internal.protolog.ProtoLogGroup;
 import com.android.internal.protolog.common.ProtoLog;
+import com.android.server.LocalServices;
+import com.android.server.statusbar.StatusBarManagerInternal;
 
 import java.util.ArrayList;
 
@@ -41,15 +53,25 @@
 class TransitionController {
     private static final String TAG = "TransitionController";
 
+    // State constants to line-up with legacy app-transition proto expectations.
+    private static final int LEGACY_STATE_IDLE = 0;
+    private static final int LEGACY_STATE_READY = 1;
+    private static final int LEGACY_STATE_RUNNING = 2;
+
     private ITransitionPlayer mTransitionPlayer;
     final ActivityTaskManagerService mAtm;
 
+    private final ArrayList<WindowManagerInternal.AppTransitionListener> mLegacyListeners =
+            new ArrayList<>();
+
     /**
      * Currently playing transitions (in the order they were started). When finished, records are
      * removed from this list.
      */
     private final ArrayList<Transition> mPlayingTransitions = new ArrayList<>();
 
+    final Lock mRunningLock = new Lock();
+
     private final IBinder.DeathRecipient mTransitionPlayerDeath = () -> {
         // clean-up/finish any playing transitions.
         for (int i = 0; i < mPlayingTransitions.size(); ++i) {
@@ -57,13 +79,18 @@
         }
         mPlayingTransitions.clear();
         mTransitionPlayer = null;
+        mRunningLock.doNotifyLocked();
     };
 
     /** The transition currently being constructed (collecting participants). */
     private Transition mCollectingTransition = null;
 
+    // TODO(b/188595497): remove when not needed.
+    final StatusBarManagerInternal mStatusBar;
+
     TransitionController(ActivityTaskManagerService atm) {
         mAtm = atm;
+        mStatusBar = LocalServices.getService(StatusBarManagerInternal.class);
     }
 
     /** @see #createTransition(int, int) */
@@ -76,7 +103,7 @@
      * Creates a transition. It can immediately collect participants.
      */
     @NonNull
-    Transition createTransition(@WindowManager.TransitionType int type,
+    private Transition createTransition(@WindowManager.TransitionType int type,
             @WindowManager.TransitionFlags int flags) {
         if (mTransitionPlayer == null) {
             throw new IllegalStateException("Shell Transitions not enabled");
@@ -87,6 +114,7 @@
         mCollectingTransition = new Transition(type, flags, this, mAtm.mWindowManager.mSyncEngine);
         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Creating Transition: %s",
                 mCollectingTransition);
+        dispatchLegacyAppTransitionPending();
         return mCollectingTransition;
     }
 
@@ -154,6 +182,11 @@
         return false;
     }
 
+    @WindowManager.TransitionType
+    int getCollectingTransitionType() {
+        return mCollectingTransition != null ? mCollectingTransition.mType : TRANSIT_NONE;
+    }
+
     /**
      * @see #requestTransitionIfNeeded(int, int, WindowContainer, IRemoteTransition)
      */
@@ -240,6 +273,12 @@
         mCollectingTransition.collectExistenceChange(wc);
     }
 
+    /** @see Transition#setOverrideAnimation */
+    void setOverrideAnimation(TransitionInfo.AnimationOptions options) {
+        if (mCollectingTransition == null) return;
+        mCollectingTransition.setOverrideAnimation(options);
+    }
+
     /** @see Transition#setReady */
     void setReady(boolean ready) {
         if (mCollectingTransition == null) return;
@@ -261,6 +300,7 @@
         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Finish Transition: %s", record);
         mPlayingTransitions.remove(record);
         record.finishTransition();
+        mRunningLock.doNotifyLocked();
     }
 
     void moveToPlaying(Transition transition) {
@@ -279,4 +319,109 @@
         mCollectingTransition = null;
     }
 
+    /**
+     * Explicitly mark the collectingTransition as being part of recents gesture. Used for legacy
+     * behaviors.
+     * TODO(b/188669821): Remove once legacy recents behavior is moved to shell.
+     */
+    void setIsLegacyRecents() {
+        if (mCollectingTransition == null) return;
+        mCollectingTransition.addFlag(TRANSIT_FLAG_IS_RECENTS);
+    }
+
+    void legacyDetachNavigationBarFromApp(@NonNull IBinder token) {
+        final Transition transition = Transition.fromBinder(token);
+        if (transition == null || !mPlayingTransitions.contains(transition)) {
+            Slog.e(TAG, "Transition isn't playing: " + token);
+            return;
+        }
+        transition.legacyRestoreNavigationBarFromApp();
+    }
+
+    void registerLegacyListener(WindowManagerInternal.AppTransitionListener listener) {
+        mLegacyListeners.add(listener);
+    }
+
+    void dispatchLegacyAppTransitionPending() {
+        for (int i = 0; i < mLegacyListeners.size(); ++i) {
+            mLegacyListeners.get(i).onAppTransitionPendingLocked();
+        }
+    }
+
+    void dispatchLegacyAppTransitionStarting(TransitionInfo info) {
+        final boolean keyguardGoingAway = info.getType() == TRANSIT_KEYGUARD_GOING_AWAY
+                || (info.getFlags() & (TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE
+                        | TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION
+                        | TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER
+                        | TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION)) != 0;
+        for (int i = 0; i < mLegacyListeners.size(); ++i) {
+            mLegacyListeners.get(i).onAppTransitionStartingLocked(keyguardGoingAway,
+                    0 /* durationHint */, SystemClock.uptimeMillis(),
+                    AnimationAdapter.STATUS_BAR_TRANSITION_DURATION);
+        }
+    }
+
+    void dispatchLegacyAppTransitionFinished(ActivityRecord ar) {
+        for (int i = 0; i < mLegacyListeners.size(); ++i) {
+            mLegacyListeners.get(i).onAppTransitionFinishedLocked(ar.token);
+        }
+    }
+
+    void dispatchLegacyAppTransitionCancelled() {
+        for (int i = 0; i < mLegacyListeners.size(); ++i) {
+            mLegacyListeners.get(i).onAppTransitionCancelledLocked(
+                    false /* keyguardGoingAway */);
+        }
+    }
+
+    void dumpDebugLegacy(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        int state = LEGACY_STATE_IDLE;
+        if (!mPlayingTransitions.isEmpty()) {
+            state = LEGACY_STATE_RUNNING;
+        } else if (mCollectingTransition != null && mCollectingTransition.getLegacyIsReady()) {
+            state = LEGACY_STATE_READY;
+        }
+        proto.write(AppTransitionProto.APP_TRANSITION_STATE, state);
+        proto.end(token);
+    }
+
+    class Lock {
+        private int mTransitionWaiters = 0;
+        void runWhenIdle(long timeout, Runnable r) {
+            synchronized (mAtm.mGlobalLock) {
+                if (!inTransition()) {
+                    r.run();
+                    return;
+                }
+                mTransitionWaiters += 1;
+            }
+            final long startTime = SystemClock.uptimeMillis();
+            final long endTime = startTime + timeout;
+            while (true) {
+                synchronized (mAtm.mGlobalLock) {
+                    if (!inTransition() || SystemClock.uptimeMillis() > endTime) {
+                        mTransitionWaiters -= 1;
+                        r.run();
+                        return;
+                    }
+                }
+                synchronized (this) {
+                    try {
+                        this.wait(timeout);
+                    } catch (InterruptedException e) {
+                        return;
+                    }
+                }
+            }
+        }
+
+        void doNotifyLocked() {
+            synchronized (this) {
+                if (mTransitionWaiters > 0) {
+                    this.notifyAll();
+                }
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index b1c7e19..913c5e5 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -1671,6 +1671,15 @@
         return false;
     }
 
+    boolean forAllLeafTaskFragments(Function<TaskFragment, Boolean> callback) {
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            if (mChildren.get(i).forAllLeafTaskFragments(callback)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * For all root tasks at or below this container call the callback.
      *
@@ -1726,6 +1735,28 @@
         }
     }
 
+    /**
+     * For all task fragments at or below this container call the callback.
+     *
+     * @param callback Callback to be called for every task.
+     */
+    void forAllTaskFragments(Consumer<TaskFragment> callback) {
+        forAllTaskFragments(callback, true /*traverseTopToBottom*/);
+    }
+
+    void forAllTaskFragments(Consumer<TaskFragment> callback, boolean traverseTopToBottom) {
+        final int count = mChildren.size();
+        if (traverseTopToBottom) {
+            for (int i = count - 1; i >= 0; --i) {
+                mChildren.get(i).forAllTaskFragments(callback, traverseTopToBottom);
+            }
+        } else {
+            for (int i = 0; i < count; i++) {
+                mChildren.get(i).forAllTaskFragments(callback, traverseTopToBottom);
+            }
+        }
+    }
+
     void forAllLeafTasks(Consumer<Task> callback, boolean traverseTopToBottom) {
         final int count = mChildren.size();
         if (traverseTopToBottom) {
@@ -1739,6 +1770,19 @@
         }
     }
 
+    void forAllLeafTaskFragments(Consumer<TaskFragment> callback, boolean traverseTopToBottom) {
+        final int count = mChildren.size();
+        if (traverseTopToBottom) {
+            for (int i = count - 1; i >= 0; --i) {
+                mChildren.get(i).forAllLeafTaskFragments(callback, traverseTopToBottom);
+            }
+        } else {
+            for (int i = 0; i < count; i++) {
+                mChildren.get(i).forAllLeafTaskFragments(callback, traverseTopToBottom);
+            }
+        }
+    }
+
     /**
      * For all root tasks at or below this container call the callback.
      *
@@ -3075,6 +3119,11 @@
     }
 
     /** Cheap way of doing cast and instanceof. */
+    TaskFragment asTaskFragment() {
+        return null;
+    }
+
+    /** Cheap way of doing cast and instanceof. */
     WindowToken asWindowToken() {
         return null;
     }
@@ -3282,6 +3331,20 @@
     }
 
     /**
+     * Special helper to check that all windows are synced (vs just top one). This is only
+     * used to differentiate between starting-window vs full-drawn in activity-metrics reporting.
+     */
+    boolean allSyncFinished() {
+        if (!isVisibleRequested()) return true;
+        if (mSyncState != SYNC_STATE_READY) return false;
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final WindowContainer child = mChildren.get(i);
+            if (!child.allSyncFinished()) return false;
+        }
+        return true;
+    }
+
+    /**
      * Called during reparent to handle sync state when the hierarchy changes.
      * If this is in a sync group and gets reparented out, it will cancel syncing.
      * If this is not in a sync group and gets parented into one, it will prepare itself.
diff --git a/services/core/java/com/android/server/wm/WindowFrames.java b/services/core/java/com/android/server/wm/WindowFrames.java
index ffd6d21..baea854 100644
--- a/services/core/java/com/android/server/wm/WindowFrames.java
+++ b/services/core/java/com/android/server/wm/WindowFrames.java
@@ -89,6 +89,11 @@
     final Rect mCompatFrame = new Rect();
 
     /**
+     * {@code true} if the window frame is a simulated frame and attached to a decor window.
+     */
+    boolean mIsSimulatingDecorWindow = false;
+
+    /**
      * Whether the parent frame would have been different if there was no display cutout.
      */
     private boolean mParentFrameWasClippedByDisplayCutout;
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 47087cf..5bc4d49 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -40,6 +40,7 @@
 import com.android.server.policy.WindowManagerPolicy;
 
 import java.util.List;
+import java.util.Set;
 
 /**
  * Window manager local system service interface.
@@ -54,17 +55,18 @@
      */
     public interface AccessibilityControllerInternal {
         /**
-         * Enable the accessibility trace logging.
+         * Start tracing for the given logging types.
+         * @param loggingTypeFlags flags of the logging types enabled.
          */
-        void startTrace();
+        void startTrace(long loggingTypeFlags);
 
         /**
-         * Disable the accessibility trace logging.
+         * Disable accessibility tracing for all logging types.
          */
         void stopTrace();
 
         /**
-         * Is trace enabled or not.
+         * Is tracing enabled for any logging type.
          */
         boolean isAccessibilityTracingEnabled();
 
@@ -73,20 +75,23 @@
          *
          * @param where A string to identify this log entry, which can be used to filter/search
          *        through the tracing file.
+         * @param loggingTypeFlags The flags for the logging types this log entry belongs to.
          * @param callingParams The parameters for the method to be logged.
          * @param a11yDump The proto byte array for a11y state when the entry is generated.
          * @param callingUid The calling uid.
          * @param stackTrace The stack trace, null if not needed.
+         * @param ignoreStackEntries The stack entries can be removed
          */
         void logTrace(
-                String where, String callingParams, byte[] a11yDump, int callingUid,
-                StackTraceElement[] stackTrace);
+                String where, long loggingTypeFlags, String callingParams, byte[] a11yDump,
+                int callingUid, StackTraceElement[] stackTrace, Set<String> ignoreStackEntries);
 
         /**
          * Add an accessibility trace entry.
          *
          * @param where A string to identify this log entry, which can be used to filter/search
          *        through the tracing file.
+         * @param loggingTypeFlags The flags for the logging types this log entry belongs to.
          * @param callingParams The parameters for the method to be logged.
          * @param a11yDump The proto byte array for a11y state when the entry is generated.
          * @param callingUid The calling uid.
@@ -94,9 +99,11 @@
          * @param timeStamp The time when the method to be logged is called.
          * @param processId The calling process Id.
          * @param threadId The calling thread Id.
+         * @param ignoreStackEntries The stack entries can be removed
          */
-        void logTrace(String where, String callingParams, byte[] a11yDump, int callingUid,
-                StackTraceElement[] callStack, long timeStamp, int processId, long threadId);
+        void logTrace(String where, long loggingTypeFlags, String callingParams,
+                byte[] a11yDump, int callingUid, StackTraceElement[] callStack, long timeStamp,
+                int processId, long threadId, Set<String> ignoreStackEntries);
     }
 
     /**
@@ -115,6 +122,16 @@
          */
         void onWindowsForAccessibilityChanged(boolean forceSend, int topFocusedDisplayId,
                 IBinder topFocusedWindowToken, @NonNull List<WindowInfo> windows);
+
+        /**
+         * Called when the display is reparented and becomes an embedded
+         * display. The {@link WindowsForAccessibilityCallback} with the given embedded
+         * display will be replaced by the {@link WindowsForAccessibilityCallback}
+         * associated with its parent display at the same time.
+         *
+         * @param embeddedDisplayId The embedded display Id.
+         */
+        void onDisplayReparented(int embeddedDisplayId);
     }
 
     /**
@@ -143,11 +160,11 @@
         void onRectangleOnScreenRequested(int left, int top, int right, int bottom);
 
         /**
-         * Notifies that the rotation changed.
+         * Notifies that the display size is changed when rotation or the
+         * logical display is changed.
          *
-         * @param rotation The current rotation.
          */
-        void onRotationChanged(int rotation);
+        void onDisplaySizeChanged();
 
         /**
          * Notifies that the context of the user changed. For example, an application
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 1ec9187..53e02e5 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -47,6 +47,7 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
+import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
 import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
 import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
 import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
@@ -1804,7 +1805,8 @@
             winAnimator.mEnterAnimationPending = true;
             winAnimator.mEnteringAnimation = true;
             // Check if we need to prepare a transition for replacing window first.
-            if (activity != null && activity.isVisible()
+            if (mAtmService.getTransitionController().getTransitionPlayer() == null
+                    && activity != null && activity.isVisible()
                     && !prepareWindowReplacementTransition(activity)) {
                 // If not, check if need to set up a dummy transition during display freeze
                 // so that the unfreeze wait for the apps to draw. This might be needed if
@@ -2478,7 +2480,7 @@
             if (win.mActivityRecord != null) {
                 win.mActivityRecord.updateReportedVisibilityLocked();
             }
-            if (displayPolicy.areSystemBarsForcedShownLw(win)) {
+            if (displayPolicy.areSystemBarsForcedShownLw()) {
                 result |= WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS;
             }
             if (!win.isGoneForLayout()) {
@@ -2524,7 +2526,8 @@
                 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
             }
             if (winAnimator.mSurfaceController != null) {
-                win.calculateSurfaceBounds(win.getAttrs(), mTmpRect);
+                win.calculateSurfaceBounds(win.getLayoutingAttrs(
+                        win.getWindowConfiguration().getRotation()), mTmpRect);
                 outSurfaceSize.set(mTmpRect.width(), mTmpRect.height());
             }
             getInsetsSourceControls(win, outActiveControls);
@@ -2717,8 +2720,8 @@
     }
 
     @Override
-    public boolean attachWindowContextToDisplayArea(IBinder clientToken, int type, int displayId,
-            Bundle options) {
+    public Configuration attachWindowContextToDisplayArea(IBinder clientToken, int
+            type, int displayId, Bundle options) {
         final boolean callerCanManageAppTokens = checkCallingPermission(MANAGE_APP_TOKENS,
                 "attachWindowContextToDisplayArea", false /* printLog */);
         final int callingUid = Binder.getCallingUid();
@@ -2729,15 +2732,17 @@
                 if (dc == null) {
                     ProtoLog.w(WM_ERROR, "attachWindowContextToDisplayArea: trying to attach"
                             + " to a non-existing display:%d", displayId);
-                    return false;
+                    return null;
                 }
                 // TODO(b/155340867): Investigate if we still need roundedCornerOverlay after
                 // the feature b/155340867 is completed.
                 final DisplayArea da = dc.findAreaForWindowType(type, options,
                         callerCanManageAppTokens, false /* roundedCornerOverlay */);
+                // TODO(b/190019118): Avoid to send onConfigurationChanged because it has been done
+                //  in return value of attachWindowContextToDisplayArea.
                 mWindowContextListenerController.registerWindowContainerListener(clientToken, da,
                         callingUid, type, options);
-                return true;
+                return da.getConfiguration();
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -5390,6 +5395,25 @@
         }
     }
 
+    void setSandboxDisplayApis(int displayId, boolean sandboxDisplayApis) {
+        if (mContext.checkCallingOrSelfPermission(WRITE_SECURE_SETTINGS)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Must hold permission " + WRITE_SECURE_SETTINGS);
+        }
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (mGlobalLock) {
+                final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
+                if (displayContent != null) {
+                    displayContent.setSandboxDisplayApis(sandboxDisplayApis);
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
     /** The global settings only apply to default display. */
     private boolean applyForcedPropertiesForDefaultDisplay() {
         boolean changed = false;
@@ -7571,6 +7595,7 @@
         public void registerAppTransitionListener(AppTransitionListener listener) {
             synchronized (mGlobalLock) {
                 getDefaultDisplayContentLocked().mAppTransition.registerListenerLocked(listener);
+                mAtmService.getTransitionController().registerLegacyListener(listener);
             }
         }
 
@@ -8160,11 +8185,11 @@
             displayContent.getParent().positionChildAt(WindowContainer.POSITION_TOP, displayContent,
                     true /* includingParents */);
         }
-        handleTaskFocusChange(touchedWindow.getTask());
+        handleTaskFocusChange(touchedWindow.getTask(), touchedWindow.mActivityRecord);
     }
 
     @VisibleForTesting
-    void handleTaskFocusChange(Task task) {
+    void handleTaskFocusChange(Task task, ActivityRecord touchedActivity) {
         if (task == null) {
             return;
         }
@@ -8183,7 +8208,7 @@
             }
         }
 
-        mAtmService.setFocusedTask(task.mTaskId);
+        mAtmService.setFocusedTask(task.mTaskId, touchedActivity);
     }
 
     /**
@@ -8556,7 +8581,7 @@
             }
 
             if (win.mActivityRecord == null || !win.mActivityRecord.isState(
-                    Task.ActivityState.RESUMED)) {
+                    ActivityRecord.State.RESUMED)) {
                 mDisplayHashController.sendDisplayHashError(callback,
                         DISPLAY_HASH_ERROR_MISSING_WINDOW);
                 return;
@@ -8611,4 +8636,23 @@
             return snapshot != null && snapshot.hasImeSurface();
         }
     }
+
+    @Override
+    public int getImeDisplayId() {
+        // TODO(b/189805422): Add a toast to notify users that IMS may get extra
+        //  onConfigurationChanged callback when perDisplayFocus is enabled.
+        //  Enabling perDisplayFocus means that we track focus on each display, so we don't have
+        //  the "top focus" display and getTopFocusedDisplayContent returns the default display
+        //  as the fallback. It leads to InputMethodService receives an extra onConfiguration
+        //  callback when InputMethodService move from a secondary display to another display
+        //  with the same display metrics because InputMethodService will always associate with
+        //  the ImeContainer on the default display in onCreate and receive a configuration update
+        //  to match default display ImeContainer and then receive another configuration update
+        //  from attachToWindowToken.
+        synchronized (mGlobalLock) {
+            final DisplayContent dc = mRoot.getTopFocusedDisplayContent();
+            return dc.getImePolicy() == DISPLAY_IME_POLICY_LOCAL ? dc.getDisplayId()
+                    : DEFAULT_DISPLAY;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index a94fd07..d5965494 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -19,6 +19,12 @@
 import static android.os.Build.IS_USER;
 import static android.view.CrossWindowBlurListeners.CROSS_WINDOW_BLUR_SUPPORTED;
 
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_WALLPAPER;
+
+import android.graphics.Color;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.ParcelFileDescriptor;
@@ -36,6 +42,7 @@
 import com.android.internal.protolog.ProtoLogImpl;
 import com.android.server.LocalServices;
 import com.android.server.statusbar.StatusBarManagerInternal;
+import com.android.server.wm.LetterboxConfiguration.LetterboxBackgroundType;
 
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -58,10 +65,12 @@
 
     // Internal service impl -- must perform security checks before touching.
     private final WindowManagerService mInternal;
+    private final LetterboxConfiguration mLetterboxConfiguration;
 
     public WindowManagerShellCommand(WindowManagerService service) {
         mInterface = service;
         mInternal = service;
+        mLetterboxConfiguration = service.mLetterboxConfiguration;
     }
 
     @Override
@@ -113,6 +122,14 @@
                     return runGetIgnoreOrientationRequest(pw);
                 case "dump-visible-window-views":
                     return runDumpVisibleWindowViews(pw);
+                case "set-letterbox-style":
+                    return runSetLetterboxStyle(pw);
+                case "get-letterbox-style":
+                    return runGetLetterboxStyle(pw);
+                case "reset-letterbox-style":
+                    return runResetLetterboxStyle(pw);
+                case "set-sandbox-display-apis":
+                    return runSandboxDisplayApis(pw);
                 case "set-multi-window-config":
                     return runSetMultiWindowConfig();
                 case "get-multi-window-config":
@@ -331,6 +348,37 @@
         return 0;
     }
 
+    /**
+     * Override display size and metrics to reflect the DisplayArea of the calling activity.
+     */
+    private int runSandboxDisplayApis(PrintWriter pw) throws RemoteException {
+        int displayId = Display.DEFAULT_DISPLAY;
+        String arg = getNextArgRequired();
+        if ("-d".equals(arg)) {
+            displayId = Integer.parseInt(getNextArgRequired());
+            arg = getNextArgRequired();
+        }
+
+        final boolean sandboxDisplayApis;
+        switch (arg) {
+            case "true":
+            case "1":
+                sandboxDisplayApis = true;
+                break;
+            case "false":
+            case "0":
+                sandboxDisplayApis = false;
+                break;
+            default:
+                getErrPrintWriter().println("Error: expecting true, 1, false, 0, but we "
+                        + "get " + arg);
+                return -1;
+        }
+
+        mInternal.setSandboxDisplayApis(displayId, sandboxDisplayApis);
+        return 0;
+    }
+
     private int runDismissKeyguard(PrintWriter pw) throws RemoteException {
         mInterface.dismissKeyguard(null /* callback */, null /* message */);
         return 0;
@@ -548,6 +596,231 @@
         return 0;
     }
 
+    private int runSetFixedOrientationLetterboxAspectRatio(PrintWriter pw) throws RemoteException {
+        final float aspectRatio;
+        try {
+            String arg = getNextArgRequired();
+            aspectRatio = Float.parseFloat(arg);
+        } catch (NumberFormatException  e) {
+            getErrPrintWriter().println("Error: bad aspect ratio format " + e);
+            return -1;
+        } catch (IllegalArgumentException  e) {
+            getErrPrintWriter().println(
+                    "Error: 'reset' or aspect ratio should be provided as an argument " + e);
+            return -1;
+        }
+        synchronized (mInternal.mGlobalLock) {
+            mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(aspectRatio);
+        }
+        return 0;
+    }
+
+    private int runSetLetterboxActivityCornersRadius(PrintWriter pw) throws RemoteException {
+        final int cornersRadius;
+        try {
+            String arg = getNextArgRequired();
+            cornersRadius = Integer.parseInt(arg);
+        } catch (NumberFormatException  e) {
+            getErrPrintWriter().println("Error: bad corners radius format " + e);
+            return -1;
+        } catch (IllegalArgumentException  e) {
+            getErrPrintWriter().println(
+                    "Error: 'reset' or corners radius should be provided as an argument " + e);
+            return -1;
+        }
+        synchronized (mInternal.mGlobalLock) {
+            mLetterboxConfiguration.setLetterboxActivityCornersRadius(cornersRadius);
+        }
+        return 0;
+    }
+
+    private int runSetLetterboxBackgroundType(PrintWriter pw) throws RemoteException {
+        @LetterboxBackgroundType final int backgroundType;
+        try {
+            String arg = getNextArgRequired();
+            switch (arg) {
+                case "solid_color":
+                    backgroundType = LETTERBOX_BACKGROUND_SOLID_COLOR;
+                    break;
+                case "app_color_background":
+                    backgroundType = LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND;
+                    break;
+                case "app_color_background_floating":
+                    backgroundType = LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
+                    break;
+                case "wallpaper":
+                    backgroundType = LETTERBOX_BACKGROUND_WALLPAPER;
+                    break;
+                default:
+                    getErrPrintWriter().println(
+                            "Error: 'reset', 'solid_color', 'app_color_background' or "
+                            + "'wallpaper' should be provided as an argument");
+                    return -1;
+            }
+        } catch (IllegalArgumentException  e) {
+            getErrPrintWriter().println(
+                    "Error: 'reset', 'solid_color', 'app_color_background' or "
+                        + "'wallpaper' should be provided as an argument" + e);
+            return -1;
+        }
+        synchronized (mInternal.mGlobalLock) {
+            mLetterboxConfiguration.setLetterboxBackgroundType(backgroundType);
+        }
+        return 0;
+    }
+
+    private int runSetLetterboxBackgroundColor(PrintWriter pw) throws RemoteException {
+        final Color color;
+        try {
+            String arg = getNextArgRequired();
+            color = Color.valueOf(Color.parseColor(arg));
+        } catch (IllegalArgumentException  e) {
+            getErrPrintWriter().println(
+                    "Error: 'reset' or color in #RRGGBB format should be provided as "
+                            + "an argument " + e);
+            return -1;
+        }
+        synchronized (mInternal.mGlobalLock) {
+            mLetterboxConfiguration.setLetterboxBackgroundColor(color);
+        }
+        return 0;
+    }
+
+    private int runSetLetterboxBackgroundWallpaperBlurRadius(PrintWriter pw)
+            throws RemoteException {
+        final int radius;
+        try {
+            String arg = getNextArgRequired();
+            radius = Integer.parseInt(arg);
+        } catch (NumberFormatException  e) {
+            getErrPrintWriter().println("Error: blur radius format " + e);
+            return -1;
+        } catch (IllegalArgumentException  e) {
+            getErrPrintWriter().println(
+                    "Error: 'reset' or blur radius should be provided as an argument " + e);
+            return -1;
+        }
+        synchronized (mInternal.mGlobalLock) {
+            mLetterboxConfiguration.setLetterboxBackgroundWallpaperBlurRadius(radius);
+        }
+        return 0;
+    }
+
+    private int runSetLetterboxBackgroundWallpaperDarkScrimAlpha(PrintWriter pw)
+            throws RemoteException {
+        final float alpha;
+        try {
+            String arg = getNextArgRequired();
+            alpha = Float.parseFloat(arg);
+        } catch (NumberFormatException  e) {
+            getErrPrintWriter().println("Error: bad alpha format " + e);
+            return -1;
+        } catch (IllegalArgumentException  e) {
+            getErrPrintWriter().println(
+                    "Error: 'reset' or alpha should be provided as an argument " + e);
+            return -1;
+        }
+        synchronized (mInternal.mGlobalLock) {
+            mLetterboxConfiguration.setLetterboxBackgroundWallpaperDarkScrimAlpha(alpha);
+        }
+        return 0;
+    }
+
+    private int runSeLetterboxHorizontalPositionMultiplier(PrintWriter pw) throws RemoteException {
+        final float multiplier;
+        try {
+            String arg = getNextArgRequired();
+            multiplier = Float.parseFloat(arg);
+        } catch (NumberFormatException  e) {
+            getErrPrintWriter().println("Error: bad multiplier format " + e);
+            return -1;
+        } catch (IllegalArgumentException  e) {
+            getErrPrintWriter().println(
+                    "Error: 'reset' or multiplier should be provided as an argument " + e);
+            return -1;
+        }
+        synchronized (mInternal.mGlobalLock) {
+            mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(multiplier);
+        }
+        return 0;
+    }
+
+    private int runSetLetterboxStyle(PrintWriter pw) throws RemoteException {
+        if (peekNextArg() == null) {
+            getErrPrintWriter().println("Error: No arguments provided.");
+        }
+        while (peekNextArg() != null) {
+            String arg = getNextArg();
+            switch (arg) {
+                case "--aspectRatio":
+                    runSetFixedOrientationLetterboxAspectRatio(pw);
+                    break;
+                case "--cornerRadius":
+                    runSetLetterboxActivityCornersRadius(pw);
+                    break;
+                case "--backgroundType":
+                    runSetLetterboxBackgroundType(pw);
+                    break;
+                case "--backgroundColor":
+                    runSetLetterboxBackgroundColor(pw);
+                    break;
+                case "--wallpaperBlurRadius":
+                    runSetLetterboxBackgroundWallpaperBlurRadius(pw);
+                    break;
+                case "--wallpaperDarkScrimAlpha":
+                    runSetLetterboxBackgroundWallpaperDarkScrimAlpha(pw);
+                    break;
+                case "--horizontalPositionMultiplier":
+                    runSeLetterboxHorizontalPositionMultiplier(pw);
+                    break;
+                default:
+                    getErrPrintWriter().println(
+                            "Error: Unrecognized letterbox style option: " + arg);
+                    return -1;
+            }
+        }
+        return 0;
+    }
+
+    private int runResetLetterboxStyle(PrintWriter pw) throws RemoteException {
+        if (peekNextArg() == null) {
+            resetLetterboxStyle();
+        }
+        synchronized (mInternal.mGlobalLock) {
+            while (peekNextArg() != null) {
+                String arg = getNextArg();
+                switch (arg) {
+                    case "aspectRatio":
+                        mLetterboxConfiguration.resetFixedOrientationLetterboxAspectRatio();
+                        break;
+                    case "cornerRadius":
+                        mLetterboxConfiguration.resetLetterboxActivityCornersRadius();
+                        break;
+                    case "backgroundType":
+                        mLetterboxConfiguration.resetLetterboxBackgroundType();
+                        break;
+                    case "backgroundColor":
+                        mLetterboxConfiguration.resetLetterboxBackgroundColor();
+                        break;
+                    case "wallpaperBlurRadius":
+                        mLetterboxConfiguration.resetLetterboxBackgroundWallpaperBlurRadius();
+                        break;
+                    case "wallpaperDarkScrimAlpha":
+                        mLetterboxConfiguration.resetLetterboxBackgroundWallpaperDarkScrimAlpha();
+                        break;
+                    case "horizontalPositionMultiplier":
+                        mLetterboxConfiguration.resetLetterboxHorizontalPositionMultiplier();
+                        break;
+                    default:
+                        getErrPrintWriter().println(
+                                "Error: Unrecognized letterbox style option: " + arg);
+                        return -1;
+                }
+            }
+        }
+        return 0;
+    }
+
     private int runSetMultiWindowConfig() {
         if (peekNextArg() == null) {
             getErrPrintWriter().println("Error: No arguments provided.");
@@ -622,6 +895,40 @@
         return 0;
     }
 
+    private void resetLetterboxStyle() {
+        synchronized (mInternal.mGlobalLock) {
+            mLetterboxConfiguration.resetFixedOrientationLetterboxAspectRatio();
+            mLetterboxConfiguration.resetLetterboxActivityCornersRadius();
+            mLetterboxConfiguration.resetLetterboxBackgroundType();
+            mLetterboxConfiguration.resetLetterboxBackgroundColor();
+            mLetterboxConfiguration.resetLetterboxBackgroundWallpaperBlurRadius();
+            mLetterboxConfiguration.resetLetterboxBackgroundWallpaperDarkScrimAlpha();
+            mLetterboxConfiguration.resetLetterboxHorizontalPositionMultiplier();
+        }
+    }
+
+    private int runGetLetterboxStyle(PrintWriter pw) throws RemoteException {
+        synchronized (mInternal.mGlobalLock) {
+            pw.println("Corner radius: "
+                    + mLetterboxConfiguration.getLetterboxActivityCornersRadius());
+            pw.println("Horizontal position multiplier: "
+                    + mLetterboxConfiguration.getLetterboxHorizontalPositionMultiplier());
+            pw.println("Aspect ratio: "
+                    + mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio());
+
+            pw.println("Background type: "
+                    + LetterboxConfiguration.letterboxBackgroundTypeToString(
+                            mLetterboxConfiguration.getLetterboxBackgroundType()));
+            pw.println("    Background color: " + Integer.toHexString(
+                    mLetterboxConfiguration.getLetterboxBackgroundColor().toArgb()));
+            pw.println("    Wallpaper blur radius: "
+                    + mLetterboxConfiguration.getLetterboxBackgroundWallpaperBlurRadius());
+            pw.println("    Wallpaper dark scrim alpha: "
+                    + mLetterboxConfiguration.getLetterboxBackgroundWallpaperDarkScrimAlpha());
+        }
+        return 0;
+    }
+
     private int runReset(PrintWriter pw) throws RemoteException {
         int displayId = getDisplayId(getNextArg());
 
@@ -646,6 +953,12 @@
         // set-ignore-orientation-request
         mInterface.setIgnoreOrientationRequest(displayId, false /* ignoreOrientationRequest */);
 
+        // set-letterbox-style
+        resetLetterboxStyle();
+
+        // set-sandbox-display-apis
+        mInternal.setSandboxDisplayApis(displayId, /* sandboxDisplayApis= */ true);
+
         // set-multi-window-config
         runResetMultiWindowConfig();
 
@@ -680,7 +993,12 @@
         pw.println("  set-ignore-orientation-request [-d DISPLAY_ID] [true|1|false|0]");
         pw.println("  get-ignore-orientation-request [-d DISPLAY_ID] ");
         pw.println("    If app requested orientation should be ignored.");
+        pw.println("  set-sandbox-display-apis [true|1|false|0]");
+        pw.println("    Sets override of Display APIs getRealSize / getRealMetrics to reflect ");
+        pw.println("    DisplayArea of the activity, or the window bounds if in letterbox or");
+        pw.println("    Size Compat Mode.");
 
+        printLetterboxHelp(pw);
         printMultiWindowConfigHelp(pw);
 
         pw.println("  reset [-d DISPLAY_ID]");
@@ -693,6 +1011,49 @@
         }
     }
 
+    private void printLetterboxHelp(PrintWriter pw) {
+        pw.println("  set-letterbox-style");
+        pw.println("    Sets letterbox style using the following options:");
+        pw.println("      --aspectRatio aspectRatio");
+        pw.println("        Aspect ratio of letterbox for fixed orientation. If aspectRatio <= "
+                + LetterboxConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO);
+        pw.println("        both it and R.dimen.config_fixedOrientationLetterboxAspectRatio will");
+        pw.println("        be ignored and framework implementation will determine aspect ratio.");
+        pw.println("      --cornerRadius radius");
+        pw.println("        Corners radius for activities in the letterbox mode. If radius < 0,");
+        pw.println("        both it and R.integer.config_letterboxActivityCornersRadius will be");
+        pw.println("        ignored and corners of the activity won't be rounded.");
+        pw.println("      --backgroundType [reset|solid_color|app_color_background");
+        pw.println("          |app_color_background_floating|wallpaper]");
+        pw.println("        Type of background used in the letterbox mode.");
+        pw.println("      --backgroundColor color");
+        pw.println("        Color of letterbox which is be used when letterbox background type");
+        pw.println("        is 'solid-color'. Use (set)get-letterbox-style to check and control");
+        pw.println("        letterbox background type. See Color#parseColor for allowed color");
+        pw.println("        formats (#RRGGBB and some colors by name, e.g. magenta or olive).");
+        pw.println("      --wallpaperBlurRadius radius");
+        pw.println("        Blur radius for 'wallpaper' letterbox background. If radius <= 0");
+        pw.println("        both it and R.dimen.config_letterboxBackgroundWallpaperBlurRadius");
+        pw.println("        are ignored and 0 is used.");
+        pw.println("      --wallpaperDarkScrimAlpha alpha");
+        pw.println("        Alpha of a black translucent scrim shown over 'wallpaper'");
+        pw.println("        letterbox background. If alpha < 0 or >= 1 both it and");
+        pw.println("        R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha are ignored");
+        pw.println("        and 0.0 (transparent) is used instead.");
+        pw.println("      --horizontalPositionMultiplier multiplier");
+        pw.println("        Horizontal position of app window center. If multiplier < 0 or > 1,");
+        pw.println("        both it and R.dimen.config_letterboxHorizontalPositionMultiplier");
+        pw.println("        are ignored and central position (0.5) is used.");
+        pw.println("  reset-letterbox-style [aspectRatio|cornerRadius|backgroundType");
+        pw.println("      |backgroundColor|wallpaperBlurRadius|wallpaperDarkScrimAlpha");
+        pw.println("      |horizontalPositionMultiplier]");
+        pw.println("    Resets overrides to default values for specified properties separated");
+        pw.println("    by space, e.g. 'reset-letterbox-style aspectRatio cornerRadius'.");
+        pw.println("    If no arguments provided, all values will be reset.");
+        pw.println("  get-letterbox-style");
+        pw.println("    Prints letterbox style configuration.");
+    }
+
     private void printMultiWindowConfigHelp(PrintWriter pw) {
         pw.println("  set-multi-window-config");
         pw.println("    Sets options to determine if activity should be shown in multi window:");
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index a82a478..eeb83c6 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -16,13 +16,19 @@
 
 package com.android.server.wm;
 
+import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_LAUNCH_TASK;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT_CHILDREN;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
 import static com.android.server.wm.ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED;
@@ -34,6 +40,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.WindowConfiguration;
+import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
 import android.graphics.Rect;
@@ -42,14 +49,17 @@
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.RemoteException;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Slog;
 import android.view.SurfaceControl;
 import android.window.IDisplayAreaOrganizerController;
+import android.window.ITaskFragmentOrganizerController;
 import android.window.ITaskOrganizerController;
 import android.window.ITransitionPlayer;
 import android.window.IWindowContainerTransactionCallback;
 import android.window.IWindowOrganizerController;
+import android.window.TaskFragmentCreationParams;
 import android.window.WindowContainerTransaction;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -95,14 +105,21 @@
 
     final TaskOrganizerController mTaskOrganizerController;
     final DisplayAreaOrganizerController mDisplayAreaOrganizerController;
+    final TaskFragmentOrganizerController mTaskFragmentOrganizerController;
 
     final TransitionController mTransitionController;
+    /**
+     * A Map which manages the relationship between
+     * {@link TaskFragmentCreationParams.mFragmentToken fragmentToken} and {@link TaskFragment}
+     */
+    private final ArrayMap<IBinder, TaskFragment> mLaunchTaskFragments = new ArrayMap<>();
 
     WindowOrganizerController(ActivityTaskManagerService atm) {
         mService = atm;
         mGlobalLock = atm.mGlobalLock;
         mTaskOrganizerController = new TaskOrganizerController(mService);
         mDisplayAreaOrganizerController = new DisplayAreaOrganizerController(mService);
+        mTaskFragmentOrganizerController = new TaskFragmentOrganizerController(atm);
         mTransitionController = new TransitionController(atm);
     }
 
@@ -126,10 +143,11 @@
         if (t == null) {
             throw new IllegalArgumentException("Null transaction passed to applySyncTransaction");
         }
+        final CallerInfo caller = new CallerInfo();
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
-                applyTransaction(t, -1 /*syncId*/, null /*transition*/);
+                applyTransaction(t, -1 /*syncId*/, null /*transition*/, caller);
             }
         } finally {
             Binder.restoreCallingIdentity(ident);
@@ -143,6 +161,7 @@
         if (t == null) {
             throw new IllegalArgumentException("Null transaction passed to applySyncTransaction");
         }
+        final CallerInfo caller = new CallerInfo();
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
@@ -162,7 +181,7 @@
                 if (callback != null) {
                     syncId = startSyncWithOrganizer(callback);
                 }
-                applyTransaction(t, syncId, null /*transition*/);
+                applyTransaction(t, syncId, null /*transition*/, caller);
                 if (syncId >= 0) {
                     setSyncReady(syncId);
                 }
@@ -177,6 +196,7 @@
     public IBinder startTransition(int type, @Nullable IBinder transitionToken,
             @Nullable WindowContainerTransaction t) {
         enforceTaskPermission("startTransition()");
+        final CallerInfo caller = new CallerInfo();
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
@@ -196,7 +216,7 @@
                             throw new IllegalArgumentException("Can't use legacy transitions in"
                                     + " compatibility mode with no WCT.");
                         }
-                        applyTransaction(t, -1 /* syncId */, null);
+                        applyTransaction(t, -1 /* syncId */, null, caller);
                         return null;
                     }
                     transition = mTransitionController.createTransition(type);
@@ -205,7 +225,7 @@
                 if (t == null) {
                     t = new WindowContainerTransaction();
                 }
-                applyTransaction(t, -1 /*syncId*/, transition);
+                applyTransaction(t, -1 /*syncId*/, transition, caller);
                 if (needsSetReady) {
                     transition.setReady();
                 }
@@ -221,6 +241,7 @@
             @Nullable WindowContainerTransaction t,
             @Nullable IWindowContainerTransactionCallback callback) {
         enforceTaskPermission("finishTransition()");
+        final CallerInfo caller = new CallerInfo();
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
@@ -231,7 +252,7 @@
                 // apply the incoming transaction before finish in case it alters the visibility
                 // of the participants.
                 if (t != null) {
-                    applyTransaction(t, syncId, null /*transition*/);
+                    applyTransaction(t, syncId, null /*transition*/, caller);
                 }
                 getTransitionController().finishTransition(transitionToken);
                 if (syncId >= 0) {
@@ -247,12 +268,14 @@
     /**
      * @param syncId If non-null, this will be a sync-transaction.
      * @param transition A transition to collect changes into.
+     * @param caller Info about the calling process.
      */
     private void applyTransaction(@NonNull WindowContainerTransaction t, int syncId,
-            @Nullable Transition transition) {
+            @Nullable Transition transition, @Nullable CallerInfo caller) {
         int effects = 0;
         ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Apply window transaction, syncId=%d", syncId);
         mService.deferWindowLayout();
+        mService.mTaskSupervisor.setDeferRootVisibilityUpdate(true /* deferUpdate */);
         try {
             if (transition != null) {
                 // First check if we have a display rotation transition and if so, update it.
@@ -303,7 +326,7 @@
                 final boolean isInLockTaskMode = mService.isInLockTaskMode();
                 for (int i = 0; i < hopSize; ++i) {
                     effects |= applyHierarchyOp(hops.get(i), effects, syncId, transition,
-                            isInLockTaskMode);
+                            isInLockTaskMode, caller);
                 }
             }
             // Queue-up bounds-change transactions for tasks which are now organized. Do
@@ -341,6 +364,7 @@
                 task.setMainWindowSizeChangeTransaction(sft);
             }
             if ((effects & TRANSACT_EFFECTS_LIFECYCLE) != 0) {
+                mService.mTaskSupervisor.setDeferRootVisibilityUpdate(false /* deferUpdate */);
                 // Already calls ensureActivityConfig
                 mService.mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
                 mService.mRootWindowContainer.resumeFocusedTasksTopActivities();
@@ -362,6 +386,7 @@
                 mService.addWindowLayoutReasons(LAYOUT_REASON_CONFIG_CHANGED);
             }
         } finally {
+            mService.mTaskSupervisor.setDeferRootVisibilityUpdate(false /* deferUpdate */);
             mService.continueWindowLayout();
         }
     }
@@ -458,7 +483,8 @@
     }
 
     private int applyHierarchyOp(WindowContainerTransaction.HierarchyOp hop, int effects,
-            int syncId, @Nullable Transition transition, boolean isInLockTaskMode) {
+            int syncId, @Nullable Transition transition, boolean isInLockTaskMode,
+            @Nullable CallerInfo caller) {
         final int type = hop.getType();
         switch (type) {
             case HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT: {
@@ -480,7 +506,7 @@
                 } else if (!task.mCreatedByOrganizer) {
                     throw new UnsupportedOperationException(
                             "Cannot set non-organized task as adjacent flag root: " + wc);
-                } else if (task.mAdjacentTask == null) {
+                } else if (task.getAdjacentTaskFragment() == null) {
                     throw new UnsupportedOperationException(
                             "Cannot set non-adjacent task as adjacent flag root: " + wc);
                 }
@@ -501,13 +527,15 @@
             return effects;
         }
 
+        final WindowContainer wc;
+        final IBinder fragmentToken;
         switch (type) {
             case HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT:
                 effects |= reparentChildrenTasksHierarchyOp(hop, transition, syncId);
                 break;
             case HIERARCHY_OP_TYPE_REORDER:
             case HIERARCHY_OP_TYPE_REPARENT:
-                final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer());
+                wc = WindowContainer.fromBinder(hop.getContainer());
                 if (wc == null || !wc.isAttached()) {
                     Slog.e(TAG, "Attempt to operate on detached container: " + wc);
                     break;
@@ -537,11 +565,68 @@
                 effects |= sanitizeAndApplyHierarchyOp(wc, hop);
                 break;
             case HIERARCHY_OP_TYPE_LAUNCH_TASK:
+                mService.mAmInternal.enforceCallingPermission(START_TASKS_FROM_RECENTS,
+                        "launchTask HierarchyOp");
                 final Bundle launchOpts = hop.getLaunchOptions();
                 final int taskId = launchOpts.getInt(
                         WindowContainerTransaction.HierarchyOp.LAUNCH_KEY_TASK_ID);
                 launchOpts.remove(WindowContainerTransaction.HierarchyOp.LAUNCH_KEY_TASK_ID);
-                mService.startActivityFromRecents(taskId, launchOpts);
+                final SafeActivityOptions safeOptions = caller != null
+                        ? SafeActivityOptions.fromBundle(launchOpts, caller.mPid, caller.mUid)
+                        : SafeActivityOptions.fromBundle(launchOpts);
+                mService.mTaskSupervisor.startActivityFromRecents(caller.mPid, caller.mUid,
+                        taskId, safeOptions);
+                break;
+            case HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT:
+                final TaskFragmentCreationParams taskFragmentCreationOptions =
+                        hop.getTaskFragmentCreationOptions();
+                createTaskFragment(taskFragmentCreationOptions);
+                break;
+            case HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT:
+                wc = WindowContainer.fromBinder(hop.getContainer());
+                if (wc == null || !wc.isAttached()) {
+                    Slog.e(TAG, "Attempt to operate on unknown or detached container: " + wc);
+                    break;
+                }
+                final TaskFragment taskFragment = wc.asTaskFragment();
+                if (taskFragment == null || taskFragment.asTask() != null) {
+                    throw new IllegalArgumentException(
+                            "Can only delete organized TaskFragment, but not Task.");
+                }
+                deleteTaskFragment(taskFragment);
+                break;
+            case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT:
+                fragmentToken = hop.getContainer();
+                if (!mLaunchTaskFragments.containsKey(fragmentToken)) {
+                    throw new IllegalArgumentException(
+                            "Not allowed to operate with invalid fragment token");
+                }
+                final Intent activityIntent = hop.getActivityIntent();
+                final Bundle activityOptions = hop.getLaunchOptions();
+                mService.getActivityStartController()
+                        .startActivityInTaskFragment(mLaunchTaskFragments.get(fragmentToken),
+                                activityIntent, activityOptions);
+                // TODO(b/189385246) : report the failure back to the organizer if the activity
+                // start failed
+                break;
+            case HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT:
+                fragmentToken = hop.getNewParent();
+                final ActivityRecord activity = ActivityRecord.forTokenLocked(hop.getContainer());
+                if (!mLaunchTaskFragments.containsKey(fragmentToken) || activity == null) {
+                    throw new IllegalArgumentException(
+                            "Not allowed to operate with invalid fragment token or activity.");
+                }
+                activity.reparent(mLaunchTaskFragments.get(fragmentToken), POSITION_TOP);
+                break;
+            case HIERARCHY_OP_TYPE_REPARENT_CHILDREN:
+                final WindowContainer oldParent = WindowContainer.fromBinder(hop.getContainer());
+                final WindowContainer newParent = WindowContainer.fromBinder(hop.getNewParent());
+                if (oldParent == null || !oldParent.isAttached()) {
+                    Slog.e(TAG, "Attempt to operate on unknown or detached container: "
+                            + oldParent);
+                    break;
+                }
+                reparentTaskFragment(oldParent, newParent);
                 break;
         }
         return effects;
@@ -704,13 +789,14 @@
     }
 
     private int setAdjacentRootsHierarchyOp(WindowContainerTransaction.HierarchyOp hop) {
-        final Task root1 = WindowContainer.fromBinder(hop.getContainer()).asTask();
-        final Task root2 = WindowContainer.fromBinder(hop.getAdjacentRoot()).asTask();
+        final TaskFragment root1 = WindowContainer.fromBinder(hop.getContainer()).asTaskFragment();
+        final TaskFragment root2 =
+                WindowContainer.fromBinder(hop.getAdjacentRoot()).asTaskFragment();
         if (!root1.mCreatedByOrganizer || !root2.mCreatedByOrganizer) {
             throw new IllegalArgumentException("setAdjacentRootsHierarchyOp: Not created by"
                     + " organizer root1=" + root1 + " root2=" + root2);
         }
-        root1.setAdjacentTask(root2);
+        root1.setAdjacentTaskFragment(root2);
         return TRANSACT_EFFECTS_LIFECYCLE;
     }
 
@@ -747,6 +833,11 @@
         return mDisplayAreaOrganizerController;
     }
 
+    @Override
+    public ITaskFragmentOrganizerController getTaskFragmentOrganizerController() {
+        return mTaskFragmentOrganizerController;
+    }
+
     @VisibleForTesting
     int startSyncWithOrganizer(IWindowContainerTransactionCallback callback) {
         int id = mService.mWindowManager.mSyncEngine.startSyncSet(this);
@@ -795,7 +886,80 @@
         }
     }
 
+    /** Whether the configuration changes are important to report back to an organizer. */
+    static boolean configurationsAreEqualForOrganizer(
+            Configuration newConfig, @Nullable Configuration oldConfig) {
+        if (oldConfig == null) {
+            return false;
+        }
+        int cfgChanges = newConfig.diff(oldConfig);
+        final int winCfgChanges = (cfgChanges & ActivityInfo.CONFIG_WINDOW_CONFIGURATION) != 0
+                ? (int) newConfig.windowConfiguration.diff(oldConfig.windowConfiguration,
+                true /* compareUndefined */) : 0;
+        if ((winCfgChanges & CONTROLLABLE_WINDOW_CONFIGS) == 0) {
+            cfgChanges &= ~ActivityInfo.CONFIG_WINDOW_CONFIGURATION;
+        }
+        return (cfgChanges & CONTROLLABLE_CONFIGS) == 0;
+    }
+
     private void enforceTaskPermission(String func) {
         mService.enforceTaskPermission(func);
     }
+
+    void createTaskFragment(@NonNull TaskFragmentCreationParams creationParams) {
+        final ActivityRecord ownerActivity =
+                ActivityRecord.forTokenLocked(creationParams.getOwnerToken());
+        if (ownerActivity == null || ownerActivity.getTask() == null) {
+            // TODO(b/189385246)  : report the failure back to the organizer
+            return;
+        }
+        // The ownerActivity has to belong to the same app as the root Activity of the target Task.
+        final ActivityRecord rootActivity = ownerActivity.getTask().getRootActivity();
+        if (rootActivity.getUid() != ownerActivity.getUid()) {
+            // TODO(b/189385246) : report the failure back to the organizer
+            return;
+        }
+        final TaskFragment taskFragment = new TaskFragment(mService,
+                creationParams.getFragmentToken(), true /* createdByOrganizer */);
+        ownerActivity.getTask().addChild(taskFragment, POSITION_TOP);
+        taskFragment.setWindowingMode(creationParams.getWindowingMode());
+        taskFragment.setBounds(creationParams.getInitialBounds());
+        taskFragment.setTaskFragmentOrganizer(creationParams.getOrganizer());
+        mLaunchTaskFragments.put(creationParams.getFragmentToken(), taskFragment);
+    }
+
+    void reparentTaskFragment(@NonNull WindowContainer oldParent,
+            @Nullable WindowContainer newParent) {
+        WindowContainer parent = newParent;
+        if (parent == null && oldParent.asTaskFragment() != null) {
+            parent = oldParent.asTaskFragment().getTask();
+        }
+        if (parent == null) {
+            // TODO(b/189385246)  : report the failure back to the organizer
+            return;
+        }
+        while (oldParent.hasChild()) {
+            oldParent.getChildAt(0).reparent(parent, POSITION_TOP);
+        }
+    }
+
+    void deleteTaskFragment(@NonNull TaskFragment taskFragment) {
+        final int index = mLaunchTaskFragments.indexOfValue(taskFragment);
+        if (index < 0) {
+            throw new IllegalArgumentException(
+                    "Not allowed to operate with invalid taskFragment");
+        }
+        mLaunchTaskFragments.removeAt(index);
+        taskFragment.removeImmediately();
+    }
+
+    static class CallerInfo {
+        final int mPid;
+        final int mUid;
+
+        CallerInfo() {
+            mPid = Binder.getCallingPid();
+            mUid = Binder.getCallingUid();
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 1364c72..65b065a 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -25,6 +25,13 @@
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
 import static com.android.internal.util.Preconditions.checkArgument;
 import static com.android.server.am.ActivityManagerService.MY_PID;
+import static com.android.server.wm.ActivityRecord.State.DESTROYED;
+import static com.android.server.wm.ActivityRecord.State.DESTROYING;
+import static com.android.server.wm.ActivityRecord.State.PAUSED;
+import static com.android.server.wm.ActivityRecord.State.PAUSING;
+import static com.android.server.wm.ActivityRecord.State.RESUMED;
+import static com.android.server.wm.ActivityRecord.State.STARTED;
+import static com.android.server.wm.ActivityRecord.State.STOPPING;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RELEASE;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_CONFIGURATION;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RELEASE;
@@ -32,13 +39,6 @@
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.ActivityTaskManagerService.INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT_MILLIS;
 import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
-import static com.android.server.wm.Task.ActivityState.DESTROYED;
-import static com.android.server.wm.Task.ActivityState.DESTROYING;
-import static com.android.server.wm.Task.ActivityState.PAUSED;
-import static com.android.server.wm.Task.ActivityState.PAUSING;
-import static com.android.server.wm.Task.ActivityState.RESUMED;
-import static com.android.server.wm.Task.ActivityState.STARTED;
-import static com.android.server.wm.Task.ActivityState.STOPPING;
 
 import android.Manifest;
 import android.annotation.NonNull;
@@ -75,6 +75,7 @@
 
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -219,6 +220,10 @@
     /** Whether our process is currently running a {@link IRemoteAnimationRunner} */
     private boolean mRunningRemoteAnimation;
 
+    /** List of "chained" processes that are running remote animations for this process */
+    private final ArrayList<WeakReference<WindowProcessController>> mRemoteAnimationDelegates =
+            new ArrayList<>();
+
     // The bits used for mActivityStateFlags.
     private static final int ACTIVITY_STATE_FLAG_IS_VISIBLE = 1 << 16;
     private static final int ACTIVITY_STATE_FLAG_IS_PAUSING_OR_PAUSED = 1 << 17;
@@ -735,10 +740,10 @@
         if (canUpdate) {
             // Make sure the previous top activity in the process no longer be resumed.
             if (mPreQTopResumedActivity != null && mPreQTopResumedActivity.isState(RESUMED)) {
-                final Task task = mPreQTopResumedActivity.getTask();
-                if (task != null) {
-                    boolean userLeaving = task.shouldBeVisible(null);
-                    task.startPausingLocked(userLeaving, false /* uiSleeping */,
+                final TaskFragment taskFrag = mPreQTopResumedActivity.getTaskFragment();
+                if (taskFrag != null) {
+                    boolean userLeaving = taskFrag.shouldBeVisible(null);
+                    taskFrag.startPausing(userLeaving, false /* uiSleeping */,
                             activity, "top-resumed-changed");
                 }
             }
@@ -991,7 +996,7 @@
         // Since there could be more than one activities in a process record, we don't need to
         // compute the OomAdj with each of them, just need to find out the activity with the
         // "best" state, the order would be visible, pausing, stopping...
-        Task.ActivityState bestInvisibleState = DESTROYED;
+        ActivityRecord.State bestInvisibleState = DESTROYED;
         boolean allStoppingFinishing = true;
         boolean visible = false;
         int minTaskLayer = Integer.MAX_VALUE;
@@ -1215,12 +1220,12 @@
                 hasVisibleActivities = true;
             }
 
-            final Task task = r.getTask();
-            if (task != null) {
+            final TaskFragment taskFragment = r.getTaskFragment();
+            if (taskFragment != null) {
                 // There may be a pausing activity that hasn't shown any window and was requested
                 // to be hidden. But pausing is also a visible state, it should be regarded as
                 // visible, so the caller can know the next activity should be resumed.
-                hasVisibleActivities |= task.handleAppDied(this);
+                hasVisibleActivities |= taskFragment.handleAppDied(this);
             }
             r.handleAppDied();
         }
@@ -1596,11 +1601,38 @@
         updateRunningRemoteOrRecentsAnimation();
     }
 
+    /**
+     * Marks another process as a "delegate" animator. This means that process is doing some part
+     * of a remote animation on behalf of this process.
+     */
+    void addRemoteAnimationDelegate(WindowProcessController delegate) {
+        if (!isRunningRemoteTransition()) {
+            throw new IllegalStateException("Can't add a delegate to a process which isn't itself"
+                    + " running a remote animation");
+        }
+        mRemoteAnimationDelegates.add(new WeakReference<>(delegate));
+    }
+
     void updateRunningRemoteOrRecentsAnimation() {
+        if (!isRunningRemoteTransition()) {
+            // Clean-up any delegates
+            for (int i = 0; i < mRemoteAnimationDelegates.size(); ++i) {
+                final WindowProcessController delegate = mRemoteAnimationDelegates.get(i).get();
+                if (delegate == null) continue;
+                delegate.setRunningRemoteAnimation(false);
+                delegate.setRunningRecentsAnimation(false);
+            }
+            mRemoteAnimationDelegates.clear();
+        }
+
         // Posting on handler so WM lock isn't held when we call into AM.
         mAtm.mH.sendMessage(PooledLambda.obtainMessage(
                 WindowProcessListener::setRunningRemoteAnimation, mListener,
-                mRunningRecentsAnimation || mRunningRemoteAnimation));
+                isRunningRemoteTransition()));
+    }
+
+    boolean isRunningRemoteTransition() {
+        return mRunningRecentsAnimation || mRunningRemoteAnimation;
     }
 
     /** Adjusts scheduling group for animation. This method MUST NOT be called inside WM lock. */
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index c3fc995..f829b0f 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -34,6 +34,7 @@
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.SurfaceControl.Transaction;
 import static android.view.SurfaceControl.getGlobalTransaction;
+import static android.view.ViewRootImpl.INSETS_LAYOUT_GENERALIZATION;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
@@ -202,6 +203,7 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.Region;
+import android.gui.TouchOcclusionMode;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Debug;
@@ -211,7 +213,6 @@
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.SystemClock;
-import android.os.TouchOcclusionMode;
 import android.os.Trace;
 import android.os.WorkSource;
 import android.provider.Settings;
@@ -874,6 +875,15 @@
     }
 
     /**
+     * Returns all the requested visibilities.
+     *
+     * @return an {@link InsetsState} as the requested visibilities.
+     */
+    InsetsState getRequestedState() {
+        return mRequestedInsetsState;
+    }
+
+    /**
      * @see #getRequestedVisibility(int)
      */
     void updateRequestedVisibility(InsetsState state) {
@@ -1259,8 +1269,8 @@
         frame.inset(left, top, right, bottom);
     }
 
-    void computeFrameAndUpdateSourceFrame() {
-        computeFrame();
+    void computeFrameAndUpdateSourceFrame(DisplayFrames displayFrames) {
+        computeFrame(displayFrames);
         // Update the source frame to provide insets to other windows during layout. If the
         // simulated frames exist, then this is not computing a stable result so just skip.
         if (mControllableInsetProvider != null && mSimulatedWindowFrames == null) {
@@ -1271,7 +1281,7 @@
     /**
      * Perform standard frame computation. The result can be obtained with getFrame() if so desired.
      */
-    void computeFrame() {
+    void computeFrame(DisplayFrames displayFrames) {
         if (mWillReplaceWindow && (mAnimatingExit || !mReplacingRemoveRequested)) {
             // This window is being replaced and either already got information that it's being
             // removed or we are still waiting for some information. Because of this we don't
@@ -1384,7 +1394,8 @@
         final int fw = windowFrames.mFrame.width();
         final int fh = windowFrames.mFrame.height();
 
-        applyGravityAndUpdateFrame(windowFrames, layoutContainingFrame, layoutDisplayFrame);
+        applyGravityAndUpdateFrame(windowFrames, layoutContainingFrame, layoutDisplayFrame,
+                displayFrames);
 
         if (mAttrs.type == TYPE_DOCK_DIVIDER) {
             if (!windowFrames.mFrame.equals(windowFrames.mLastFrame)) {
@@ -1477,6 +1488,18 @@
         return mAttrs;
     }
 
+    WindowManager.LayoutParams getLayoutingAttrs(int rotation) {
+        if (!INSETS_LAYOUT_GENERALIZATION) {
+            return mAttrs;
+        }
+        final WindowManager.LayoutParams[] paramsForRotation = mAttrs.paramsForRotation;
+        if (paramsForRotation == null || paramsForRotation.length != 4
+                || paramsForRotation[rotation] == null) {
+            return mAttrs;
+        }
+        return paramsForRotation[rotation];
+    }
+
     /** Retrieves the flags used to disable system UI functions. */
     int getDisableFlags() {
         return mDisableFlags;
@@ -1715,6 +1738,10 @@
         return mActivityRecord != null ? mActivityRecord.getTask() : null;
     }
 
+    @Nullable TaskFragment getTaskFragment() {
+        return mActivityRecord != null ? mActivityRecord.getTaskFragment() : null;
+    }
+
     @Nullable Task getRootTask() {
         final Task task = getTask();
         if (task != null) {
@@ -1842,9 +1869,8 @@
         return super.hasContentToDisplay();
     }
 
-    @Override
-    boolean isVisible() {
-        return wouldBeVisibleIfPolicyIgnored() && isVisibleByPolicy()
+    private boolean isVisibleByPolicyOrInsets() {
+        return isVisibleByPolicy()
                 // If we don't have a provider, this window isn't used as a window generating
                 // insets, so nobody can hide it over the inset APIs.
                 && (mControllableInsetProvider == null
@@ -1852,11 +1878,18 @@
     }
 
     @Override
+    boolean isVisible() {
+        return wouldBeVisibleIfPolicyIgnored() && isVisibleByPolicyOrInsets();
+    }
+
+    @Override
     boolean isVisibleRequested() {
-        if (shouldCheckTokenVisibleRequested()) {
-            return isVisible() && mToken.isVisibleRequested();
+        final boolean localVisibleRequested =
+                wouldBeVisibleRequestedIfPolicyIgnored() && isVisibleByPolicyOrInsets();
+        if (localVisibleRequested && shouldCheckTokenVisibleRequested()) {
+            return mToken.isVisibleRequested();
         }
-        return isVisible();
+        return localVisibleRequested;
     }
 
     /**
@@ -1903,6 +1936,16 @@
         return !isWallpaper || mToken.isVisible();
     }
 
+    private boolean wouldBeVisibleRequestedIfPolicyIgnored() {
+        final WindowState parent = getParentWindow();
+        final boolean isParentHiddenRequested = parent != null && !parent.isVisibleRequested();
+        if (isParentHiddenRequested || mAnimatingExit || mDestroying) {
+            return false;
+        }
+        final boolean isWallpaper = mToken.asWallpaperToken() != null;
+        return !isWallpaper || mToken.isVisibleRequested();
+    }
+
     /**
      * Is this window visible, ignoring its app token? It is not visible if there is no surface,
      * or we are in the process of running an exit animation that will remove the surface.
@@ -2878,9 +2921,14 @@
                 // means we need to intercept touches outside of that window. The dim layer
                 // user associated with the window (task or root task) will give us the good
                 // bounds, as they would be used to display the dim layer.
-                final Task task = getTask();
-                if (task != null) {
-                    task.getDimBounds(mTmpRect);
+                final TaskFragment taskFragment = getTaskFragment();
+                if (taskFragment != null) {
+                    final Task task = taskFragment.asTask();
+                    if (task != null) {
+                        task.getDimBounds(mTmpRect);
+                    } else {
+                        mTmpRect.set(taskFragment.getBounds());
+                    }
                 } else if (getRootTask() != null) {
                     getRootTask().getDimBounds(mTmpRect);
                 }
@@ -3863,7 +3911,7 @@
         final boolean forceRelayout = syncRedraw || reportOrientation || isDragResizeChanged();
         final DisplayContent displayContent = getDisplayContent();
         final boolean alwaysConsumeSystemBars =
-                displayContent.getDisplayPolicy().areSystemBarsForcedShownLw(this);
+                displayContent.getDisplayPolicy().areSystemBarsForcedShownLw();
         final int displayId = displayContent.getDisplayId();
 
         markRedrawForSyncReported();
@@ -4397,12 +4445,13 @@
     }
 
     private void applyGravityAndUpdateFrame(WindowFrames windowFrames, Rect containingFrame,
-            Rect displayFrame) {
+            Rect displayFrame, DisplayFrames displayFrames) {
         final int pw = containingFrame.width();
         final int ph = containingFrame.height();
         final Task task = getTask();
         final boolean inNonFullscreenContainer = !inAppWindowThatMatchesParentBounds();
-        final boolean noLimits = (mAttrs.flags & FLAG_LAYOUT_NO_LIMITS) != 0;
+        final WindowManager.LayoutParams attrs = getLayoutingAttrs(displayFrames.mRotation);
+        final boolean noLimits = (attrs.flags & FLAG_LAYOUT_NO_LIMITS) != 0;
 
         // We need to fit it to the display if either
         // a) The window is in a fullscreen container, or we don't have a task (we assume fullscreen
@@ -4412,49 +4461,54 @@
         // screen, but SurfaceViews want to be always at a specific location so we don't fit it to
         // the display.
         final boolean fitToDisplay = (task == null || !inNonFullscreenContainer)
-                || ((mAttrs.type != TYPE_BASE_APPLICATION) && !noLimits);
+                || ((attrs.type != TYPE_BASE_APPLICATION) && !noLimits);
         float x, y;
         int w,h;
 
         final boolean hasCompatScale = hasCompatScale();
-        if ((mAttrs.flags & FLAG_SCALED) != 0) {
-            if (mAttrs.width < 0) {
+        if ((attrs.flags & FLAG_SCALED) != 0 || mAttrs != attrs) {
+            // For the window with different layout attrs for different rotations, we need to avoid
+            // using requested size. Otherwise, when finishing a simulated rotation, the information
+            // coming from WindowManagerServices to the ViewRootImpl may not contain the correct
+            // value for the new rotation, and there will be a quick flash of wrong layout when the
+            // simulated activity faded out.
+            if (attrs.width < 0) {
                 w = pw;
             } else if (hasCompatScale) {
-                w = (int)(mAttrs.width * mGlobalScale + .5f);
+                w = (int) (attrs.width * mGlobalScale + .5f);
             } else {
-                w = mAttrs.width;
+                w = attrs.width;
             }
-            if (mAttrs.height < 0) {
+            if (attrs.height < 0) {
                 h = ph;
             } else if (hasCompatScale) {
-                h = (int)(mAttrs.height * mGlobalScale + .5f);
+                h = (int) (attrs.height * mGlobalScale + .5f);
             } else {
-                h = mAttrs.height;
+                h = attrs.height;
             }
         } else {
-            if (mAttrs.width == MATCH_PARENT) {
+            if (attrs.width == MATCH_PARENT) {
                 w = pw;
             } else if (hasCompatScale) {
-                w = (int)(mRequestedWidth * mGlobalScale + .5f);
+                w = (int) (mRequestedWidth * mGlobalScale + .5f);
             } else {
                 w = mRequestedWidth;
             }
-            if (mAttrs.height == MATCH_PARENT) {
+            if (attrs.height == MATCH_PARENT) {
                 h = ph;
             } else if (hasCompatScale) {
-                h = (int)(mRequestedHeight * mGlobalScale + .5f);
+                h = (int) (mRequestedHeight * mGlobalScale + .5f);
             } else {
                 h = mRequestedHeight;
             }
         }
 
         if (hasCompatScale) {
-            x = mAttrs.x * mGlobalScale;
-            y = mAttrs.y * mGlobalScale;
+            x = attrs.x * mGlobalScale;
+            y = attrs.y * mGlobalScale;
         } else {
-            x = mAttrs.x;
-            y = mAttrs.y;
+            x = attrs.x;
+            y = attrs.y;
         }
 
         if (inNonFullscreenContainer && !layoutInParentFrame()) {
@@ -4481,13 +4535,12 @@
         }
 
         // Set mFrame
-        Gravity.apply(mAttrs.gravity, w, h, containingFrame,
-                (int) (x + mAttrs.horizontalMargin * pw),
-                (int) (y + mAttrs.verticalMargin * ph), windowFrames.mFrame);
-
+        Gravity.apply(attrs.gravity, w, h, containingFrame,
+                (int) (x + attrs.horizontalMargin * pw),
+                (int) (y + attrs.verticalMargin * ph), windowFrames.mFrame);
         // Now make sure the window fits in the overall display frame.
         if (fitToDisplay) {
-            Gravity.applyDisplay(mAttrs.gravity, displayFrame, windowFrames.mFrame);
+            Gravity.applyDisplay(attrs.gravity, displayFrame, windowFrames.mFrame);
         }
 
         // We need to make sure we update the CompatFrame as it is used for
@@ -5949,6 +6002,13 @@
             return mWinAnimator.finishDrawingLocked(postDrawTransaction);
         }
 
+        if (mActivityRecord != null
+                && mWmService.mAtmService.getTransitionController().isShellTransitionsEnabled()
+                && mAttrs.type == TYPE_APPLICATION_STARTING) {
+            mWmService.mAtmService.mTaskSupervisor.getActivityMetricsLogger()
+                    .notifyStartingWindowDrawn(mActivityRecord);
+        }
+
         if (postDrawTransaction != null) {
             mSyncTransaction.merge(postDrawTransaction);
         }
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index fbfa400..fa32be3 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -16,9 +16,11 @@
 
 package com.android.server.wm;
 
+import static android.view.ViewRootImpl.INSETS_LAYOUT_GENERALIZATION;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
@@ -229,6 +231,11 @@
             ProtoLog.w(WM_DEBUG_WINDOW_MOVEMENT,
                     "removeAllWindowsIfPossible: removing win=%s", win);
             win.removeIfPossible();
+            if (i > mChildren.size()) {
+                // It's possible for removeIfPossible to delete siblings (for example if it is a
+                // starting window, it will perform operations on the ActivityRecord).
+                i = mChildren.size();
+            }
         }
     }
 
@@ -451,9 +458,24 @@
     }
 
     Rect getFixedRotationBarContentFrame(int windowType) {
-        return isFixedRotationTransforming()
-                ? mFixedRotationTransformState.mBarContentFrames.get(windowType)
-                : null;
+        if (!isFixedRotationTransforming()) {
+            return null;
+        }
+        if (!INSETS_LAYOUT_GENERALIZATION) {
+            return mFixedRotationTransformState.mBarContentFrames.get(windowType);
+        }
+        final DisplayFrames displayFrames = mFixedRotationTransformState.mDisplayFrames;
+        final Rect tmpRect = new Rect();
+        if (windowType == TYPE_NAVIGATION_BAR) {
+            tmpRect.set(displayFrames.mInsetsState.getSource(InsetsState.ITYPE_NAVIGATION_BAR)
+                    .getFrame());
+        }
+        if (windowType == TYPE_STATUS_BAR) {
+            tmpRect.set(displayFrames.mInsetsState.getSource(InsetsState.ITYPE_STATUS_BAR)
+                    .getFrame());
+        }
+        tmpRect.intersect(displayFrames.mDisplayCutoutSafe);
+        return tmpRect;
     }
 
     InsetsState getFixedRotationTransformInsetsState() {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index aca7cc9..5086060 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -378,6 +378,8 @@
             "com.android.server.connectivity.IpConnectivityMetrics";
     private static final String MEDIA_COMMUNICATION_SERVICE_CLASS =
             "com.android.server.media.MediaCommunicationService";
+    private static final String APP_COMPAT_OVERRIDES_SERVICE_CLASS =
+            "com.android.server.compat.overrides.AppCompatOverridesService$Lifecycle";
 
     private static final String ROLE_SERVICE_CLASS = "com.android.role.RoleService";
     private static final String GAME_MANAGER_SERVICE_CLASS =
@@ -2655,6 +2657,10 @@
         mSystemServiceManager.startService(MEDIA_COMMUNICATION_SERVICE_CLASS);
         t.traceEnd();
 
+        t.traceBegin("AppCompatOverridesService");
+        mSystemServiceManager.startService(APP_COMPAT_OVERRIDES_SERVICE_CLASS);
+        t.traceEnd();
+
         ConcurrentUtils.waitForFutureNoInterrupt(mBlobStoreServiceStart,
                 START_BLOB_STORE_SERVICE);
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/compat/overrides/AppCompatOverridesParserTest.java b/services/tests/mockingservicestests/src/com/android/server/compat/overrides/AppCompatOverridesParserTest.java
new file mode 100644
index 0000000..bf97042
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/compat/overrides/AppCompatOverridesParserTest.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.compat.overrides;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.when;
+
+import static java.util.Collections.emptySet;
+
+import android.app.compat.PackageOverride;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.platform.test.annotations.Presubmit;
+import android.util.ArraySet;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.compat.overrides.AppCompatOverridesParser.PackageOverrides;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Test class for {@link AppCompatOverridesParser}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksMockingServicesTests:AppCompatOverridesParserTest
+ */
+@RunWith(MockitoJUnitRunner.class)
+@SmallTest
+@Presubmit
+public class AppCompatOverridesParserTest {
+    private static final String PACKAGE_1 = "com.android.test1";
+    private static final String PACKAGE_2 = "com.android.test2";
+    private static final String PACKAGE_3 = "com.android.test3";
+    private static final String PACKAGE_4 = "com.android.test4";
+
+    private AppCompatOverridesParser mParser;
+
+    @Mock
+    private PackageManager mPackageManager;
+
+    @Before
+    public void setUp() throws Exception {
+        mParser = new AppCompatOverridesParser(mPackageManager);
+    }
+
+    @Test
+    public void parseRemoveOverrides_emptyConfig_returnsEmpty() {
+        Set<Long> ownedChangeIds = new ArraySet<>(Arrays.asList(123L, 456L));
+
+        assertThat(mParser.parseRemoveOverrides("", ownedChangeIds)).isEmpty();
+    }
+
+    @Test
+    public void parseRemoveOverrides_configHasWildcardNoOwnedChangeIds_returnsEmpty() {
+        when(mPackageManager.getInstalledApplications(anyInt()))
+                .thenReturn(Arrays.asList(createAppInfo(PACKAGE_1), createAppInfo(PACKAGE_2)));
+
+        assertThat(mParser.parseRemoveOverrides("*", /* ownedChangeIds= */ emptySet())).isEmpty();
+    }
+
+    @Test
+    public void parseRemoveOverrides_configHasWildcard_returnsAllInstalledPackagesToAllOwnedIds() {
+        Set<Long> ownedChangeIds = new ArraySet<>(Arrays.asList(123L, 456L));
+        when(mPackageManager.getInstalledApplications(anyInt()))
+                .thenReturn(Arrays.asList(createAppInfo(PACKAGE_1), createAppInfo(PACKAGE_2),
+                        createAppInfo(PACKAGE_3)));
+
+        Map<String, Set<Long>> result = mParser.parseRemoveOverrides("*", ownedChangeIds);
+
+        assertThat(result).hasSize(3);
+        assertThat(result.get(PACKAGE_1)).containsExactly(123L, 456L);
+        assertThat(result.get(PACKAGE_2)).containsExactly(123L, 456L);
+        assertThat(result.get(PACKAGE_3)).containsExactly(123L, 456L);
+    }
+
+    @Test
+    public void parseRemoveOverrides_configHasInvalidWildcardSymbol_returnsEmpty() {
+        Set<Long> ownedChangeIds = new ArraySet<>(Arrays.asList(123L, 456L));
+        when(mPackageManager.getInstalledApplications(anyInt())).thenReturn(
+                Arrays.asList(createAppInfo(PACKAGE_1), createAppInfo(PACKAGE_2)));
+
+        assertThat(mParser.parseRemoveOverrides("**", ownedChangeIds)).isEmpty();
+    }
+
+    @Test
+    public void parseRemoveOverrides_configHasSingleEntry_returnsPackageToChangeIds() {
+        Map<String, Set<Long>> result = mParser.parseRemoveOverrides(
+                PACKAGE_1 + "=12:34", /* ownedChangeIds= */ emptySet());
+
+        assertThat(result).hasSize(1);
+        assertThat(result.get(PACKAGE_1)).containsExactly(12L, 34L);
+    }
+
+    @Test
+    public void parseRemoveOverrides_configHasMultipleEntries_returnsPackagesToChangeIds() {
+        Set<Long> ownedChangeIds = new ArraySet<>(Arrays.asList(12L, 34L, 56L, 78L));
+
+        Map<String, Set<Long>> result = mParser.parseRemoveOverrides(
+                PACKAGE_1 + "=12," + PACKAGE_2 + "=*," + PACKAGE_3 + "=12:56:78," + PACKAGE_4
+                        + "=", ownedChangeIds);
+
+        assertThat(result).hasSize(3);
+        assertThat(result.get(PACKAGE_1)).containsExactly(12L);
+        assertThat(result.get(PACKAGE_2)).containsExactly(12L, 34L, 56L, 78L);
+        assertThat(result.get(PACKAGE_3)).containsExactly(12L, 56L, 78L);
+    }
+
+    @Test
+    public void parseRemoveOverrides_configHasPackageWithWildcardNoOwnedId_returnsWithoutPackage() {
+        Map<String, Set<Long>> result = mParser.parseRemoveOverrides(
+                PACKAGE_1 + "=*," + PACKAGE_2 + "=12", /* ownedChangeIds= */ emptySet());
+
+        assertThat(result).hasSize(1);
+        assertThat(result.get(PACKAGE_2)).containsExactly(12L);
+    }
+
+    @Test
+    public void parseRemoveOverrides_configHasInvalidKeyValueListFormat_returnsEmpty() {
+        Set<Long> ownedChangeIds = new ArraySet<>(Arrays.asList(12L, 34L));
+
+        assertThat(mParser.parseRemoveOverrides(
+                PACKAGE_1 + "=12," + PACKAGE_2 + ">34", ownedChangeIds)).isEmpty();
+    }
+
+
+    @Test
+    public void parseRemoveOverrides_configHasInvalidChangeIds_returnsWithoutInvalidChangeIds() {
+        Map<String, Set<Long>> result = mParser.parseRemoveOverrides(
+                PACKAGE_1 + "=12," + PACKAGE_2 + "=12:56L:78," + PACKAGE_3
+                        + "=34L", /* ownedChangeIds= */ emptySet());
+
+        assertThat(result).hasSize(2);
+        assertThat(result.get(PACKAGE_1)).containsExactly(12L);
+        assertThat(result.get(PACKAGE_2)).containsExactly(12L, 78L);
+    }
+
+    @Test
+    public void parseOwnedChangeIds_emptyConfig_returnsEmpty() {
+        assertThat(AppCompatOverridesParser.parseOwnedChangeIds("")).isEmpty();
+    }
+
+    @Test
+    public void parseOwnedChangeIds_configHasSingleChangeId_returnsChangeId() {
+        assertThat(AppCompatOverridesParser.parseOwnedChangeIds("123")).containsExactly(123L);
+    }
+
+    @Test
+    public void parseOwnedChangeIds_configHasMultipleChangeIds_returnsChangeIds() {
+        assertThat(AppCompatOverridesParser.parseOwnedChangeIds("12,34,56")).containsExactly(12L,
+                34L, 56L);
+    }
+
+    @Test
+    public void parseOwnedChangeIds_configHasInvalidChangeIds_returnsWithoutInvalidChangeIds() {
+        // We add a valid entry before and after the invalid ones to make sure they are applied.
+        assertThat(AppCompatOverridesParser.parseOwnedChangeIds("12,C34,56")).containsExactly(12L,
+                56L);
+    }
+
+    @Test
+    public void parsePackageOverrides_emptyConfig_returnsEmpty() {
+        PackageOverrides result = AppCompatOverridesParser.parsePackageOverrides(/* configStr= */
+                "", /* versionCode= */ 0, /* changeIdsToSkip= */ emptySet());
+
+        assertThat(result.overridesToAdd).isEmpty();
+        assertThat(result.overridesToRemove).isEmpty();
+    }
+
+    @Test
+    public void parsePackageOverrides_configWithSingleOverride_returnsOverride() {
+        PackageOverrides result = AppCompatOverridesParser.parsePackageOverrides(/* configStr= */
+                "123:::true", /* versionCode= */ 5, /* changeIdsToSkip= */ emptySet());
+
+        assertThat(result.overridesToAdd).hasSize(1);
+        assertThat(result.overridesToAdd.get(123L)).isEqualTo(
+                new PackageOverride.Builder().setEnabled(true).build());
+    }
+
+    @Test
+    public void parsePackageOverrides_configWithMultipleOverridesToAdd_returnsOverrides() {
+        PackageOverrides result = AppCompatOverridesParser.parsePackageOverrides(/* configStr= */
+                "910:3:4:false,78:10::false,12:::false,34:1:2:true,34:10::true,56::2:true,"
+                        + "56:3:4:false,34:4:8:true,78:6:7:true,910:5::true,1112::5:true,"
+                        + "56:6::true,1112:6:7:false", /* versionCode= */
+                5, /* changeIdsToSkip= */ emptySet());
+
+        assertThat(result.overridesToAdd).hasSize(6);
+        assertThat(result.overridesToAdd.get(12L)).isEqualTo(
+                new PackageOverride.Builder().setEnabled(false).build());
+        assertThat(result.overridesToAdd.get(34L)).isEqualTo(
+                new PackageOverride.Builder().setMinVersionCode(4).setMaxVersionCode(8).setEnabled(
+                        true).build());
+        assertThat(result.overridesToAdd.get(56L)).isEqualTo(
+                new PackageOverride.Builder().setMinVersionCode(3).setMaxVersionCode(4).setEnabled(
+                        false).build());
+        assertThat(result.overridesToAdd.get(78L)).isEqualTo(
+                new PackageOverride.Builder().setMinVersionCode(6).setMaxVersionCode(7).setEnabled(
+                        true).build());
+        assertThat(result.overridesToAdd.get(910L)).isEqualTo(
+                new PackageOverride.Builder().setMinVersionCode(5).setEnabled(true).build());
+        assertThat(result.overridesToAdd.get(1112L)).isEqualTo(
+                new PackageOverride.Builder().setMaxVersionCode(5).setEnabled(true).build());
+        assertThat(result.overridesToRemove).isEmpty();
+    }
+
+    @Test
+    public void parsePackageOverrides_configWithMultipleOverridesToRemove_returnsOverrides() {
+        PackageOverrides result = AppCompatOverridesParser.parsePackageOverrides(/* configStr= */
+                "12:::,34:1:2:", /* versionCode= */ 5, /* changeIdsToSkip= */ emptySet());
+
+        assertThat(result.overridesToRemove).containsExactly(12L, 34L);
+        assertThat(result.overridesToAdd).isEmpty();
+    }
+
+    @Test
+    public void parsePackageOverrides_configWithBothOverridesToAddAndRemove_returnsOverrides() {
+        // Note that change 56 is both added and removed, therefore it will only be removed.
+        PackageOverrides result = AppCompatOverridesParser.parsePackageOverrides(/* configStr= */
+                "56:::,12:::true,34:::,56:3:7:true", /* versionCode= */ 5, /* changeIdsToSkip= */
+                emptySet());
+
+        assertThat(result.overridesToAdd).hasSize(1);
+        assertThat(result.overridesToAdd.get(12L)).isEqualTo(
+                new PackageOverride.Builder().setEnabled(true).build());
+        assertThat(result.overridesToRemove).containsExactly(34L, 56L);
+    }
+
+    @Test
+    public void parsePackageOverrides_changeIdsToSkipSpecified_returnsWithoutChangeIdsToSkip() {
+        ArraySet<Long> changeIdsToSkip = new ArraySet<>();
+        changeIdsToSkip.add(34L);
+        changeIdsToSkip.add(56L);
+        changeIdsToSkip.add(910L);
+        PackageOverrides result = AppCompatOverridesParser.parsePackageOverrides(/* configStr= */
+                "12:::true,34:::,56:3:7:true,78:::", /* versionCode= */ 5, changeIdsToSkip);
+
+        assertThat(result.overridesToAdd).hasSize(1);
+        assertThat(result.overridesToAdd.get(12L)).isEqualTo(
+                new PackageOverride.Builder().setEnabled(true).build());
+        assertThat(result.overridesToRemove).containsExactly(78L);
+    }
+
+    @Test
+    public void parsePackageOverrides_changeIdsToSkipContainsAllIds_returnsEmpty() {
+        ArraySet<Long> changeIdsToSkip = new ArraySet<>();
+        changeIdsToSkip.add(12L);
+        changeIdsToSkip.add(34L);
+        PackageOverrides result = AppCompatOverridesParser.parsePackageOverrides(/* configStr= */
+                "12:::true,34:::", /* versionCode= */ 5, changeIdsToSkip);
+
+        assertThat(result.overridesToAdd).isEmpty();
+        assertThat(result.overridesToRemove).isEmpty();
+    }
+
+    @Test
+    public void parsePackageOverrides_someOverridesAreInvalid_returnsWithoutInvalidOverrides() {
+        // We add a valid entry before and after the invalid ones to make sure they are applied.
+        PackageOverrides result = AppCompatOverridesParser.parsePackageOverrides(/* configStr= */
+                "12:::True,56:1:2:FALSE,56:3:true,78:4:8:true:,C1:::true,910:::no,"
+                        + "1112:1:ten:true,1112:one:10:true,,1314:7:3:false,34:one:ten:",
+                /* versionCode= */ 5, /* changeIdsToSkip= */ emptySet());
+
+        assertThat(result.overridesToAdd).hasSize(2);
+        assertThat(result.overridesToAdd.get(12L)).isEqualTo(
+                new PackageOverride.Builder().setEnabled(true).build());
+        assertThat(result.overridesToAdd.get(56L)).isEqualTo(
+                new PackageOverride.Builder().setMinVersionCode(1).setMaxVersionCode(2).setEnabled(
+                        false).build());
+        assertThat(result.overridesToRemove).containsExactly(34L);
+    }
+
+    private static ApplicationInfo createAppInfo(String packageName) {
+        ApplicationInfo appInfo = new ApplicationInfo();
+        appInfo.packageName = packageName;
+        return appInfo;
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/compat/overrides/AppCompatOverridesServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/compat/overrides/AppCompatOverridesServiceTest.java
new file mode 100644
index 0000000..d3f04f3
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/compat/overrides/AppCompatOverridesServiceTest.java
@@ -0,0 +1,380 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.compat.overrides;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.compat.overrides.AppCompatOverridesParser.FLAG_OWNED_CHANGE_IDS;
+import static com.android.server.compat.overrides.AppCompatOverridesParser.FLAG_REMOVE_OVERRIDES;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.when;
+
+import android.app.compat.PackageOverride;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.provider.DeviceConfig;
+import android.provider.DeviceConfig.Properties;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.compat.CompatibilityOverrideConfig;
+import com.android.internal.compat.CompatibilityOverridesToRemoveConfig;
+import com.android.internal.compat.IPlatformCompat;
+import com.android.server.testables.TestableDeviceConfig.TestableDeviceConfigRule;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/**
+ * Test class for {@link AppCompatOverridesService}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksMockingServicesTests:AppCompatOverridesServiceTest
+ */
+@RunWith(MockitoJUnitRunner.class)
+@SmallTest
+@Presubmit
+public class AppCompatOverridesServiceTest {
+    private static final String NAMESPACE_1 = "namespace_1";
+    private static final List<String> SUPPORTED_NAMESPACES = Arrays.asList(NAMESPACE_1);
+
+    private static final String PACKAGE_1 = "com.android.test1";
+    private static final String PACKAGE_2 = "com.android.test2";
+    private static final String PACKAGE_3 = "com.android.test3";
+    private static final String PACKAGE_4 = "com.android.test4";
+    private static final String PACKAGE_5 = "com.android.test5";
+
+    private MockContext mMockContext;
+    private AppCompatOverridesService mService;
+
+    @Mock
+    private PackageManager mPackageManager;
+    @Mock
+    private IPlatformCompat mPlatformCompat;
+
+    @Captor
+    private ArgumentCaptor<CompatibilityOverrideConfig> mOverridesToAddConfigCaptor;
+    @Captor
+    private ArgumentCaptor<CompatibilityOverridesToRemoveConfig> mOverridesToRemoveConfigCaptor;
+
+    @Rule
+    public TestableDeviceConfigRule mDeviceConfigRule = new TestableDeviceConfigRule();
+
+    class MockContext extends ContextWrapper {
+        MockContext(Context base) {
+            super(base);
+        }
+
+        @Override
+        public PackageManager getPackageManager() {
+            return mPackageManager;
+        }
+
+        @Override
+        public Executor getMainExecutor() {
+            // Run on current thread
+            return Runnable::run;
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mMockContext = new MockContext(
+                InstrumentationRegistry.getInstrumentation().getTargetContext());
+        mService = new AppCompatOverridesService(mMockContext, mPlatformCompat,
+                SUPPORTED_NAMESPACES);
+    }
+
+    @Test
+    public void onPropertiesChanged_removeOverridesFlagNotSet_appliesPackageOverrides()
+            throws Exception {
+        mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 3);
+        mockGetApplicationInfoNotInstalled(PACKAGE_2);
+        mockGetApplicationInfo(PACKAGE_3, /* versionCode= */ 10);
+        mockGetApplicationInfo(PACKAGE_4, /* versionCode= */ 1);
+        mockGetApplicationInfo(PACKAGE_5, /* versionCode= */ 1);
+
+        mService.registerDeviceConfigListeners();
+        DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1)
+                .setString(PACKAGE_1, "123:::true,456::1:false,456:2::true")
+                .setString(PACKAGE_2, "123:::true")
+                .setString(PACKAGE_3, "123:1:9:true,123:10:11:false,123:11::true,456:::")
+                .setString(PACKAGE_4, "")
+                .setString(PACKAGE_5, "123:::,789:::")
+                .setString(FLAG_OWNED_CHANGE_IDS, "123,456,789").build());
+
+        Map<Long, PackageOverride> addedOverrides;
+        // Package 1
+        verify(mPlatformCompat).putOverridesOnReleaseBuilds(mOverridesToAddConfigCaptor.capture(),
+                eq(PACKAGE_1));
+        verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds(
+                any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_1));
+        addedOverrides = mOverridesToAddConfigCaptor.getValue().overrides;
+        assertThat(addedOverrides).hasSize(2);
+        assertThat(addedOverrides.get(123L)).isEqualTo(
+                new PackageOverride.Builder().setEnabled(true).build());
+        assertThat(addedOverrides.get(456L)).isEqualTo(
+                new PackageOverride.Builder().setMinVersionCode(2).setEnabled(true).build());
+        // Package 2
+        verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds(
+                any(CompatibilityOverrideConfig.class), eq(PACKAGE_2));
+        verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds(
+                any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_2));
+        // Package 3
+        verify(mPlatformCompat).putOverridesOnReleaseBuilds(mOverridesToAddConfigCaptor.capture(),
+                eq(PACKAGE_3));
+        verify(mPlatformCompat).removeOverridesOnReleaseBuilds(
+                mOverridesToRemoveConfigCaptor.capture(), eq(PACKAGE_3));
+        addedOverrides = mOverridesToAddConfigCaptor.getValue().overrides;
+        assertThat(addedOverrides).hasSize(1);
+        assertThat(addedOverrides.get(123L)).isEqualTo(
+                new PackageOverride.Builder().setMinVersionCode(10).setMaxVersionCode(
+                        11).setEnabled(false).build());
+        assertThat(mOverridesToRemoveConfigCaptor.getValue().changeIds).containsExactly(456L);
+        // Package 4
+        verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds(
+                any(CompatibilityOverrideConfig.class), eq(PACKAGE_4));
+        verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds(
+                any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_4));
+        // Package 5
+        verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds(
+                any(CompatibilityOverrideConfig.class), eq(PACKAGE_5));
+        verify(mPlatformCompat).removeOverridesOnReleaseBuilds(
+                mOverridesToRemoveConfigCaptor.capture(), eq(PACKAGE_5));
+        assertThat(mOverridesToRemoveConfigCaptor.getValue().changeIds).containsExactly(123L, 789L);
+    }
+
+    @Test
+    public void onPropertiesChanged_removeOverridesFlagSetBefore_skipsOverridesToRemove()
+            throws Exception {
+        DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1)
+                .setString(FLAG_REMOVE_OVERRIDES, PACKAGE_1 + "=123:456," + PACKAGE_2 + "=123")
+                .setString(PACKAGE_1, "123:::true")
+                .setString(PACKAGE_4, "123:::true").build());
+        mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 0);
+        mockGetApplicationInfo(PACKAGE_2, /* versionCode= */ 0);
+        mockGetApplicationInfo(PACKAGE_3, /* versionCode= */ 0);
+
+        mService.registerDeviceConfigListeners();
+        DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1)
+                .setString(PACKAGE_1, "123:::true,456:::,789:::false")
+                .setString(PACKAGE_2, "123:::true")
+                .setString(PACKAGE_3, "456:::true").build());
+
+        // Package 1
+        verify(mPlatformCompat).putOverridesOnReleaseBuilds(mOverridesToAddConfigCaptor.capture(),
+                eq(PACKAGE_1));
+        verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds(
+                any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_1));
+        assertThat(mOverridesToAddConfigCaptor.getValue().overrides.keySet()).containsExactly(789L);
+        // Package 2
+        verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds(
+                any(CompatibilityOverrideConfig.class), eq(PACKAGE_2));
+        verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds(
+                any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_2));
+        // Package 3
+        verify(mPlatformCompat).putOverridesOnReleaseBuilds(mOverridesToAddConfigCaptor.capture(),
+                eq(PACKAGE_3));
+        verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds(
+                any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_3));
+        assertThat(mOverridesToAddConfigCaptor.getValue().overrides.keySet()).containsExactly(456L);
+        // Package 4 (not applied because it hasn't changed after the listener was added)
+        verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds(
+                any(CompatibilityOverrideConfig.class), eq(PACKAGE_4));
+        verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds(
+                any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_4));
+    }
+
+    @Test
+    public void onPropertiesChanged_removeOverridesFlagChangedNoPackageOverridesFlags_removesOnly()
+            throws Exception {
+        mService.registerDeviceConfigListeners();
+        DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1)
+                .setString(FLAG_REMOVE_OVERRIDES,
+                        PACKAGE_1 + "=123:456," + PACKAGE_2 + "=789").build());
+
+        // Package 1
+        verify(mPlatformCompat).removeOverridesOnReleaseBuilds(
+                mOverridesToRemoveConfigCaptor.capture(), eq(PACKAGE_1));
+        assertThat(mOverridesToRemoveConfigCaptor.getValue().changeIds).containsExactly(123L, 456L);
+        // Package 2
+        verify(mPlatformCompat).removeOverridesOnReleaseBuilds(
+                mOverridesToRemoveConfigCaptor.capture(), eq(PACKAGE_2));
+        assertThat(mOverridesToRemoveConfigCaptor.getValue().changeIds).containsExactly(789L);
+    }
+
+    @Test
+    public void onPropertiesChanged_removeOverridesFlagAndSomePackageOverrideFlagsChanged_ok()
+            throws Exception {
+        DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1)
+                .setString(FLAG_REMOVE_OVERRIDES, PACKAGE_1 + "=123:456")
+                .setString(PACKAGE_1, "123:::true,456:::,789:::false")
+                .setString(PACKAGE_3, "456:::false,789:::true").build());
+        mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 0);
+        mockGetApplicationInfo(PACKAGE_2, /* versionCode= */ 0);
+        mockGetApplicationInfo(PACKAGE_3, /* versionCode= */ 0);
+
+        mService.registerDeviceConfigListeners();
+        DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1)
+                .setString(FLAG_REMOVE_OVERRIDES, PACKAGE_2 + "=123," + PACKAGE_3 + "=789")
+                .setString(PACKAGE_2, "123:::true,456:::").build());
+
+        // Package 1
+        verify(mPlatformCompat).putOverridesOnReleaseBuilds(mOverridesToAddConfigCaptor.capture(),
+                eq(PACKAGE_1));
+        verify(mPlatformCompat).removeOverridesOnReleaseBuilds(
+                mOverridesToRemoveConfigCaptor.capture(), eq(PACKAGE_1));
+        assertThat(mOverridesToAddConfigCaptor.getValue().overrides.keySet()).containsExactly(123L,
+                789L);
+        assertThat(mOverridesToRemoveConfigCaptor.getValue().changeIds).containsExactly(456L);
+        // Package 2
+        verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds(
+                any(CompatibilityOverrideConfig.class), eq(PACKAGE_2));
+        verify(mPlatformCompat, times(2)).removeOverridesOnReleaseBuilds(
+                mOverridesToRemoveConfigCaptor.capture(), eq(PACKAGE_2));
+        List<CompatibilityOverridesToRemoveConfig> configs =
+                mOverridesToRemoveConfigCaptor.getAllValues();
+        assertThat(configs.size()).isAtLeast(2);
+        assertThat(configs.get(configs.size() - 2).changeIds).containsExactly(123L);
+        assertThat(configs.get(configs.size() - 1).changeIds).containsExactly(456L);
+        // Package 3
+        verify(mPlatformCompat).putOverridesOnReleaseBuilds(mOverridesToAddConfigCaptor.capture(),
+                eq(PACKAGE_3));
+        verify(mPlatformCompat).removeOverridesOnReleaseBuilds(
+                mOverridesToRemoveConfigCaptor.capture(), eq(PACKAGE_3));
+        assertThat(mOverridesToAddConfigCaptor.getValue().overrides.keySet()).containsExactly(456L);
+        assertThat(mOverridesToRemoveConfigCaptor.getValue().changeIds).containsExactly(789L);
+    }
+
+    @Test
+    public void onPropertiesChanged_ownedChangeIdsFlagAndSomePackageOverrideFlagsChanged_ok()
+            throws Exception {
+        DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1)
+                .setString(FLAG_REMOVE_OVERRIDES, PACKAGE_1 + "=*")
+                .setString(FLAG_OWNED_CHANGE_IDS, "123,456")
+                .setString(PACKAGE_1, "123:::true")
+                .setString(PACKAGE_3, "456:::false").build());
+        mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 0);
+        mockGetApplicationInfo(PACKAGE_2, /* versionCode= */ 0);
+        mockGetApplicationInfo(PACKAGE_3, /* versionCode= */ 0);
+
+        mService.registerDeviceConfigListeners();
+        DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1)
+                .setString(FLAG_OWNED_CHANGE_IDS, "123,456,789")
+                .setString(PACKAGE_2, "123:::true").build());
+
+        // Package 1
+        verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds(
+                any(CompatibilityOverrideConfig.class), eq(PACKAGE_1));
+        verify(mPlatformCompat).removeOverridesOnReleaseBuilds(
+                mOverridesToRemoveConfigCaptor.capture(), eq(PACKAGE_1));
+        assertThat(mOverridesToRemoveConfigCaptor.getValue().changeIds).containsExactly(123L, 456L,
+                789L);
+        // Package 2
+        verify(mPlatformCompat).putOverridesOnReleaseBuilds(mOverridesToAddConfigCaptor.capture(),
+                eq(PACKAGE_2));
+        verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds(
+                any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_2));
+        assertThat(mOverridesToAddConfigCaptor.getValue().overrides.keySet()).containsExactly(123L);
+        // Package 3
+        verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds(
+                any(CompatibilityOverrideConfig.class), eq(PACKAGE_3));
+        verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds(
+                any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_3));
+    }
+
+    @Test
+    public void onPropertiesChanged_platformCompatThrowsExceptionForSomeCalls_skipsFailedCalls()
+            throws Exception {
+        mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 0);
+        mockGetApplicationInfo(PACKAGE_2, /* versionCode= */ 0);
+        mockGetApplicationInfo(PACKAGE_3, /* versionCode= */ 0);
+        mockGetApplicationInfo(PACKAGE_4, /* versionCode= */ 0);
+        doThrow(new RemoteException()).when(mPlatformCompat).putOverridesOnReleaseBuilds(
+                any(CompatibilityOverrideConfig.class), eq(PACKAGE_2));
+        doThrow(new RemoteException()).when(mPlatformCompat).removeOverridesOnReleaseBuilds(
+                any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_3));
+
+        mService.registerDeviceConfigListeners();
+        DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1)
+                .setString(PACKAGE_1, "123:::true,456:::")
+                .setString(PACKAGE_2, "123:::true,456:::")
+                .setString(PACKAGE_3, "123:::true,456:::")
+                .setString(PACKAGE_4, "123:::true,456:::").build());
+
+        // Package 1
+        verify(mPlatformCompat).putOverridesOnReleaseBuilds(any(CompatibilityOverrideConfig.class),
+                eq(PACKAGE_1));
+        verify(mPlatformCompat).removeOverridesOnReleaseBuilds(
+                any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_1));
+        // Package 2
+        verify(mPlatformCompat).putOverridesOnReleaseBuilds(any(CompatibilityOverrideConfig.class),
+                eq(PACKAGE_2));
+        verify(mPlatformCompat).removeOverridesOnReleaseBuilds(
+                any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_2));
+        // Package 3
+        verify(mPlatformCompat).putOverridesOnReleaseBuilds(any(CompatibilityOverrideConfig.class),
+                eq(PACKAGE_3));
+        verify(mPlatformCompat).removeOverridesOnReleaseBuilds(
+                any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_3));
+        // Package 4
+        verify(mPlatformCompat).putOverridesOnReleaseBuilds(any(CompatibilityOverrideConfig.class),
+                eq(PACKAGE_1));
+        verify(mPlatformCompat).removeOverridesOnReleaseBuilds(
+                any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_4));
+    }
+
+    private void mockGetApplicationInfo(String packageName, long versionCode)
+            throws Exception {
+        when(mPackageManager.getApplicationInfo(eq(packageName), anyInt())).thenReturn(
+                createAppInfo(versionCode));
+    }
+
+    private void mockGetApplicationInfoNotInstalled(String packageName) throws Exception {
+        when(mPackageManager.getApplicationInfo(eq(packageName), anyInt()))
+                .thenThrow(new PackageManager.NameNotFoundException());
+    }
+
+    private static ApplicationInfo createAppInfo(long versionCode) {
+        ApplicationInfo appInfo = new ApplicationInfo();
+        appInfo.longVersionCode = versionCode;
+        return appInfo;
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfig.java b/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfig.java
index 64b24c1..43188f6 100644
--- a/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfig.java
+++ b/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfig.java
@@ -95,18 +95,25 @@
                     String name = invocationOnMock.getArgument(1);
                     String value = invocationOnMock.getArgument(2);
                     mKeyValueMap.put(getKey(namespace, name), value);
-                    for (DeviceConfig.OnPropertiesChangedListener listener :
-                            mOnPropertiesChangedListenerMap.keySet()) {
-                        if (namespace.equals(mOnPropertiesChangedListenerMap.get(listener).first)) {
-                            mOnPropertiesChangedListenerMap.get(listener).second.execute(
-                                    () -> listener.onPropertiesChanged(
-                                            getProperties(namespace, name, value)));
-                        }
-                    }
+                    invokeListeners(namespace, getProperties(namespace, name, value));
                     return true;
                 }
         ).when(() -> DeviceConfig.setProperty(anyString(), anyString(), anyString(), anyBoolean()));
 
+        doAnswer((Answer<Boolean>) invocationOnMock -> {
+                    Properties properties = invocationOnMock.getArgument(0);
+                    String namespace = properties.getNamespace();
+                    Map<String, String> keyValues = new ArrayMap<>();
+                    for (String name : properties.getKeyset()) {
+                        String value = properties.getString(name, /* defaultValue= */ "");
+                        mKeyValueMap.put(getKey(namespace, name), value);
+                        keyValues.put(name.toLowerCase(), value);
+                    }
+                    invokeListeners(namespace, getProperties(namespace, keyValues));
+                    return true;
+                }
+        ).when(() -> DeviceConfig.setProperties(any(Properties.class)));
+
         doAnswer((Answer<String>) invocationOnMock -> {
             String namespace = invocationOnMock.getArgument(0);
             String name = invocationOnMock.getArgument(1);
@@ -153,6 +160,16 @@
         return Pair.create(values[0], values[1]);
     }
 
+    private void invokeListeners(String namespace, Properties properties) {
+        for (DeviceConfig.OnPropertiesChangedListener listener :
+                mOnPropertiesChangedListenerMap.keySet()) {
+            if (namespace.equals(mOnPropertiesChangedListenerMap.get(listener).first)) {
+                mOnPropertiesChangedListenerMap.get(listener).second.execute(
+                        () -> listener.onPropertiesChanged(properties));
+            }
+        }
+    }
+
     private Properties getProperties(String namespace, String name, String value) {
         return getProperties(namespace, Collections.singletonMap(name.toLowerCase(), value));
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfigTest.java b/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfigTest.java
index 0e40669..d68b814 100644
--- a/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfigTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfigTest.java
@@ -23,6 +23,7 @@
 import android.app.ActivityThread;
 import android.platform.test.annotations.Presubmit;
 import android.provider.DeviceConfig;
+import android.provider.DeviceConfig.BadConfigException;
 import android.provider.DeviceConfig.Properties;
 
 import androidx.test.filters.SmallTest;
@@ -92,6 +93,16 @@
     }
 
     @Test
+    public void setProperties() throws BadConfigException {
+        String newKey = "key2";
+        String newValue = "value2";
+        DeviceConfig.setProperties(new Properties.Builder(sNamespace).setString(sKey,
+                sValue).setString(newKey, newValue).build());
+        assertThat(DeviceConfig.getProperty(sNamespace, sKey)).isEqualTo(sValue);
+        assertThat(DeviceConfig.getProperty(sNamespace, newKey)).isEqualTo(newValue);
+    }
+
+    @Test
     public void getProperties_empty() {
         String newKey = "key2";
         String newValue = "value2";
@@ -131,13 +142,12 @@
     }
 
     @Test
-    public void testListener() throws InterruptedException {
+    public void testListener_setProperty() throws InterruptedException {
         CountDownLatch countDownLatch = new CountDownLatch(1);
 
         OnPropertiesChangedListener changeListener = (properties) -> {
             assertThat(properties.getNamespace()).isEqualTo(sNamespace);
-            assertThat(properties.getKeyset().size()).isEqualTo(1);
-            assertThat(properties.getKeyset()).contains(sKey);
+            assertThat(properties.getKeyset()).containsExactly(sKey);
             assertThat(properties.getString(sKey, "bogus_value")).isEqualTo(sValue);
             assertThat(properties.getString("bogus_key", "bogus_value")).isEqualTo("bogus_value");
             countDownLatch.countDown();
@@ -153,6 +163,32 @@
         }
     }
 
+    @Test
+    public void testListener_setProperties() throws BadConfigException, InterruptedException {
+        CountDownLatch countDownLatch = new CountDownLatch(1);
+        String newKey = "key2";
+        String newValue = "value2";
+
+        OnPropertiesChangedListener changeListener = (properties) -> {
+            assertThat(properties.getNamespace()).isEqualTo(sNamespace);
+            assertThat(properties.getKeyset()).containsExactly(sKey, newKey);
+            assertThat(properties.getString(sKey, "bogus_value")).isEqualTo(sValue);
+            assertThat(properties.getString(newKey, "bogus_value")).isEqualTo(newValue);
+            assertThat(properties.getString("bogus_key", "bogus_value")).isEqualTo("bogus_value");
+            countDownLatch.countDown();
+        };
+        try {
+            DeviceConfig.addOnPropertiesChangedListener(sNamespace,
+                    ActivityThread.currentApplication().getMainExecutor(), changeListener);
+            DeviceConfig.setProperties(new Properties.Builder(sNamespace).setString(sKey,
+                    sValue).setString(newKey, newValue).build());
+            assertThat(countDownLatch.await(
+                    WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue();
+        } finally {
+            DeviceConfig.removeOnPropertiesChangedListener(changeListener);
+        }
+    }
+
 }
 
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
index d6c11a5..e612d12 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
@@ -65,6 +65,7 @@
 import static org.mockito.Mockito.when;
 
 import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.AccessibilityTrace;
 import android.accessibilityservice.IAccessibilityServiceClient;
 import android.content.ComponentName;
 import android.content.Context;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java
index 80e81d6..554f0a4 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java
@@ -29,6 +29,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
@@ -176,7 +177,7 @@
     }
 
     @Test
-    public void testEventHandler_shouldChangeAfterOnDisplayChanged() {
+    public void testEventHandler_shouldIncreaseAndHaveCorrectOrderAfterOnDisplayAdded() {
         prepareLooper();
 
         // Check if there is only one mEventHandler when there is one default display.
@@ -184,13 +185,51 @@
         assertEquals(1, mEventHandler.size());
 
         // Check if it has correct numbers of mEventHandler for corresponding displays.
-        setDisplayCount(4);
-        mA11yInputFilter.onDisplayChanged();
-        assertEquals(4, mEventHandler.size());
+        setDisplayCount(2);
+        mA11yInputFilter.onDisplayAdded(mDisplayList.get(SECOND_DISPLAY));
+        assertEquals(2, mEventHandler.size());
+
+        EventStreamTransformation next = mEventHandler.get(SECOND_DISPLAY);
+        assertNotNull(next);
+
+        // Start from index 1 because KeyboardInterceptor only exists in EventHandler for
+        // DEFAULT_DISPLAY.
+        for (int i = 1; next != null; i++) {
+            assertEquals(next.getClass(), mExpectedEventHandlerTypes[i]);
+            next = next.getNext();
+        }
+    }
+
+    @Test
+    public void testEventHandler_shouldDecreaseAfterOnDisplayRemoved() {
+        prepareLooper();
 
         setDisplayCount(2);
-        mA11yInputFilter.onDisplayChanged();
+        mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures);
         assertEquals(2, mEventHandler.size());
+
+        // Check if it has correct numbers of mEventHandler for corresponding displays.
+        mA11yInputFilter.onDisplayRemoved(SECOND_DISPLAY);
+        assertEquals(1, mEventHandler.size());
+
+        EventStreamTransformation eventHandler = mEventHandler.get(SECOND_DISPLAY);
+        assertNull(eventHandler);
+    }
+
+    @Test
+    public void testEventHandler_shouldNoChangedInOtherDisplayAfterOnDisplayRemoved() {
+        prepareLooper();
+
+        setDisplayCount(2);
+        mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures);
+        EventStreamTransformation eventHandlerBeforeDisplayRemoved =
+                mEventHandler.get(DEFAULT_DISPLAY);
+
+        mA11yInputFilter.onDisplayRemoved(SECOND_DISPLAY);
+        EventStreamTransformation eventHandlerAfterDisplayRemoved =
+                mEventHandler.get(DEFAULT_DISPLAY);
+
+        assertEquals(eventHandlerBeforeDisplayRemoved, eventHandlerAfterDisplayRemoved);
     }
 
     @Test
@@ -240,7 +279,7 @@
     }
 
     @Test
-    public void testInputEvent_shouldClearEventsForAllEventHandlers() {
+    public void testInputEvent_shouldClearEventsForDisplayEventHandlers() {
         prepareLooper();
 
         mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures);
@@ -253,13 +292,71 @@
         send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN));
         assertEquals(2, mCaptor1.mEvents.size());
 
-        // InputEvent with different input source should trigger clearEvents() for each
-        // EventStreamTransformation in EventHandler.
+        // InputEvent with different input source to the same display should trigger
+        // clearEvents() for the EventHandler in this display.
         send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_MOUSE));
         assertEquals(1, mCaptor1.mEvents.size());
     }
 
     @Test
+    public void testInputEvent_shouldNotClearEventsForOtherDisplayEventHandlers() {
+        prepareLooper();
+
+        setDisplayCount(2);
+        mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures);
+        assertEquals(2, mEventHandler.size());
+
+        mCaptor1 = new EventCaptor();
+        mCaptor2 = new EventCaptor();
+        mEventHandler.put(DEFAULT_DISPLAY, mCaptor1);
+        mEventHandler.put(SECOND_DISPLAY, mCaptor2);
+
+        // InputEvent with different displayId should be dispatched to corresponding EventHandler.
+        send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN));
+        send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN));
+        send(downEvent(SECOND_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN));
+
+        // InputEvent with different input source should not trigger clearEvents() for
+        // the EventHandler in the other display.
+        send(downEvent(SECOND_DISPLAY, InputDevice.SOURCE_MOUSE));
+        assertEquals(2, mCaptor1.mEvents.size());
+    }
+
+    @Test
+    public void testInputEvent_shouldNotClearEventsForOtherDisplayAfterOnDisplayAdded() {
+        prepareLooper();
+
+        mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures);
+        mCaptor1 = new EventCaptor();
+        mEventHandler.put(DEFAULT_DISPLAY, mCaptor1);
+
+        send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN));
+        send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN));
+        assertEquals(2, mCaptor1.mEvents.size());
+
+        setDisplayCount(2);
+        mA11yInputFilter.onDisplayAdded(mDisplayList.get(SECOND_DISPLAY));
+        assertEquals(2, mCaptor1.mEvents.size());
+    }
+
+    @Test
+    public void testInputEvent_shouldNotClearEventsForOtherDisplayAfterOnDisplayRemoved() {
+        prepareLooper();
+
+        setDisplayCount(2);
+        mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures);
+        mCaptor1 = new EventCaptor();
+        mEventHandler.put(DEFAULT_DISPLAY, mCaptor1);
+
+        send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN));
+        send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN));
+        assertEquals(2, mCaptor1.mEvents.size());
+
+        mA11yInputFilter.onDisplayRemoved(SECOND_DISPLAY);
+        assertEquals(2, mCaptor1.mEvents.size());
+    }
+
+    @Test
     public void testEnabledFeatures_windowMagnificationMode_expectedMagnificationGestureHandler() {
         prepareLooper();
         doReturn(Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW).when(
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
index 00daa5c..432a500 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
@@ -29,6 +29,7 @@
 import static org.mockito.Mockito.when;
 
 import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.AccessibilityTrace;
 import android.accessibilityservice.GestureDescription;
 import android.accessibilityservice.IAccessibilityServiceClient;
 import android.content.ComponentName;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
index e4d51e4..4afe099 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
@@ -120,6 +120,7 @@
     @Mock private AccessibilityWindowManager.AccessibilityEventSender mMockA11yEventSender;
     @Mock private AccessibilitySecurityPolicy mMockA11ySecurityPolicy;
     @Mock private AccessibilitySecurityPolicy.AccessibilityUserManager mMockA11yUserManager;
+    @Mock private AccessibilityTraceManager mMockA11yTraceManager;
 
     @Mock private IBinder mMockHostToken;
     @Mock private IBinder mMockEmbeddedToken;
@@ -140,7 +141,8 @@
                 mMockWindowManagerInternal,
                 mMockA11yEventSender,
                 mMockA11ySecurityPolicy,
-                mMockA11yUserManager);
+                mMockA11yUserManager,
+                mMockA11yTraceManager);
         // Starts tracking window of default display and sets the default display
         // as top focused display before each testing starts.
         startTrackingPerDisplay(Display.DEFAULT_DISPLAY);
@@ -834,6 +836,19 @@
         assertNull(token);
     }
 
+    @Test
+    public void onDisplayReparented_shouldRemoveObserver() throws RemoteException {
+        // Starts tracking window of second display.
+        startTrackingPerDisplay(SECONDARY_DISPLAY_ID);
+        assertTrue(mA11yWindowManager.isTrackingWindowsLocked(SECONDARY_DISPLAY_ID));
+        // Notifies the second display is an embedded one of the default display.
+        final WindowsForAccessibilityCallback callbacks =
+                mCallbackOfWindows.get(Display.DEFAULT_DISPLAY);
+        callbacks.onDisplayReparented(SECONDARY_DISPLAY_ID);
+        // Makes sure the observer of the second display is removed.
+        assertFalse(mA11yWindowManager.isTrackingWindowsLocked(SECONDARY_DISPLAY_ID));
+    }
+
     private void registerLeashedTokenAndWindowId() {
         mA11yWindowManager.registerIdLocked(mMockHostToken, HOST_WINDOW_ID);
         mA11yWindowManager.registerIdLocked(mMockEmbeddedToken, EMBEDDED_WINDOW_ID);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java
index 78e651b..c62cae5 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java
@@ -56,11 +56,13 @@
     private MessageCapturingHandler mHandler = new MessageCapturingHandler(
             msg -> mInterceptor.handleMessage(msg));
     @Mock AccessibilityManagerService mMockAms;
+    @Mock AccessibilityTraceManager mMockTraceManager;
     @Mock WindowManagerPolicy mMockPolicy;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        when(mMockAms.getTraceManager()).thenReturn(mMockTraceManager);
         mInterceptor = new KeyboardInterceptor(mMockAms, mMockPolicy, mHandler);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java
index d4908ee..59b69f9 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java
@@ -122,6 +122,7 @@
 
     MotionEventInjector mMotionEventInjector;
     IAccessibilityServiceClient mServiceInterface;
+    AccessibilityTraceManager mTrace;
     List<GestureStep> mLineList = new ArrayList<>();
     List<GestureStep> mClickList = new ArrayList<>();
     List<GestureStep> mContinuedLineList1 = new ArrayList<>();
@@ -148,7 +149,8 @@
                 return mMotionEventInjector.handleMessage(msg);
             }
         });
-        mMotionEventInjector = new MotionEventInjector(mMessageCapturingHandler);
+        mTrace = mock(AccessibilityTraceManager.class);
+        mMotionEventInjector = new MotionEventInjector(mMessageCapturingHandler, mTrace);
         mServiceInterface = mock(IAccessibilityServiceClient.class);
 
         mLineList = createSimpleGestureFromPoints(0, 0, false, LINE_DURATION, LINE_START, LINE_END);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
index 1603087..4ce9ba0 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
@@ -27,6 +27,7 @@
 import static org.mockito.Mockito.when;
 
 import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.AccessibilityTrace;
 import android.accessibilityservice.IAccessibilityServiceClient;
 import android.app.UiAutomation;
 import android.content.Context;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
index 7bf0bb8..8a745d5 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
@@ -36,6 +36,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.accessibilityservice.AccessibilityGestureEvent;
 import android.accessibilityservice.AccessibilityService;
@@ -53,6 +54,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.accessibility.AccessibilityManagerService;
+import com.android.server.accessibility.AccessibilityTraceManager;
 import com.android.server.accessibility.EventStreamTransformation;
 import com.android.server.accessibility.utils.GestureLogParser;
 import com.android.server.testutils.OffsettableClock;
@@ -107,6 +109,8 @@
 
     @Mock
     private AccessibilityManagerService mMockAms;
+    @Mock
+    private AccessibilityTraceManager mMockTraceManager;
     @Captor
     private ArgumentCaptor<AccessibilityGestureEvent> mGestureCaptor;
 
@@ -143,6 +147,7 @@
         if (Looper.myLooper() == null) {
             Looper.prepare();
         }
+        when(mMockAms.getTraceManager()).thenReturn(mMockTraceManager);
         mContext = InstrumentationRegistry.getContext();
         mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
         AccessibilityManagerService ams = new AccessibilityManagerService(mContext);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
index 502f64a..fe4fed9 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
@@ -52,6 +52,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.accessibility.AccessibilityManagerService;
+import com.android.server.accessibility.AccessibilityTraceManager;
 import com.android.server.accessibility.test.MessageCapturingHandler;
 import com.android.server.wm.WindowManagerInternal;
 import com.android.server.wm.WindowManagerInternal.MagnificationCallbacks;
@@ -93,6 +94,7 @@
             mock(FullScreenMagnificationController.ControllerContext.class);
     final Context mMockContext = mock(Context.class);
     final AccessibilityManagerService mMockAms = mock(AccessibilityManagerService.class);
+    final AccessibilityTraceManager mMockTraceManager = mock(AccessibilityTraceManager.class);
     final WindowManagerInternal mMockWindowManager = mock(WindowManagerInternal.class);
     private final MagnificationAnimationCallback mAnimationCallback = mock(
             MagnificationAnimationCallback.class);
@@ -113,9 +115,11 @@
         when(mMockContext.getMainLooper()).thenReturn(looper);
         when(mMockControllerCtx.getContext()).thenReturn(mMockContext);
         when(mMockControllerCtx.getAms()).thenReturn(mMockAms);
+        when(mMockControllerCtx.getTraceManager()).thenReturn(mMockTraceManager);
         when(mMockControllerCtx.getWindowManager()).thenReturn(mMockWindowManager);
         when(mMockControllerCtx.getHandler()).thenReturn(mMessageCapturingHandler);
         when(mMockControllerCtx.getAnimationDuration()).thenReturn(1000L);
+        when(mMockAms.getTraceManager()).thenReturn(mMockTraceManager);
         initMockWindowManager();
 
         mFullScreenMagnificationController = new FullScreenMagnificationController(
@@ -773,20 +777,20 @@
     }
 
     @Test
-    public void testRotation_resetsMagnification() {
+    public void testDisplaySizeChanged_resetsMagnification() {
         for (int i = 0; i < DISPLAY_COUNT; i++) {
-            rotation_resetsMagnification(i);
+            changeDisplaySize_resetsMagnification(i);
             resetMockWindowManager();
         }
     }
 
-    private void rotation_resetsMagnification(int displayId) {
+    private void changeDisplaySize_resetsMagnification(int displayId) {
         register(displayId);
         MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
         zoomIn2xToMiddle(displayId);
         mMessageCapturingHandler.sendAllMessages();
         assertTrue(mFullScreenMagnificationController.isMagnifying(displayId));
-        callbacks.onRotationChanged(0);
+        callbacks.onDisplaySizeChanged();
         mMessageCapturingHandler.sendAllMessages();
         assertFalse(mFullScreenMagnificationController.isMagnifying(displayId));
     }
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index f881f04..b14c353 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -52,6 +52,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.accessibility.AccessibilityManagerService;
+import com.android.server.accessibility.AccessibilityTraceManager;
 import com.android.server.accessibility.EventStreamTransformation;
 import com.android.server.accessibility.magnification.FullScreenMagnificationController.MagnificationInfoChangedCallback;
 import com.android.server.testutils.OffsettableClock;
@@ -129,6 +130,10 @@
     MagnificationInfoChangedCallback mMagnificationInfoChangedCallback;
     @Mock
     WindowMagnificationPromptController mWindowMagnificationPromptController;
+    @Mock
+    AccessibilityManagerService mMockAccessibilityManagerService;
+    @Mock
+    AccessibilityTraceManager mMockTraceManager;
 
     private OffsettableClock mClock;
     private FullScreenMagnificationGestureHandler mMgh;
@@ -144,7 +149,9 @@
                 mock(FullScreenMagnificationController.ControllerContext.class);
         final WindowManagerInternal mockWindowManager = mock(WindowManagerInternal.class);
         when(mockController.getContext()).thenReturn(mContext);
-        when(mockController.getAms()).thenReturn(mock(AccessibilityManagerService.class));
+        when(mockController.getAms()).thenReturn(mMockAccessibilityManagerService);
+        when(mMockAccessibilityManagerService.getTraceManager()).thenReturn(mMockTraceManager);
+        when(mockController.getTraceManager()).thenReturn(mMockTraceManager);
         when(mockController.getWindowManager()).thenReturn(mockWindowManager);
         when(mockController.getHandler()).thenReturn(new Handler(mContext.getMainLooper()));
         when(mockController.newValueAnimator()).thenReturn(new ValueAnimator());
@@ -179,7 +186,7 @@
     private FullScreenMagnificationGestureHandler newInstance(boolean detectTripleTap,
             boolean detectShortcutTrigger) {
         FullScreenMagnificationGestureHandler h = new FullScreenMagnificationGestureHandler(
-                mContext, mFullScreenMagnificationController, mMockCallback,
+                mContext, mFullScreenMagnificationController, mMockTraceManager, mMockCallback,
                 detectTripleTap, detectShortcutTrigger,
                 mWindowMagnificationPromptController, DISPLAY_0);
         mHandler = new TestHandler(h.mDetectingState, mClock) {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
index b7f5f4d..e82adc8 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
@@ -54,6 +54,7 @@
 
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.server.accessibility.AccessibilityManagerService;
+import com.android.server.accessibility.AccessibilityTraceManager;
 
 import org.junit.After;
 import org.junit.Before;
@@ -84,6 +85,8 @@
             Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
 
     @Mock
+    private AccessibilityTraceManager mTraceManager;
+    @Mock
     private AccessibilityManagerService mService;
     @Mock
     private MagnificationController.TransitionCallBack mTransitionCallBack;
@@ -112,7 +115,7 @@
                 CURRENT_USER_ID);
         mWindowMagnificationManager = Mockito.spy(
                 new WindowMagnificationManager(mContext, CURRENT_USER_ID,
-                        mock(WindowMagnificationManager.Callback.class)));
+                        mock(WindowMagnificationManager.Callback.class), mTraceManager));
         mMockConnection = new MockWindowMagnificationConnection(true);
         mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
         mScreenMagnificationControllerStubber = new FullScreenMagnificationControllerStubber(
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java
index 514d16a..ef6ed88 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java
@@ -31,6 +31,8 @@
 
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.accessibility.AccessibilityTraceManager;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -49,6 +51,8 @@
             Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
 
     @Mock
+    AccessibilityTraceManager mTraceManager;
+    @Mock
     MagnificationGestureHandler.Callback mCallback;
 
     @Before
@@ -57,6 +61,7 @@
         mMgh = new TestMagnificationGestureHandler(DISPLAY_0,
                 /* detectTripleTap= */true,
                 /* detectShortcutTrigger= */true,
+                mTraceManager,
                 mCallback);
     }
 
@@ -129,8 +134,9 @@
         boolean mIsInternalMethodCalled = false;
 
         TestMagnificationGestureHandler(int displayId, boolean detectTripleTap,
-                boolean detectShortcutTrigger, @NonNull Callback callback) {
-            super(displayId, detectTripleTap, detectShortcutTrigger, callback);
+                boolean detectShortcutTrigger, @NonNull AccessibilityTraceManager trace,
+                @NonNull Callback callback) {
+            super(displayId, detectTripleTap, detectShortcutTrigger, trace, callback);
         }
 
         @Override
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java
index c88bc3b..1638563 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java
@@ -29,6 +29,8 @@
 import android.view.accessibility.IWindowMagnificationConnectionCallback;
 import android.view.accessibility.MagnificationAnimationCallback;
 
+import com.android.server.accessibility.AccessibilityTraceManager;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mock;
@@ -45,6 +47,8 @@
 
     private IWindowMagnificationConnection mConnection;
     @Mock
+    private AccessibilityTraceManager mTrace;
+    @Mock
     private IWindowMagnificationConnectionCallback mCallback;
     @Mock
     private MagnificationAnimationCallback mAnimationCallback;
@@ -57,7 +61,7 @@
         MockitoAnnotations.initMocks(this);
         mMockWindowMagnificationConnection = new MockWindowMagnificationConnection();
         mConnection = mMockWindowMagnificationConnection.getConnection();
-        mConnectionWrapper = new WindowMagnificationConnectionWrapper(mConnection);
+        mConnectionWrapper = new WindowMagnificationConnectionWrapper(mConnection, mTrace);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java
index b9498d6..6a5aae6 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java
@@ -35,6 +35,7 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.accessibility.AccessibilityTraceManager;
 import com.android.server.accessibility.EventStreamTransformation;
 import com.android.server.accessibility.utils.TouchEventGenerator;
 
@@ -74,16 +75,18 @@
     private WindowMagnificationGestureHandler mWindowMagnificationGestureHandler;
     @Mock
     MagnificationGestureHandler.Callback mMockCallback;
+    @Mock
+    AccessibilityTraceManager mMockTrace;
 
     @Before
     public void setUp() throws RemoteException {
         MockitoAnnotations.initMocks(this);
         mContext = InstrumentationRegistry.getInstrumentation().getContext();
         mWindowMagnificationManager = new WindowMagnificationManager(mContext, 0,
-                mock(WindowMagnificationManager.Callback.class));
+                mock(WindowMagnificationManager.Callback.class), mMockTrace);
         mMockConnection = new MockWindowMagnificationConnection();
         mWindowMagnificationGestureHandler = new WindowMagnificationGestureHandler(
-                mContext, mWindowMagnificationManager, mMockCallback,
+                mContext, mWindowMagnificationManager, mMockTrace, mMockCallback,
                 /** detectTripleTap= */true,   /** detectShortcutTrigger= */true, DISPLAY_0);
         mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
         mWindowMagnificationGestureHandler.setNext(strictMock(EventStreamTransformation.class));
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
index a20272a..af6d40f 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
@@ -52,6 +52,7 @@
 
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.server.LocalServices;
+import com.android.server.accessibility.AccessibilityTraceManager;
 import com.android.server.statusbar.StatusBarManagerInternal;
 
 import org.junit.Before;
@@ -72,6 +73,8 @@
     @Mock
     private Context mContext;
     @Mock
+    private AccessibilityTraceManager mMockTrace;
+    @Mock
     private StatusBarManagerInternal mMockStatusBarManagerInternal;
     @Mock
     private MagnificationAnimationCallback mAnimationCallback;
@@ -88,7 +91,7 @@
         mResolver = new MockContentResolver();
         mMockConnection = new MockWindowMagnificationConnection();
         mWindowMagnificationManager = new WindowMagnificationManager(mContext, CURRENT_USER_ID,
-                mMockCallback);
+                mMockCallback, mMockTrace);
 
         when(mContext.getContentResolver()).thenReturn(mResolver);
         doAnswer((InvocationOnMock invocation) -> {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
index 32a4774..697d102 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
@@ -260,7 +260,7 @@
         notifyActivityLaunching(mTopActivity.intent);
         notifyActivityLaunched(START_SUCCESS, mTopActivity);
         doReturn(true).when(mTopActivity.mDisplayContent).isSleeping();
-        mTopActivity.setState(Task.ActivityState.RESUMED, "test");
+        mTopActivity.setState(ActivityRecord.State.RESUMED, "test");
         mTopActivity.setVisibility(false);
         waitHandlerIdle(mAtm.mH);
         // Not cancel immediately because in one of real cases, the keyguard may be going away or
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index a1f1610..fbe8c89 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -66,19 +66,19 @@
 import static com.android.server.wm.ActivityRecord.FINISH_RESULT_CANCELLED;
 import static com.android.server.wm.ActivityRecord.FINISH_RESULT_REMOVED;
 import static com.android.server.wm.ActivityRecord.FINISH_RESULT_REQUESTED;
-import static com.android.server.wm.Task.ActivityState.DESTROYED;
-import static com.android.server.wm.Task.ActivityState.DESTROYING;
-import static com.android.server.wm.Task.ActivityState.FINISHING;
-import static com.android.server.wm.Task.ActivityState.INITIALIZING;
-import static com.android.server.wm.Task.ActivityState.PAUSED;
-import static com.android.server.wm.Task.ActivityState.PAUSING;
-import static com.android.server.wm.Task.ActivityState.RESUMED;
-import static com.android.server.wm.Task.ActivityState.STARTED;
-import static com.android.server.wm.Task.ActivityState.STOPPED;
-import static com.android.server.wm.Task.ActivityState.STOPPING;
-import static com.android.server.wm.Task.TASK_VISIBILITY_INVISIBLE;
-import static com.android.server.wm.Task.TASK_VISIBILITY_VISIBLE;
-import static com.android.server.wm.Task.TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT;
+import static com.android.server.wm.ActivityRecord.State.DESTROYED;
+import static com.android.server.wm.ActivityRecord.State.DESTROYING;
+import static com.android.server.wm.ActivityRecord.State.FINISHING;
+import static com.android.server.wm.ActivityRecord.State.INITIALIZING;
+import static com.android.server.wm.ActivityRecord.State.PAUSED;
+import static com.android.server.wm.ActivityRecord.State.PAUSING;
+import static com.android.server.wm.ActivityRecord.State.RESUMED;
+import static com.android.server.wm.ActivityRecord.State.STARTED;
+import static com.android.server.wm.ActivityRecord.State.STOPPED;
+import static com.android.server.wm.ActivityRecord.State.STOPPING;
+import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_INVISIBLE;
+import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE;
+import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_AFTER_ANIM;
 import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_BEFORE_ANIM;
@@ -136,7 +136,7 @@
 import androidx.test.filters.MediumTest;
 
 import com.android.internal.R;
-import com.android.server.wm.Task.ActivityState;
+import com.android.server.wm.ActivityRecord.State;
 
 import org.junit.Assert;
 import org.junit.Before;
@@ -171,25 +171,25 @@
     }
 
     @Test
-    public void testStackCleanupOnClearingTask() {
+    public void testTaskFragmentCleanupOnClearingTask() {
         final ActivityRecord activity = createActivityWith2LevelTask();
         final Task task = activity.getTask();
-        final Task rootTask = activity.getRootTask();
+        final TaskFragment taskFragment = activity.getTaskFragment();
         activity.onParentChanged(null /*newParent*/, task);
-        verify(rootTask, times(1)).cleanUpActivityReferences(any());
+        verify(taskFragment).cleanUpActivityReferences(any());
     }
 
     @Test
-    public void testStackCleanupOnActivityRemoval() {
+    public void testTaskFragmentCleanupOnActivityRemoval() {
         final ActivityRecord activity = createActivityWith2LevelTask();
         final Task task = activity.getTask();
-        final Task rootTask = activity.getRootTask();
+        final TaskFragment taskFragment = activity.getTaskFragment();
         task.removeChild(activity);
-        verify(rootTask, times(1)).cleanUpActivityReferences(any());
+        verify(taskFragment).cleanUpActivityReferences(any());
     }
 
     @Test
-    public void testStackCleanupOnTaskRemoval() {
+    public void testRootTaskCleanupOnTaskRemoval() {
         final ActivityRecord activity = createActivityWith2LevelTask();
         final Task task = activity.getTask();
         final Task rootTask = activity.getRootTask();
@@ -344,7 +344,7 @@
     public void testSetsRelaunchReason_NotDragResizing() {
         final ActivityRecord activity = createActivityWithTask();
         final Task task = activity.getTask();
-        activity.setState(Task.ActivityState.RESUMED, "Testing");
+        activity.setState(RESUMED, "Testing");
 
         task.onRequestedOverrideConfigurationChanged(task.getConfiguration());
         activity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
@@ -369,7 +369,7 @@
     public void testSetsRelaunchReason_DragResizing() {
         final ActivityRecord activity = createActivityWithTask();
         final Task task = activity.getTask();
-        activity.setState(Task.ActivityState.RESUMED, "Testing");
+        activity.setState(RESUMED, "Testing");
 
         task.onRequestedOverrideConfigurationChanged(task.getConfiguration());
         activity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
@@ -396,7 +396,7 @@
     public void testRelaunchClearTopWaitingTranslucent() {
         final ActivityRecord activity = createActivityWithTask();
         final Task task = activity.getTask();
-        activity.setState(Task.ActivityState.RESUMED, "Testing");
+        activity.setState(RESUMED, "Testing");
 
         task.onRequestedOverrideConfigurationChanged(task.getConfiguration());
         activity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
@@ -417,7 +417,7 @@
     public void testSetsRelaunchReason_NonResizeConfigChanges() {
         final ActivityRecord activity = createActivityWithTask();
         final Task task = activity.getTask();
-        activity.setState(Task.ActivityState.RESUMED, "Testing");
+        activity.setState(RESUMED, "Testing");
 
         task.onRequestedOverrideConfigurationChanged(task.getConfiguration());
         activity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
@@ -465,7 +465,7 @@
                 .setCreateTask(true)
                 .setConfigChanges(CONFIG_ORIENTATION | CONFIG_SCREEN_LAYOUT)
                 .build();
-        activity.setState(Task.ActivityState.RESUMED, "Testing");
+        activity.setState(RESUMED, "Testing");
 
         activity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
                 activity.getConfiguration()));
@@ -628,7 +628,7 @@
     @Test
     public void testShouldMakeActive_deferredResume() {
         final ActivityRecord activity = createActivityWithTask();
-        activity.setState(Task.ActivityState.STOPPED, "Testing");
+        activity.setState(STOPPED, "Testing");
 
         mSupervisor.beginDeferResume();
         assertEquals(false, activity.shouldMakeActive(null /* activeActivity */));
@@ -644,7 +644,7 @@
         ActivityRecord finishingActivity = new ActivityBuilder(mAtm).setTask(task).build();
         finishingActivity.finishing = true;
         ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(task).build();
-        activity.setState(Task.ActivityState.STOPPED, "Testing");
+        activity.setState(STOPPED, "Testing");
 
         assertEquals(false, activity.shouldMakeActive(null /* activeActivity */));
     }
@@ -653,15 +653,16 @@
     public void testShouldResume_stackVisibility() {
         final ActivityRecord activity = createActivityWithTask();
         final Task task = activity.getTask();
-        activity.setState(Task.ActivityState.STOPPED, "Testing");
+        activity.setState(STOPPED, "Testing");
 
-        doReturn(TASK_VISIBILITY_VISIBLE).when(task).getVisibility(null);
+        doReturn(TASK_FRAGMENT_VISIBILITY_VISIBLE).when(task).getVisibility(null);
         assertEquals(true, activity.shouldResumeActivity(null /* activeActivity */));
 
-        doReturn(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT).when(task).getVisibility(null);
+        doReturn(TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT)
+                .when(task).getVisibility(null);
         assertEquals(false, activity.shouldResumeActivity(null /* activeActivity */));
 
-        doReturn(TASK_VISIBILITY_INVISIBLE).when(task).getVisibility(null);
+        doReturn(TASK_FRAGMENT_VISIBILITY_INVISIBLE).when(task).getVisibility(null);
         assertEquals(false, activity.shouldResumeActivity(null /* activeActivity */));
     }
 
@@ -669,13 +670,13 @@
     public void testShouldResumeOrPauseWithResults() {
         final ActivityRecord activity = createActivityWithTask();
         final Task task = activity.getTask();
-        activity.setState(Task.ActivityState.STOPPED, "Testing");
+        activity.setState(STOPPED, "Testing");
 
         ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(task).build();
         activity.addResultLocked(topActivity, "resultWho", 0, 0, new Intent());
         topActivity.finishing = true;
 
-        doReturn(TASK_VISIBILITY_VISIBLE).when(task).getVisibility(null);
+        doReturn(TASK_FRAGMENT_VISIBILITY_VISIBLE).when(task).getVisibility(null);
         assertEquals(true, activity.shouldResumeActivity(null /* activeActivity */));
         assertEquals(false, activity.shouldPauseActivity(null /*activeActivity */));
     }
@@ -688,7 +689,7 @@
                 .setConfigChanges(CONFIG_ORIENTATION | CONFIG_SCREEN_LAYOUT)
                 .build();
         final Task task = activity.getTask();
-        activity.setState(Task.ActivityState.STOPPED, "Testing");
+        activity.setState(STOPPED, "Testing");
 
         final Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
         try {
@@ -731,7 +732,7 @@
         final ActivityRecord activity = createActivityWithTask();
         ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(activity.getTask()).build();
         topActivity.setOccludesParent(false);
-        activity.setState(Task.ActivityState.STOPPED, "Testing");
+        activity.setState(STOPPED, "Testing");
         activity.setVisibility(true);
         activity.makeActiveIfNeeded(null /* activeActivity */);
         assertEquals(STARTED, activity.getState());
@@ -1015,8 +1016,8 @@
     @Test
     public void testFinishActivityIfPossible_nonResumedFinishCompletesImmediately() {
         final ActivityRecord activity = createActivityWithTask();
-        final ActivityState[] states = {INITIALIZING, STARTED, PAUSED, STOPPING, STOPPED};
-        for (ActivityState state : states) {
+        final State[] states = {INITIALIZING, STARTED, PAUSED, STOPPING, STOPPED};
+        for (State state : states) {
             activity.finishing = false;
             activity.setState(state, "test");
             reset(activity);
@@ -1151,7 +1152,7 @@
     /**
      * Verify that finish request won't change the state of next top activity if the current
      * finishing activity doesn't need to be destroyed immediately. The case is usually like
-     * from {@link ActivityStack#completePauseLocked(boolean, ActivityRecord)} to
+     * from {@link Task#completePause(boolean, ActivityRecord)} to
      * {@link ActivityRecord#completeFinishing(String)}, so the complete-pause should take the
      * responsibility to resume the next activity with updating the state.
      */
@@ -1397,7 +1398,7 @@
     }
 
     private void testCompleteFinishing_ensureActivitiesVisible(boolean diffTask,
-            ActivityState secondActivityState) {
+            State secondActivityState) {
         final ActivityRecord activity = createActivityWithTask();
         final Task task = activity.getTask();
         final ActivityRecord firstActivity = new ActivityBuilder(mAtm).setTask(task).build();
@@ -1449,7 +1450,8 @@
     @Test
     public void testDestroyIfPossible() {
         final ActivityRecord activity = createActivityWithTask();
-        doReturn(false).when(mRootWindowContainer).resumeFocusedTasksTopActivities();
+        doReturn(false).when(mRootWindowContainer)
+                .resumeFocusedTasksTopActivities();
         activity.destroyIfPossible("test");
 
         assertEquals(DESTROYING, activity.getState());
@@ -1471,7 +1473,8 @@
             homeStack.removeChild(t, "test");
         }, true /* traverseTopToBottom */);
         activity.finishing = true;
-        doReturn(false).when(mRootWindowContainer).resumeFocusedTasksTopActivities();
+        doReturn(false).when(mRootWindowContainer)
+                .resumeFocusedTasksTopActivities();
 
         // Try to destroy the last activity above the home stack.
         activity.destroyIfPossible("test");
@@ -1907,8 +1910,7 @@
         assertTrue(wpc.registeredForActivityConfigChanges());
 
         // Create a new task with custom config to reparent the activity to.
-        final Task newTask =
-                new TaskBuilder(mSupervisor).setParentTask(initialTask.getRootTask()).build();
+        final Task newTask = new TaskBuilder(mSupervisor).build();
         final Configuration newConfig = newTask.getConfiguration();
         newConfig.densityDpi += 100;
         newTask.onRequestedOverrideConfigurationChanged(newConfig);
@@ -1940,8 +1942,7 @@
                 .diff(wpc.getRequestedOverrideConfiguration()));
 
         // Create a new task with custom config to reparent the second activity to.
-        final Task newTask =
-                new TaskBuilder(mSupervisor).setParentTask(initialTask.getRootTask()).build();
+        final Task newTask = new TaskBuilder(mSupervisor).build();
         final Configuration newConfig = newTask.getConfiguration();
         newConfig.densityDpi += 100;
         newTask.onRequestedOverrideConfigurationChanged(newConfig);
@@ -2151,7 +2152,7 @@
         assertFalse(activity.supportsPictureInPicture());
     }
 
-    private void verifyProcessInfoUpdate(ActivityRecord activity, ActivityState state,
+    private void verifyProcessInfoUpdate(ActivityRecord activity, State state,
             boolean shouldUpdate, boolean activityChange) {
         reset(activity.app);
         activity.setState(state, "test");
@@ -2595,7 +2596,8 @@
                 any(), any(), anyBoolean(), anyBoolean(), any(), any());
         // In normal case, resumeFocusedTasksTopActivities() should be called after
         // startActivityLocked(). So skip resumeFocusedTasksTopActivities() in ActivityBuilder.
-        doReturn(false).when(mRootWindowContainer).resumeFocusedTasksTopActivities();
+        doReturn(false).when(mRootWindowContainer)
+                .resumeFocusedTasksTopActivities();
         // Make mVisibleSetFromTransferredStartingWindow true.
         final ActivityRecord middle = new ActivityBuilder(mAtm).setTask(task).build();
         task.startActivityLocked(middle, null /* focusedTopActivity */,
@@ -2738,6 +2740,40 @@
     }
 
     @Test
+    public void testCloseToSquareFixedOrientationPortrait() {
+        // create a square display
+        final DisplayContent squareDisplay = new TestDisplayContent.Builder(mAtm, 2000, 2000)
+                .setSystemDecorations(true).build();
+        final Task task = new TaskBuilder(mSupervisor).setDisplay(squareDisplay).build();
+
+        // create a fixed portrait activity
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task)
+                .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT).build();
+
+        // check that both the configuration and app bounds are portrait
+        assertEquals(ORIENTATION_PORTRAIT, activity.getConfiguration().orientation);
+        assertTrue(activity.getConfiguration().windowConfiguration.getAppBounds().width()
+                <= activity.getConfiguration().windowConfiguration.getAppBounds().height());
+    }
+
+    @Test
+    public void testCloseToSquareFixedOrientationLandscape() {
+        // create a square display
+        final DisplayContent squareDisplay = new TestDisplayContent.Builder(mAtm, 2000, 2000)
+                .setSystemDecorations(true).build();
+        final Task task = new TaskBuilder(mSupervisor).setDisplay(squareDisplay).build();
+
+        // create a fixed landscape activity
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task)
+                .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE).build();
+
+        // check that both the configuration and app bounds are landscape
+        assertEquals(ORIENTATION_LANDSCAPE, activity.getConfiguration().orientation);
+        assertTrue(activity.getConfiguration().windowConfiguration.getAppBounds().width()
+                > activity.getConfiguration().windowConfiguration.getAppBounds().height());
+    }
+
+    @Test
     public void testSetVisibility_visibleToVisible() {
         final ActivityRecord activity = new ActivityBuilder(mAtm)
                 .setCreateTask(true).build();
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index 741f33f..8ca14bc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -26,6 +26,10 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.wm.ActivityRecord.State.PAUSED;
+import static com.android.server.wm.ActivityRecord.State.PAUSING;
+import static com.android.server.wm.ActivityRecord.State.RESUMED;
+import static com.android.server.wm.ActivityRecord.State.STOPPING;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -244,7 +248,7 @@
                 .setTask(mRootWindowContainer.getDefaultTaskDisplayArea().getOrCreateRootHomeTask())
                 .build();
         final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
-        activity.setState(Task.ActivityState.RESUMED, "test");
+        activity.setState(RESUMED, "test");
         mSupervisor.endDeferResume();
 
         assertEquals(activity.app, mAtm.mInternal.getTopApp());
@@ -254,13 +258,13 @@
         activity.mVisibleRequested = false;
         activity.setVisible(false);
         activity.getTask().setPausingActivity(activity);
-        homeActivity.setState(Task.ActivityState.PAUSED, "test");
+        homeActivity.setState(PAUSED, "test");
 
         // Even the visibility states are invisible, the next activity should be resumed because
         // the crashed activity was pausing.
         mAtm.mInternal.handleAppDied(activity.app, false /* restarting */,
                 null /* finishInstrumentationCallback */);
-        assertEquals(Task.ActivityState.RESUMED, homeActivity.getState());
+        assertEquals(RESUMED, homeActivity.getState());
         assertEquals(homeActivity.app, mAtm.mInternal.getTopApp());
     }
 
@@ -271,7 +275,7 @@
         final Task rootHomeTask = mWm.mRoot.getDefaultTaskDisplayArea().getOrCreateRootHomeTask();
         final ActivityRecord homeActivity = new ActivityBuilder(mAtm).setTask(rootHomeTask).build();
         final ActivityRecord topActivity = new ActivityBuilder(mAtm).setCreateTask(true).build();
-        topActivity.setState(Task.ActivityState.RESUMED, "test");
+        topActivity.setState(RESUMED, "test");
 
         final Consumer<ActivityRecord> assertTopNonSleeping = activity -> {
             assertFalse(mAtm.mInternal.isSleeping());
@@ -287,7 +291,7 @@
         verify(mSupervisor.mGoingToSleepWakeLock).acquire();
         doReturn(true).when(mSupervisor.mGoingToSleepWakeLock).isHeld();
 
-        assertEquals(Task.ActivityState.PAUSING, topActivity.getState());
+        assertEquals(PAUSING, topActivity.getState());
         assertTrue(mAtm.mInternal.isSleeping());
         assertEquals(ActivityManager.PROCESS_STATE_TOP_SLEEPING,
                 mAtm.mInternal.getTopProcessState());
@@ -298,7 +302,7 @@
         final Task topRootTask = topActivity.getRootTask();
         doReturn(true).when(rootHomeTask).goToSleepIfPossible(anyBoolean());
         doReturn(true).when(topRootTask).goToSleepIfPossible(anyBoolean());
-        topActivity.setState(Task.ActivityState.STOPPING, "test");
+        topActivity.setState(STOPPING, "test");
         topActivity.activityStopped(null /* newIcicle */, null /* newPersistentState */,
                 null /* description */);
         verify(mSupervisor.mGoingToSleepWakeLock).release();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java
index 31d4612..af21e02 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java
@@ -35,10 +35,10 @@
 import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
 import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_LAST;
 import static android.window.DisplayAreaOrganizer.FEATURE_WINDOWED_MAGNIFICATION;
+import static android.window.DisplayAreaOrganizer.KEY_ROOT_DISPLAY_AREA_ID;
 
 import static com.android.server.wm.DisplayArea.Type.ABOVE_TASKS;
 import static com.android.server.wm.DisplayAreaPolicyBuilder.Feature;
-import static com.android.server.wm.DisplayAreaPolicyBuilder.KEY_ROOT_DISPLAY_AREA_ID;
 
 import static com.google.common.truth.Truth.assertThat;
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
index d5628fc..c4faaa3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
@@ -61,6 +61,7 @@
 import android.view.SurfaceControl;
 import android.view.View;
 import android.view.WindowManager;
+import android.window.DisplayAreaInfo;
 import android.window.IDisplayAreaOrganizer;
 
 import com.google.android.collect.Lists;
@@ -566,6 +567,31 @@
                 .onDisplayAreaVanished(mockDisplayAreaOrganizer, displayArea);
     }
 
+    @Test
+    public void testGetDisplayAreaInfo() {
+        final DisplayArea<WindowContainer> displayArea = new DisplayArea<>(
+                mWm, BELOW_TASKS, "NewArea", FEATURE_VENDOR_FIRST);
+        mDisplayContent.addChild(displayArea, 0);
+        final DisplayAreaInfo info = displayArea.getDisplayAreaInfo();
+
+        assertThat(info.token).isEqualTo(displayArea.mRemoteToken.toWindowContainerToken());
+        assertThat(info.configuration).isEqualTo(displayArea.getConfiguration());
+        assertThat(info.displayId).isEqualTo(mDisplayContent.getDisplayId());
+        assertThat(info.featureId).isEqualTo(displayArea.mFeatureId);
+        assertThat(info.rootDisplayAreaId).isEqualTo(mDisplayContent.mFeatureId);
+
+        final TaskDisplayArea tda = mDisplayContent.getDefaultTaskDisplayArea();
+        final int tdaIndex = tda.getParent().mChildren.indexOf(tda);
+        final RootDisplayArea root =
+                new DisplayAreaGroup(mWm, "TestRoot", FEATURE_VENDOR_FIRST + 1);
+        mDisplayContent.addChild(root, tdaIndex + 1);
+        displayArea.reparent(root, 0);
+
+        final DisplayAreaInfo info2 = displayArea.getDisplayAreaInfo();
+
+        assertThat(info2.rootDisplayAreaId).isEqualTo(root.mFeatureId);
+    }
+
     private static class TestDisplayArea<T extends WindowContainer> extends DisplayArea<T> {
         private TestDisplayArea(WindowManagerService wms, Rect bounds) {
             super(wms, ANY, "half display area");
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index d498d46..fb71dcb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1528,7 +1528,7 @@
         unblockDisplayRotation(mDisplayContent);
         final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true).build();
         app.setVisible(false);
-        app.setState(Task.ActivityState.RESUMED, "test");
+        app.setState(ActivityRecord.State.RESUMED, "test");
         mDisplayContent.prepareAppTransition(WindowManager.TRANSIT_OPEN);
         mDisplayContent.mOpeningApps.add(app);
         final int newOrientation = getRotatedOrientation(mDisplayContent);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index 03304bb..3741d49 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -24,7 +24,7 @@
 import static android.view.Surface.ROTATION_0;
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewRootImpl.INSETS_LAYOUT_GENERALIZATION;
 import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
@@ -50,13 +50,13 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertThat;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.spy;
 import static org.testng.Assert.expectThrows;
 
 import android.graphics.Insets;
-import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
 import android.util.Pair;
@@ -69,7 +69,6 @@
 import android.view.RoundedCorners;
 import android.view.WindowInsets.Side;
 import android.view.WindowInsets.Type;
-import android.view.WindowManager;
 
 import androidx.test.filters.SmallTest;
 
@@ -109,12 +108,7 @@
         mWindow = spy(createWindow(null, TYPE_APPLICATION, "window"));
         // We only test window frames set by DisplayPolicy, so here prevents computeFrameLw from
         // changing those frames.
-        doNothing().when(mWindow).computeFrame();
-
-        final WindowManager.LayoutParams attrs = mWindow.mAttrs;
-        attrs.width = MATCH_PARENT;
-        attrs.height = MATCH_PARENT;
-        attrs.format = PixelFormat.TRANSLUCENT;
+        doNothing().when(mWindow).computeFrame(any());
 
         spyOn(mStatusBarWindow);
         spyOn(mNavBarWindow);
@@ -219,7 +213,7 @@
     }
 
     @Test
-    public void addingWindow_ignoresInsetsTypes_InWindowTypeWithPredefinedInsets() {
+    public void addingWindow_InWindowTypeWithPredefinedInsets() {
         mDisplayPolicy.removeWindowLw(mStatusBarWindow);  // Removes the existing one.
         WindowState win = createWindow(null, TYPE_STATUS_BAR, "StatusBar");
         win.mAttrs.providesInsetsTypes = new int[]{ITYPE_STATUS_BAR};
@@ -230,7 +224,13 @@
 
         InsetsSourceProvider provider =
                 mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_STATUS_BAR);
-        assertNotEquals(new Rect(0, 0, 500, 100), provider.getSource().getFrame());
+        if (INSETS_LAYOUT_GENERALIZATION) {
+            // In the new flexible insets setup, the insets frame should always respect the window
+            // layout result.
+            assertEquals(new Rect(0, 0, 500, 100), provider.getSource().getFrame());
+        } else {
+            assertNotEquals(new Rect(0, 0, 500, 100), provider.getSource().getFrame());
+        }
     }
 
     @Test
@@ -733,10 +733,12 @@
 
     @Test
     public void testFixedRotationInsetsSourceFrame() {
+        mDisplayContent.mBaseDisplayHeight = DISPLAY_HEIGHT;
+        mDisplayContent.mBaseDisplayWidth = DISPLAY_WIDTH;
         doReturn((mDisplayContent.getRotation() + 1) % 4).when(mDisplayContent)
                 .rotationForActivityInDifferentOrientation(eq(mWindow.mActivityRecord));
-        mWindow.mAboveInsetsState.addSource(mDisplayContent.getInsetsStateController()
-                .getRawInsetsState().peekSource(ITYPE_STATUS_BAR));
+        mWindow.mAboveInsetsState.set(
+                mDisplayContent.getInsetsStateController().getRawInsetsState());
         final Rect frame = mDisplayPolicy.getInsetsPolicy().getInsetsForWindow(mWindow)
                 .getSource(ITYPE_STATUS_BAR).getFrame();
         mDisplayContent.rotateInDifferentOrientationIfNeeded(mWindow.mActivityRecord);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index b793be7..b998088 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -37,7 +37,6 @@
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
 
 import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_BOTTOM;
-import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_RIGHT;
 import static com.android.server.wm.utils.WmDisplayCutout.NO_CUTOUT;
 
 import static org.junit.Assert.assertEquals;
@@ -107,8 +106,7 @@
 
     @Test
     public void testChooseNavigationColorWindowLw() {
-        final WindowState opaque = createOpaqueFullscreen(false);
-
+        final WindowState candidate = createOpaqueFullscreen(false);
         final WindowState dimmingImTarget = createDimmingDialogWindow(true);
         final WindowState dimmingNonImTarget = createDimmingDialogWindow(false);
 
@@ -116,45 +114,51 @@
         final WindowState invisibleIme = createInputMethodWindow(false, true, false);
         final WindowState imeNonDrawNavBar = createInputMethodWindow(true, false, false);
 
-        // If everything is null, return null
+        // If everything is null, return null.
         assertNull(null, DisplayPolicy.chooseNavigationColorWindowLw(
-                null, null, null, NAV_BAR_BOTTOM));
+                null, null, NAV_BAR_BOTTOM));
 
-        assertEquals(opaque, DisplayPolicy.chooseNavigationColorWindowLw(
-                opaque, opaque, null, NAV_BAR_BOTTOM));
+        // If no IME windows, return candidate window.
+        assertEquals(candidate, DisplayPolicy.chooseNavigationColorWindowLw(
+                candidate, null, NAV_BAR_BOTTOM));
         assertEquals(dimmingImTarget, DisplayPolicy.chooseNavigationColorWindowLw(
-                opaque, dimmingImTarget, null, NAV_BAR_BOTTOM));
+                dimmingImTarget, null, NAV_BAR_BOTTOM));
         assertEquals(dimmingNonImTarget, DisplayPolicy.chooseNavigationColorWindowLw(
-                opaque, dimmingNonImTarget, null, NAV_BAR_BOTTOM));
+                dimmingNonImTarget, null, NAV_BAR_BOTTOM));
 
-        assertEquals(visibleIme, DisplayPolicy.chooseNavigationColorWindowLw(
-                null, null, visibleIme, NAV_BAR_BOTTOM));
-        assertEquals(visibleIme, DisplayPolicy.chooseNavigationColorWindowLw(
-                null, dimmingImTarget, visibleIme, NAV_BAR_BOTTOM));
+        // If IME is not visible, return candidate window.
+        assertEquals(null, DisplayPolicy.chooseNavigationColorWindowLw(
+                null, invisibleIme, NAV_BAR_BOTTOM));
+        assertEquals(candidate, DisplayPolicy.chooseNavigationColorWindowLw(
+                candidate, invisibleIme, NAV_BAR_BOTTOM));
+        assertEquals(dimmingImTarget, DisplayPolicy.chooseNavigationColorWindowLw(
+                dimmingImTarget, invisibleIme, NAV_BAR_BOTTOM));
         assertEquals(dimmingNonImTarget, DisplayPolicy.chooseNavigationColorWindowLw(
-                null, dimmingNonImTarget, visibleIme, NAV_BAR_BOTTOM));
-        assertEquals(visibleIme, DisplayPolicy.chooseNavigationColorWindowLw(
-                opaque, opaque, visibleIme, NAV_BAR_BOTTOM));
-        assertEquals(visibleIme, DisplayPolicy.chooseNavigationColorWindowLw(
-                opaque, dimmingImTarget, visibleIme, NAV_BAR_BOTTOM));
-        assertEquals(dimmingNonImTarget, DisplayPolicy.chooseNavigationColorWindowLw(
-                opaque, dimmingNonImTarget, visibleIme, NAV_BAR_BOTTOM));
+                dimmingNonImTarget, invisibleIme, NAV_BAR_BOTTOM));
 
-        assertEquals(opaque, DisplayPolicy.chooseNavigationColorWindowLw(
-                opaque, opaque, invisibleIme, NAV_BAR_BOTTOM));
-        assertEquals(opaque, DisplayPolicy.chooseNavigationColorWindowLw(
-                opaque, opaque, invisibleIme, NAV_BAR_BOTTOM));
-        assertEquals(opaque, DisplayPolicy.chooseNavigationColorWindowLw(
-                opaque, opaque, visibleIme, NAV_BAR_RIGHT));
+        // If IME is visible, return candidate when the candidate window is not dimming.
+        assertEquals(visibleIme, DisplayPolicy.chooseNavigationColorWindowLw(
+                null, visibleIme, NAV_BAR_BOTTOM));
+        assertEquals(visibleIme, DisplayPolicy.chooseNavigationColorWindowLw(
+                candidate, visibleIme, NAV_BAR_BOTTOM));
+
+        // If IME is visible and the candidate window is dimming, checks whether the dimming window
+        // can be IME tartget or not.
+        assertEquals(visibleIme, DisplayPolicy.chooseNavigationColorWindowLw(
+                dimmingImTarget, visibleIme, NAV_BAR_BOTTOM));
+        assertEquals(dimmingNonImTarget, DisplayPolicy.chooseNavigationColorWindowLw(
+                dimmingNonImTarget, visibleIme, NAV_BAR_BOTTOM));
 
         // Only IME windows that have FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS should be navigation color
         // window.
-        assertEquals(opaque, DisplayPolicy.chooseNavigationColorWindowLw(
-                opaque, opaque, imeNonDrawNavBar, NAV_BAR_BOTTOM));
+        assertEquals(null, DisplayPolicy.chooseNavigationColorWindowLw(
+                null, imeNonDrawNavBar, NAV_BAR_BOTTOM));
+        assertEquals(candidate, DisplayPolicy.chooseNavigationColorWindowLw(
+                candidate, imeNonDrawNavBar, NAV_BAR_BOTTOM));
         assertEquals(dimmingImTarget, DisplayPolicy.chooseNavigationColorWindowLw(
-                opaque, dimmingImTarget, imeNonDrawNavBar, NAV_BAR_BOTTOM));
+                dimmingImTarget, imeNonDrawNavBar, NAV_BAR_BOTTOM));
         assertEquals(dimmingNonImTarget, DisplayPolicy.chooseNavigationColorWindowLw(
-                opaque, dimmingNonImTarget, imeNonDrawNavBar, NAV_BAR_BOTTOM));
+                dimmingNonImTarget, imeNonDrawNavBar, NAV_BAR_BOTTOM));
     }
 
     @UseTestDisplay(addWindows = { W_NAVIGATION_BAR })
@@ -182,50 +186,21 @@
 
         // If there is no window, APPEARANCE_LIGHT_NAVIGATION_BARS is not allowed.
         assertEquals(0,
-                displayPolicy.updateLightNavigationBarLw(
-                        APPEARANCE_LIGHT_NAVIGATION_BARS, null, null,
-                        null, null));
-
-        // Opaque top fullscreen window overrides APPEARANCE_LIGHT_NAVIGATION_BARS flag.
-        assertEquals(0, displayPolicy.updateLightNavigationBarLw(
-                0, opaqueDarkNavBar, opaqueDarkNavBar, null, opaqueDarkNavBar));
-        assertEquals(0, displayPolicy.updateLightNavigationBarLw(
-                APPEARANCE_LIGHT_NAVIGATION_BARS, opaqueDarkNavBar, opaqueDarkNavBar, null,
-                opaqueDarkNavBar));
-        assertEquals(APPEARANCE_LIGHT_NAVIGATION_BARS,
-                displayPolicy.updateLightNavigationBarLw(0, opaqueLightNavBar,
-                        opaqueLightNavBar, null, opaqueLightNavBar));
-        assertEquals(APPEARANCE_LIGHT_NAVIGATION_BARS,
-                displayPolicy.updateLightNavigationBarLw(APPEARANCE_LIGHT_NAVIGATION_BARS,
-                        opaqueLightNavBar, opaqueLightNavBar, null, opaqueLightNavBar));
+                displayPolicy.updateLightNavigationBarLw(APPEARANCE_LIGHT_NAVIGATION_BARS, null));
 
         // Dimming window clears APPEARANCE_LIGHT_NAVIGATION_BARS.
+        assertEquals(0, displayPolicy.updateLightNavigationBarLw(0, dimming));
         assertEquals(0, displayPolicy.updateLightNavigationBarLw(
-                0, opaqueDarkNavBar, dimming, null, dimming));
-        assertEquals(0, displayPolicy.updateLightNavigationBarLw(
-                0, opaqueLightNavBar, dimming, null, dimming));
-        assertEquals(0, displayPolicy.updateLightNavigationBarLw(
-                APPEARANCE_LIGHT_NAVIGATION_BARS, opaqueDarkNavBar, dimming, null, dimming));
-        assertEquals(0, displayPolicy.updateLightNavigationBarLw(
-                APPEARANCE_LIGHT_NAVIGATION_BARS, opaqueLightNavBar, dimming, null, dimming));
-        assertEquals(0, displayPolicy.updateLightNavigationBarLw(
-                APPEARANCE_LIGHT_NAVIGATION_BARS, opaqueLightNavBar, dimming, imeDrawLightNavBar,
-                dimming));
+                APPEARANCE_LIGHT_NAVIGATION_BARS, dimming));
 
-        // IME window clears APPEARANCE_LIGHT_NAVIGATION_BARS
+        // Control window overrides APPEARANCE_LIGHT_NAVIGATION_BARS flag.
+        assertEquals(0, displayPolicy.updateLightNavigationBarLw(0, opaqueDarkNavBar));
         assertEquals(0, displayPolicy.updateLightNavigationBarLw(
-                APPEARANCE_LIGHT_NAVIGATION_BARS, null, null, imeDrawDarkNavBar,
-                imeDrawDarkNavBar));
-
-        // Even if the top fullscreen has APPEARANCE_LIGHT_NAVIGATION_BARS, IME window wins.
-        assertEquals(0, displayPolicy.updateLightNavigationBarLw(
-                APPEARANCE_LIGHT_NAVIGATION_BARS, opaqueLightNavBar, opaqueLightNavBar,
-                imeDrawDarkNavBar, imeDrawDarkNavBar));
-
-        // IME window should be able to use APPEARANCE_LIGHT_NAVIGATION_BARS.
-        assertEquals(APPEARANCE_LIGHT_NAVIGATION_BARS,
-                displayPolicy.updateLightNavigationBarLw(0, opaqueDarkNavBar,
-                        opaqueDarkNavBar, imeDrawLightNavBar, imeDrawLightNavBar));
+                APPEARANCE_LIGHT_NAVIGATION_BARS, opaqueDarkNavBar));
+        assertEquals(APPEARANCE_LIGHT_NAVIGATION_BARS, displayPolicy.updateLightNavigationBarLw(
+                0, opaqueLightNavBar));
+        assertEquals(APPEARANCE_LIGHT_NAVIGATION_BARS, displayPolicy.updateLightNavigationBarLw(
+                APPEARANCE_LIGHT_NAVIGATION_BARS, opaqueLightNavBar));
     }
 
     @UseTestDisplay(addWindows = W_ACTIVITY)
@@ -313,7 +288,9 @@
         displayInfo.logicalHeight = 2000;
         displayInfo.rotation = ROTATION_0;
 
-        displayPolicy.addWindowLw(mNavBarWindow, mNavBarWindow.mAttrs);
+        WindowManager.LayoutParams attrs = mNavBarWindow.mAttrs;
+        displayPolicy.addWindowLw(mNavBarWindow, attrs);
+        mNavBarWindow.setRequestedSize(attrs.width, attrs.height);
         mNavBarWindow.getControllableInsetProvider().setServerVisible(true);
         final InsetsState state = mDisplayContent.getInsetsStateController().getRawInsetsState();
         mImeWindow.mAboveInsetsState.set(state);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java
index 683ed88..3982a83 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java
@@ -58,8 +58,6 @@
     static final int DISPLAY_HEIGHT = 1000;
     static final int DISPLAY_DENSITY = 320;
 
-    static final int STATUS_BAR_HEIGHT = 10;
-    static final int NAV_BAR_HEIGHT = 15;
     static final int DISPLAY_CUTOUT_HEIGHT = 8;
     static final int IME_HEIGHT = 415;
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java
index f2418c6..a8ede13 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java
@@ -82,7 +82,7 @@
             mWm.mWindowContextListenerController.registerWindowContainerListener(clientToken,
                     dc.getImeContainer(), 1000 /* ownerUid */, TYPE_INPUT_METHOD_DIALOG,
                     null /* options */);
-            return true;
+            return dc.getImeContainer().getConfiguration();
         }).when(wms).attachWindowContextToDisplayArea(any(), eq(TYPE_INPUT_METHOD_DIALOG),
                 anyInt(), any());
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index bf3ed69..5b04c91 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -18,6 +18,7 @@
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
@@ -80,7 +81,7 @@
     }
 
     @Test
-    public void testControlsForDispatch_dockedStackVisible() {
+    public void testControlsForDispatch_dockedTaskVisible() {
         addWindow(TYPE_STATUS_BAR, "statusBar");
         addWindow(TYPE_NAVIGATION_BAR, "navBar");
 
@@ -93,7 +94,20 @@
     }
 
     @Test
-    public void testControlsForDispatch_freeformStackVisible() {
+    public void testControlsForDispatch_multiWindowTaskVisible() {
+        addWindow(TYPE_STATUS_BAR, "statusBar");
+        addWindow(TYPE_NAVIGATION_BAR, "navBar");
+
+        final WindowState win = createWindow(null, WINDOWING_MODE_MULTI_WINDOW,
+                ACTIVITY_TYPE_STANDARD, TYPE_APPLICATION, mDisplayContent, "app");
+        final InsetsSourceControl[] controls = addWindowAndGetControlsForDispatch(win);
+
+        // The app must not control any system bars.
+        assertNull(controls);
+    }
+
+    @Test
+    public void testControlsForDispatch_freeformTaskVisible() {
         addWindow(TYPE_STATUS_BAR, "statusBar");
         addWindow(TYPE_NAVIGATION_BAR, "navBar");
 
@@ -101,18 +115,6 @@
                 ACTIVITY_TYPE_STANDARD, TYPE_APPLICATION, mDisplayContent, "app");
         final InsetsSourceControl[] controls = addWindowAndGetControlsForDispatch(win);
 
-        // The app must not control any bars.
-        assertNull(controls);
-    }
-
-    @Test
-    public void testControlsForDispatch_dockedDividerControllerResizing() {
-        addWindow(TYPE_STATUS_BAR, "statusBar");
-        addWindow(TYPE_NAVIGATION_BAR, "navBar");
-        mDisplayContent.getDockedDividerController().setResizing(true);
-
-        final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch();
-
         // The app must not control any system bars.
         assertNull(controls);
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
index 647a898..609d159 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
@@ -200,28 +200,37 @@
         assertTrue(mLetterbox.needsApplySurfaceChanges());
 
         mLetterbox.applySurfaceChanges(mTransaction);
-        verify(mTransaction).setAlpha(mSurfaces.top, mDarkScrimAlpha);
+        verify(mTransaction).setAlpha(mSurfaces.fullWindowSurface, mDarkScrimAlpha);
     }
 
     @Test
-    public void testApplySurfaceChanges_cornersNotRounded_surfaceBehindNotCreated() {
+    public void testApplySurfaceChanges_cornersNotRounded_surfaceFullWindowSurfaceNotCreated() {
         mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
         mLetterbox.applySurfaceChanges(mTransaction);
 
-        assertNull(mSurfaces.behind);
+        assertNull(mSurfaces.fullWindowSurface);
     }
 
     @Test
-    public void testApplySurfaceChanges_cornersRounded_surfaceBehindCreated() {
+    public void testApplySurfaceChanges_cornersRounded_surfaceFullWindowSurfaceCreated() {
         mAreCornersRounded = true;
         mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
         mLetterbox.applySurfaceChanges(mTransaction);
 
-        assertNotNull(mSurfaces.behind);
+        assertNotNull(mSurfaces.fullWindowSurface);
     }
 
     @Test
-    public void testNotIntersectsOrFullyContains_cornersRounded_doesNotCheckSurfaceBehind() {
+    public void testApplySurfaceChanges_wallpaperBackground_surfaceFullWindowSurfaceCreated() {
+        mHasWallpaperBackground = true;
+        mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
+        mLetterbox.applySurfaceChanges(mTransaction);
+
+        assertNotNull(mSurfaces.fullWindowSurface);
+    }
+
+    @Test
+    public void testNotIntersectsOrFullyContains_cornersRounded() {
         mAreCornersRounded = true;
         mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(0, 0));
         mLetterbox.applySurfaceChanges(mTransaction);
@@ -249,8 +258,8 @@
         public SurfaceControl right;
         private SurfaceControl.Builder mBottomBuilder;
         public SurfaceControl bottom;
-        private SurfaceControl.Builder mBehindBuilder;
-        public SurfaceControl behind;
+        private SurfaceControl.Builder mFullWindowSurfaceBuilder;
+        public SurfaceControl fullWindowSurface;
 
         @Override
         public SurfaceControl.Builder get() {
@@ -265,8 +274,8 @@
                     mRightBuilder = (SurfaceControl.Builder) i.getMock();
                 } else if (((String) i.getArgument(0)).contains("bottom")) {
                     mBottomBuilder = (SurfaceControl.Builder) i.getMock();
-                } else if (((String) i.getArgument(0)).contains("behind")) {
-                    mBehindBuilder = (SurfaceControl.Builder) i.getMock();
+                } else if (((String) i.getArgument(0)).contains("fullWindow")) {
+                    mFullWindowSurfaceBuilder = (SurfaceControl.Builder) i.getMock();
                 }
                 return i.getMock();
             });
@@ -281,8 +290,8 @@
                     right = control;
                 } else if (i.getMock() == mBottomBuilder) {
                     bottom = control;
-                } else if (i.getMock() == mBehindBuilder) {
-                    behind = control;
+                } else if (i.getMock() == mFullWindowSurfaceBuilder) {
+                    fullWindowSurface = control;
                 }
                 return control;
             }).when(builder).build();
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
index d017c19..1b19a28 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
@@ -31,8 +31,8 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.wm.ActivityRecord.State.PAUSED;
 import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE;
-import static com.android.server.wm.Task.ActivityState.PAUSED;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 
 import static com.google.common.truth.Truth.assertThat;
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
index 0c6545c..7ebf775 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
@@ -36,22 +36,23 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.wm.ActivityRecord.State.DESTROYED;
+import static com.android.server.wm.ActivityRecord.State.DESTROYING;
+import static com.android.server.wm.ActivityRecord.State.FINISHING;
+import static com.android.server.wm.ActivityRecord.State.INITIALIZING;
+import static com.android.server.wm.ActivityRecord.State.PAUSING;
+import static com.android.server.wm.ActivityRecord.State.RESUMED;
+import static com.android.server.wm.ActivityRecord.State.STOPPED;
+import static com.android.server.wm.ActivityRecord.State.STOPPING;
 import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_FREE_RESIZE;
 import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
-import static com.android.server.wm.Task.ActivityState.DESTROYED;
-import static com.android.server.wm.Task.ActivityState.DESTROYING;
-import static com.android.server.wm.Task.ActivityState.FINISHING;
-import static com.android.server.wm.Task.ActivityState.PAUSING;
-import static com.android.server.wm.Task.ActivityState.RESUMED;
-import static com.android.server.wm.Task.ActivityState.STOPPED;
-import static com.android.server.wm.Task.ActivityState.STOPPING;
 import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG;
 import static com.android.server.wm.Task.REPARENT_KEEP_ROOT_TASK_AT_FRONT;
 import static com.android.server.wm.Task.REPARENT_MOVE_ROOT_TASK_TO_FRONT;
-import static com.android.server.wm.Task.TASK_VISIBILITY_INVISIBLE;
-import static com.android.server.wm.Task.TASK_VISIBILITY_VISIBLE;
-import static com.android.server.wm.Task.TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT;
 import static com.android.server.wm.TaskDisplayArea.getRootTaskAbove;
+import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_INVISIBLE;
+import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE;
+import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT;
 import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
 import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
@@ -280,11 +281,11 @@
     public void testResumedActivity() {
         final ActivityRecord r = new ActivityBuilder(mAtm).setCreateTask(true).build();
         final Task task = r.getTask();
-        assertNull(task.getResumedActivity());
+        assertNull(task.getTopResumedActivity());
         r.setState(RESUMED, "testResumedActivity");
-        assertEquals(r, task.getResumedActivity());
+        assertEquals(r, task.getTopResumedActivity());
         r.setState(PAUSING, "testResumedActivity");
-        assertNull(task.getResumedActivity());
+        assertNull(task.getTopResumedActivity());
     }
 
     @Test
@@ -295,15 +296,15 @@
         final Task task = r.getTask();
         // Ensure moving task between two root tasks updates resumed activity
         r.setState(RESUMED, "testResumedActivityFromTaskReparenting");
-        assertEquals(r, rootTask.getResumedActivity());
+        assertEquals(r, rootTask.getTopResumedActivity());
 
         final Task destRootTask = new TaskBuilder(mSupervisor).setOnTop(true).build();
         task.reparent(destRootTask, true /* toTop */, REPARENT_KEEP_ROOT_TASK_AT_FRONT,
                 false /* animate */, true /* deferResume*/,
                 "testResumedActivityFromTaskReparenting");
 
-        assertNull(rootTask.getResumedActivity());
-        assertEquals(r, destRootTask.getResumedActivity());
+        assertNull(rootTask.getTopResumedActivity());
+        assertEquals(r, destRootTask.getTopResumedActivity());
     }
 
     @Test
@@ -314,15 +315,15 @@
         final Task task = r.getTask();
         // Ensure moving task between two root tasks updates resumed activity
         r.setState(RESUMED, "testResumedActivityFromActivityReparenting");
-        assertEquals(r, rootTask.getResumedActivity());
+        assertEquals(r, rootTask.getTopResumedActivity());
 
         final Task destRootTask = new TaskBuilder(mSupervisor).setOnTop(true).build();
         task.reparent(destRootTask, true /*toTop*/, REPARENT_MOVE_ROOT_TASK_TO_FRONT,
                 false /* animate */, false /* deferResume*/,
                 "testResumedActivityFromActivityReparenting");
 
-        assertNull(rootTask.getResumedActivity());
-        assertEquals(r, destRootTask.getResumedActivity());
+        assertNull(rootTask.getTopResumedActivity());
+        assertEquals(r, destRootTask.getTopResumedActivity());
     }
 
     @Test
@@ -618,9 +619,9 @@
         doReturn(false).when(splitScreenSecondary2).isTranslucent(any());
         assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */));
         assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */));
-        assertEquals(TASK_VISIBILITY_INVISIBLE,
+        assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE,
                 splitScreenSecondary.getVisibility(null /* starting */));
-        assertEquals(TASK_VISIBILITY_VISIBLE,
+        assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE,
                 splitScreenSecondary2.getVisibility(null /* starting */));
 
         // First split-screen secondary should be visible behind another translucent split-screen
@@ -628,9 +629,9 @@
         doReturn(true).when(splitScreenSecondary2).isTranslucent(any());
         assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */));
         assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */));
-        assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
+        assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
                 splitScreenSecondary.getVisibility(null /* starting */));
-        assertEquals(TASK_VISIBILITY_VISIBLE,
+        assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE,
                 splitScreenSecondary2.getVisibility(null /* starting */));
 
         final Task assistantRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
@@ -642,13 +643,13 @@
         assertFalse(splitScreenPrimary.shouldBeVisible(null /* starting */));
         assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */));
         assertFalse(splitScreenSecondary2.shouldBeVisible(null /* starting */));
-        assertEquals(TASK_VISIBILITY_VISIBLE,
+        assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE,
                 assistantRootTask.getVisibility(null /* starting */));
-        assertEquals(TASK_VISIBILITY_INVISIBLE,
+        assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE,
                 splitScreenPrimary.getVisibility(null /* starting */));
-        assertEquals(TASK_VISIBILITY_INVISIBLE,
+        assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE,
                 splitScreenSecondary.getVisibility(null /* starting */));
-        assertEquals(TASK_VISIBILITY_INVISIBLE,
+        assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE,
                 splitScreenSecondary2.getVisibility(null /* starting */));
 
         // Split-screen root tasks should be visible behind a translucent fullscreen root task.
@@ -657,13 +658,13 @@
         assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */));
         assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */));
         assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */));
-        assertEquals(TASK_VISIBILITY_VISIBLE,
+        assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE,
                 assistantRootTask.getVisibility(null /* starting */));
-        assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
+        assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
                 splitScreenPrimary.getVisibility(null /* starting */));
-        assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
+        assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
                 splitScreenSecondary.getVisibility(null /* starting */));
-        assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
+        assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
                 splitScreenSecondary2.getVisibility(null /* starting */));
 
         // Assistant root task shouldn't be visible behind translucent split-screen root task,
@@ -678,25 +679,25 @@
             assertTrue(assistantRootTask.shouldBeVisible(null /* starting */));
             assertFalse(splitScreenPrimary.shouldBeVisible(null /* starting */));
             assertFalse(splitScreenSecondary2.shouldBeVisible(null /* starting */));
-            assertEquals(TASK_VISIBILITY_VISIBLE,
+            assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE,
                     assistantRootTask.getVisibility(null /* starting */));
-            assertEquals(TASK_VISIBILITY_INVISIBLE,
+            assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE,
                     splitScreenPrimary.getVisibility(null /* starting */));
-            assertEquals(TASK_VISIBILITY_INVISIBLE,
+            assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE,
                     splitScreenSecondary.getVisibility(null /* starting */));
-            assertEquals(TASK_VISIBILITY_INVISIBLE,
+            assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE,
                     splitScreenSecondary2.getVisibility(null /* starting */));
         } else {
             assertFalse(assistantRootTask.shouldBeVisible(null /* starting */));
             assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */));
             assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */));
-            assertEquals(TASK_VISIBILITY_INVISIBLE,
+            assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE,
                     assistantRootTask.getVisibility(null /* starting */));
-            assertEquals(TASK_VISIBILITY_VISIBLE,
+            assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE,
                     splitScreenPrimary.getVisibility(null /* starting */));
-            assertEquals(TASK_VISIBILITY_INVISIBLE,
+            assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE,
                     splitScreenSecondary.getVisibility(null /* starting */));
-            assertEquals(TASK_VISIBILITY_VISIBLE,
+            assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE,
                     splitScreenSecondary2.getVisibility(null /* starting */));
         }
     }
@@ -707,45 +708,51 @@
                 WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, true /* onTop */);
         final Task splitPrimary = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
                 WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED, true /* onTop */);
+        // Creating as two-level tasks so home task can be reparented to split-secondary root task.
         final Task splitSecondary = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_UNDEFINED, true /* onTop */);
+                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_UNDEFINED, true /* onTop */,
+                true /* twoLevelTask */);
 
         doReturn(false).when(homeRootTask).isTranslucent(any());
         doReturn(false).when(splitPrimary).isTranslucent(any());
         doReturn(false).when(splitSecondary).isTranslucent(any());
 
-
         // Re-parent home to split secondary.
         homeRootTask.reparent(splitSecondary, POSITION_TOP);
         // Current tasks should be visible.
-        assertEquals(TASK_VISIBILITY_VISIBLE, splitPrimary.getVisibility(null /* starting */));
-        assertEquals(TASK_VISIBILITY_VISIBLE, splitSecondary.getVisibility(null /* starting */));
+        assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE,
+                splitPrimary.getVisibility(null /* starting */));
+        assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE,
+                splitSecondary.getVisibility(null /* starting */));
         // Home task should still be visible even though it is a child of another visible task.
-        assertEquals(TASK_VISIBILITY_VISIBLE, homeRootTask.getVisibility(null /* starting */));
+        assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE,
+                homeRootTask.getVisibility(null /* starting */));
 
 
         // Add fullscreen translucent task that partially occludes split tasks
         final Task translucentRootTask = createStandardRootTaskForVisibilityTest(
                 WINDOWING_MODE_FULLSCREEN, true /* translucent */);
         // Fullscreen translucent task should be visible
-        assertEquals(TASK_VISIBILITY_VISIBLE,
+        assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE,
                 translucentRootTask.getVisibility(null /* starting */));
         // Split tasks should be visible behind translucent
-        assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
+        assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
                 splitPrimary.getVisibility(null /* starting */));
-        assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
+        assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
                 splitSecondary.getVisibility(null /* starting */));
         // Home task should be visible behind translucent since its parent is visible behind
         // translucent.
-        assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
+        assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
                 homeRootTask.getVisibility(null /* starting */));
 
 
         // Hide split-secondary
         splitSecondary.setForceHidden(FLAG_FORCE_HIDDEN_FOR_TASK_ORG, true /* set */);
         // Home split secondary and home task should be invisible.
-        assertEquals(TASK_VISIBILITY_INVISIBLE, splitSecondary.getVisibility(null /* starting */));
-        assertEquals(TASK_VISIBILITY_INVISIBLE, homeRootTask.getVisibility(null /* starting */));
+        assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE,
+                splitSecondary.getVisibility(null /* starting */));
+        assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE,
+                homeRootTask.getVisibility(null /* starting */));
     }
 
     @Test
@@ -757,9 +764,9 @@
                 createStandardRootTaskForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
                         true /* translucent */);
 
-        assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
+        assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
                 bottomRootTask.getVisibility(null /* starting */));
-        assertEquals(TASK_VISIBILITY_VISIBLE,
+        assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE,
                 translucentRootTask.getVisibility(null /* starting */));
     }
 
@@ -775,10 +782,12 @@
                 createStandardRootTaskForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
                         false /* translucent */);
 
-        assertEquals(TASK_VISIBILITY_INVISIBLE, bottomRootTask.getVisibility(null /* starting */));
-        assertEquals(TASK_VISIBILITY_INVISIBLE,
+        assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE,
+                bottomRootTask.getVisibility(null /* starting */));
+        assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE,
                 translucentRootTask.getVisibility(null /* starting */));
-        assertEquals(TASK_VISIBILITY_VISIBLE, opaqueRootTask.getVisibility(null /* starting */));
+        assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE,
+                opaqueRootTask.getVisibility(null /* starting */));
     }
 
     @Test
@@ -793,10 +802,11 @@
                 createStandardRootTaskForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
                         true /* translucent */);
 
-        assertEquals(TASK_VISIBILITY_INVISIBLE, bottomRootTask.getVisibility(null /* starting */));
-        assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
+        assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE,
+                bottomRootTask.getVisibility(null /* starting */));
+        assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
                 opaqueRootTask.getVisibility(null /* starting */));
-        assertEquals(TASK_VISIBILITY_VISIBLE,
+        assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE,
                 translucentRootTask.getVisibility(null /* starting */));
     }
 
@@ -809,9 +819,9 @@
                 createStandardRootTaskForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
                         true /* translucent */);
 
-        assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
+        assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
                 bottomTranslucentRootTask.getVisibility(null /* starting */));
-        assertEquals(TASK_VISIBILITY_VISIBLE,
+        assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE,
                 translucentRootTask.getVisibility(null /* starting */));
     }
 
@@ -824,9 +834,10 @@
                 createStandardRootTaskForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
                         false /* translucent */);
 
-        assertEquals(TASK_VISIBILITY_INVISIBLE,
+        assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE,
                 bottomTranslucentRootTask.getVisibility(null /* starting */));
-        assertEquals(TASK_VISIBILITY_VISIBLE, opaqueRootTask.getVisibility(null /* starting */));
+        assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE,
+                opaqueRootTask.getVisibility(null /* starting */));
     }
 
     @Test
@@ -840,16 +851,17 @@
         final Task pinnedRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
                 WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
 
-        assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
+        assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
                 bottomRootTask.getVisibility(null /* starting */));
-        assertEquals(TASK_VISIBILITY_VISIBLE,
+        assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE,
                 translucentRootTask.getVisibility(null /* starting */));
         // Add an activity to the pinned root task so it isn't considered empty for visibility
         // check.
         final ActivityRecord pinnedActivity = new ActivityBuilder(mAtm)
                 .setTask(pinnedRootTask)
                 .build();
-        assertEquals(TASK_VISIBILITY_VISIBLE, pinnedRootTask.getVisibility(null /* starting */));
+        assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE,
+                pinnedRootTask.getVisibility(null /* starting */));
     }
 
     @Test
@@ -1142,12 +1154,12 @@
         } else if (twoLevelTask) {
             task = new TaskBuilder(mSupervisor)
                     .setTaskDisplayArea(taskDisplayArea)
-                    .setWindowingMode(windowingMode)
                     .setActivityType(activityType)
                     .setOnTop(onTop)
                     .setCreateActivity(true)
                     .setCreateParentTask(true)
                     .build().getRootTask();
+            task.setWindowingMode(windowingMode);
         } else {
             task = new TaskBuilder(mSupervisor)
                     .setTaskDisplayArea(taskDisplayArea)
@@ -1301,9 +1313,9 @@
         final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(task).build();
         topActivity.info.flags |= FLAG_RESUME_WHILE_PAUSING;
 
-        task.startPausingLocked(false /* uiSleeping */, topActivity,
+        task.startPausing(false /* uiSleeping */, topActivity,
                 "test");
-        verify(task).completePauseLocked(anyBoolean(), eq(topActivity));
+        verify(task).completePause(anyBoolean(), eq(topActivity));
     }
 
     @Test
@@ -1544,7 +1556,7 @@
             activities[i] = r;
             doReturn(null).when(mAtm).getProcessController(
                     eq(r.processName), eq(r.info.applicationInfo.uid));
-            r.setState(Task.ActivityState.INITIALIZING, "test");
+            r.setState(INITIALIZING, "test");
             // Ensure precondition that the activity is opaque.
             assertTrue(r.occludesParent());
             mSupervisor.startSpecificActivity(r, false /* andResume */,
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index 9cf29d4..8d4acbb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -31,13 +31,14 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.server.wm.ActivityRecord.State.FINISHING;
+import static com.android.server.wm.ActivityRecord.State.PAUSED;
+import static com.android.server.wm.ActivityRecord.State.PAUSING;
+import static com.android.server.wm.ActivityRecord.State.RESUMED;
+import static com.android.server.wm.ActivityRecord.State.STOPPED;
+import static com.android.server.wm.ActivityRecord.State.STOPPING;
 import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
 import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_RECENT_TASKS_AND_RESTORE;
-import static com.android.server.wm.Task.ActivityState.FINISHING;
-import static com.android.server.wm.Task.ActivityState.PAUSED;
-import static com.android.server.wm.Task.ActivityState.PAUSING;
-import static com.android.server.wm.Task.ActivityState.STOPPED;
-import static com.android.server.wm.Task.ActivityState.STOPPING;
 import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -365,7 +366,7 @@
         TaskDisplayArea defaultTaskDisplayArea = display.getDefaultTaskDisplayArea();
         doReturn(isFocusedTask ? task : null).when(defaultTaskDisplayArea).getFocusedRootTask();
         mRootWindowContainer.applySleepTokens(true);
-        verify(task, times(expectWakeFromSleep ? 1 : 0)).awakeFromSleepingLocked();
+        verify(task, times(expectWakeFromSleep ? 1 : 0)).awakeFromSleeping();
         verify(task, times(expectResumeTopActivity ? 1 : 0)).resumeTopActivityUncheckedLocked(
                 null /* target */, null /* targetOptions */);
     }
@@ -386,7 +387,7 @@
         // landscape and the portrait lockscreen is shown.
         activity.setLastReportedConfiguration(
                 new MergedConfiguration(mAtm.getGlobalConfiguration(), rotatedConfig));
-        activity.setState(Task.ActivityState.STOPPED, "sleep");
+        activity.setState(STOPPED, "sleep");
 
         display.setIsSleeping(true);
         doReturn(false).when(display).shouldSleep();
@@ -557,8 +558,8 @@
         doReturn(rootTask).when(mRootWindowContainer).getTopDisplayFocusedRootTask();
 
         // Use the task as target to resume.
-        mRootWindowContainer.resumeFocusedTasksTopActivities(
-                rootTask, activity, null /* targetOptions */);
+        mRootWindowContainer.resumeFocusedTasksTopActivities(rootTask, activity,
+                null /* targetOptions */);
 
         // Verify the target task should resume its activity.
         verify(rootTask, times(1)).resumeTopActivityUncheckedLocked(
@@ -626,7 +627,7 @@
                 ACTIVITY_TYPE_STANDARD, false /* onTop */));
         final ActivityRecord activity = new ActivityBuilder(mAtm)
                 .setTask(rootTask).setOnTop(true).build();
-        activity.setState(Task.ActivityState.RESUMED, "test");
+        activity.setState(RESUMED, "test");
 
         // Assume the task is at the topmost position
         assertTrue(rootTask.isTopRootTaskInDisplayArea());
@@ -646,7 +647,7 @@
                 ACTIVITY_TYPE_STANDARD, false /* onTop */));
         final ActivityRecord activity = new ActivityBuilder(mAtm)
                 .setTask(rootTask).setOnTop(true).build();
-        activity.setState(Task.ActivityState.RESUMED, "test");
+        activity.setState(RESUMED, "test");
         taskDisplayArea.positionChildAt(POSITION_BOTTOM, rootTask, false /*includingParents*/);
 
         // Assume the task is at the topmost position
@@ -774,7 +775,7 @@
     }
 
     /**
-     * Tests that when starting {@link #ResolverActivity} for home, it should use the standard
+     * Tests that when starting {@link ResolverActivity} for home, it should use the standard
      * activity type (in a new root task) so the order of back stack won't be broken.
      */
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index f35e85c..5989497 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -40,8 +40,10 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.wm.ActivityRecord.State.RESTARTING_PROCESS;
+import static com.android.server.wm.ActivityRecord.State.RESUMED;
+import static com.android.server.wm.ActivityRecord.State.STOPPED;
 import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
-import static com.android.server.wm.Task.ActivityState.STOPPED;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -130,7 +132,7 @@
         doNothing().when(mSupervisor).scheduleRestartTimeout(mActivity);
         mActivity.mVisibleRequested = true;
         mActivity.setSavedState(null /* savedState */);
-        mActivity.setState(Task.ActivityState.RESUMED, "testRestart");
+        mActivity.setState(RESUMED, "testRestart");
         prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED);
 
         final Rect originalOverrideBounds = new Rect(mActivity.getBounds());
@@ -138,7 +140,7 @@
         // The visible activity should recompute configuration according to the last parent bounds.
         mAtm.mActivityClientController.restartActivityProcessIfVisible(mActivity.appToken);
 
-        assertEquals(Task.ActivityState.RESTARTING_PROCESS, mActivity.getState());
+        assertEquals(RESTARTING_PROCESS, mActivity.getState());
         assertNotEquals(originalOverrideBounds, mActivity.getBounds());
     }
 
@@ -585,7 +587,7 @@
     public void testHandleActivitySizeCompatModeChanged() {
         setUpDisplaySizeWithApp(1000, 2000);
         doReturn(true).when(mTask).isOrganized();
-        mActivity.setState(Task.ActivityState.RESUMED, "testHandleActivitySizeCompatModeChanged");
+        mActivity.setState(RESUMED, "testHandleActivitySizeCompatModeChanged");
         prepareUnresizable(mActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
         assertFitted();
 
@@ -604,7 +606,7 @@
         mActivity.mVisibleRequested = true;
         mActivity.restartProcessIfVisible();
         // The full lifecycle isn't hooked up so manually set state to resumed
-        mActivity.setState(Task.ActivityState.RESUMED, "testHandleActivitySizeCompatModeChanged");
+        mActivity.setState(RESUMED, "testHandleActivitySizeCompatModeChanged");
         mTask.mDisplayContent.handleActivitySizeCompatModeIfNeeded(mActivity);
 
         // Expect null token when switching to non-size-compat mode activity.
@@ -618,7 +620,7 @@
     public void testHandleActivitySizeCompatModeChangedOnDifferentTask() {
         setUpDisplaySizeWithApp(1000, 2000);
         doReturn(true).when(mTask).isOrganized();
-        mActivity.setState(Task.ActivityState.RESUMED, "testHandleActivitySizeCompatModeChanged");
+        mActivity.setState(RESUMED, "testHandleActivitySizeCompatModeChanged");
         prepareUnresizable(mActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
         assertFitted();
 
@@ -637,7 +639,7 @@
                 .setCreateActivity(true).build();
         final ActivityRecord secondActivity = secondTask.getTopNonFinishingActivity();
         doReturn(true).when(secondTask).isOrganized();
-        secondActivity.setState(Task.ActivityState.RESUMED,
+        secondActivity.setState(RESUMED,
                 "testHandleActivitySizeCompatModeChanged");
         prepareUnresizable(secondActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
 
@@ -1553,6 +1555,30 @@
     }
 
     @Test
+    public void testSandboxDisplayApis_unresizableAppNotSandboxed() {
+        // Set up a display in landscape with an unresizable app.
+        setUpDisplaySizeWithApp(2500, 1000);
+        mActivity.mDisplayContent.setSandboxDisplayApis(false /* sandboxDisplayApis */);
+        prepareUnresizable(mActivity, 1.5f, SCREEN_ORIENTATION_LANDSCAPE);
+        assertFitted();
+
+        // Activity max bounds not be sandboxed since sandboxing is disabled.
+        assertMaxBoundsInheritDisplayAreaBounds();
+    }
+
+    @Test
+    public void testSandboxDisplayApis_unresizableAppSandboxed() {
+        // Set up a display in landscape with an unresizable app.
+        setUpDisplaySizeWithApp(2500, 1000);
+        mActivity.mDisplayContent.setSandboxDisplayApis(true /* sandboxDisplayApis */);
+        prepareUnresizable(mActivity, 1.5f, SCREEN_ORIENTATION_LANDSCAPE);
+        assertFitted();
+
+        // Activity max bounds should be sandboxed since sandboxing is enabled.
+        assertActivityMaxBoundsSandboxed();
+    }
+
+    @Test
     public void testResizableApp_notSandboxed() {
         // Set up a display in landscape with a fully resizable app.
         setUpDisplaySizeWithApp(2500, 1000);
diff --git a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
index b89539c..e32b2aa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
+++ b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
@@ -178,6 +178,11 @@
     }
 
     @Override
+    public SurfaceControl.Transaction setDisplayFlags(IBinder displayToken, int flags) {
+        return this;
+    }
+
+    @Override
     public SurfaceControl.Transaction setDisplayProjection(IBinder displayToken,
             int orientation, Rect layerStackRect, Rect displayRect) {
         return this;
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
index 67b273a..c45c18d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
@@ -37,8 +37,8 @@
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.server.wm.ActivityRecord.State.RESUMED;
 import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
-import static com.android.server.wm.Task.ActivityState.RESUMED;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -84,8 +84,7 @@
                 mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
         adjacentRootTask.mCreatedByOrganizer = true;
         final TaskDisplayArea taskDisplayArea = rootTask.getDisplayArea();
-        adjacentRootTask.mAdjacentTask = rootTask;
-        rootTask.mAdjacentTask = adjacentRootTask;
+        adjacentRootTask.setAdjacentTaskFragment(rootTask);
 
         taskDisplayArea.setLaunchAdjacentFlagRootTask(adjacentRootTask);
         Task actualRootTask = taskDisplayArea.getLaunchRootTask(
@@ -111,8 +110,7 @@
         final Task adjacentRootTask = createTask(
                 mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
         adjacentRootTask.mCreatedByOrganizer = true;
-        adjacentRootTask.mAdjacentTask = rootTask;
-        rootTask.mAdjacentTask = adjacentRootTask;
+        adjacentRootTask.setAdjacentTaskFragment(rootTask);
 
         taskDisplayArea.setLaunchRootTask(rootTask,
                 new int[]{WINDOWING_MODE_MULTI_WINDOW}, new int[]{ACTIVITY_TYPE_STANDARD});
@@ -133,8 +131,7 @@
                 mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
         adjacentRootTask.mCreatedByOrganizer = true;
         final TaskDisplayArea taskDisplayArea = rootTask.getDisplayArea();
-        adjacentRootTask.mAdjacentTask = rootTask;
-        rootTask.mAdjacentTask = adjacentRootTask;
+        adjacentRootTask.setAdjacentTaskFragment(rootTask);
 
         taskDisplayArea.setLaunchAdjacentFlagRootTask(adjacentRootTask);
         final Task actualRootTask = taskDisplayArea.getLaunchRootTask(
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 0ebff1d..629e452 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -1257,7 +1257,8 @@
         LaunchParamsPersister persister = mAtm.mTaskSupervisor.mLaunchParamsPersister;
         spyOn(persister);
 
-        final Task task = getTestTask();
+        final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true)
+                .setCreateParentTask(true).build().getRootTask();
         task.setHasBeenVisible(false);
         task.getDisplayContent().setDisplayWindowingMode(WINDOWING_MODE_FREEFORM);
         task.getRootTask().setWindowingMode(WINDOWING_MODE_FULLSCREEN);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 2dfb3a1..45e5f8e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -339,6 +339,44 @@
     }
 
     @Test
+    public void testTargets_noIntermediatesToWallpaper() {
+        final Transition transition = createTestTransition(TRANSIT_OLD_TASK_OPEN);
+
+        final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm,
+                mock(IBinder.class), true, mDisplayContent, true /* ownerCanManageAppTokens */);
+        // Make DA organized so we can check that they don't get included.
+        WindowContainer parent = wallpaperWindowToken.getParent();
+        while (parent != null && parent != mDisplayContent) {
+            if (parent.asDisplayArea() != null) {
+                parent.asDisplayArea().setOrganizer(
+                        mock(android.window.IDisplayAreaOrganizer.class), true /* skipAppear */);
+            }
+            parent = parent.getParent();
+        }
+        final WindowState wallpaperWindow = createWindow(null, TYPE_WALLPAPER, wallpaperWindowToken,
+                "wallpaperWindow");
+        wallpaperWindowToken.setVisibleRequested(false);
+        transition.collect(wallpaperWindowToken);
+        wallpaperWindowToken.setVisibleRequested(true);
+        wallpaperWindow.mHasSurface = true;
+        doReturn(true).when(mDisplayContent).isAttached();
+        transition.collect(mDisplayContent);
+        mDisplayContent.getWindowConfiguration().setRotation(
+                (mDisplayContent.getWindowConfiguration().getRotation() + 1) % 4);
+
+        ArraySet<WindowContainer> targets = Transition.calculateTargets(
+                transition.mParticipants, transition.mChanges);
+        TransitionInfo info = Transition.calculateTransitionInfo(
+                0, 0, targets, transition.mChanges);
+        // The wallpaper is not organized, so it won't have a token; however, it will be marked
+        // as IS_WALLPAPER
+        assertEquals(FLAG_IS_WALLPAPER, info.getChanges().get(0).getFlags());
+        // Make sure no intermediate display areas were pulled in between wallpaper and display.
+        assertEquals(mDisplayContent.mRemoteToken.toWindowContainerToken(),
+                info.getChanges().get(0).getParent());
+    }
+
+    @Test
     public void testIndependent() {
         final Transition transition = createTestTransition(TRANSIT_OPEN);
         ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java
index a1f89ec..efe6538 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java
@@ -57,12 +57,14 @@
 public class WindowFrameTests extends WindowTestsBase {
 
     private DisplayContent mTestDisplayContent;
+    private DisplayFrames mTestDisplayFrames;
 
     @Before
     public void setUp() throws Exception {
         DisplayInfo testDisplayInfo = new DisplayInfo(mDisplayInfo);
         testDisplayInfo.displayCutout = null;
         mTestDisplayContent = createNewDisplay(testDisplayInfo);
+        mTestDisplayFrames = mTestDisplayContent.mDisplayFrames;
     }
 
     // Do not use this function directly in the tests below. Instead, use more explicit function
@@ -99,7 +101,7 @@
         // Here the window has FILL_PARENT, FILL_PARENT
         // so we expect it to fill the entire available frame.
         w.getWindowFrames().setFrames(pf, pf);
-        w.computeFrame();
+        w.computeFrame(mTestDisplayFrames);
         assertFrame(w, 0, 0, 1000, 1000);
         assertRelFrame(w, 0, 0, 1000, 1000);
 
@@ -108,14 +110,14 @@
         // and we use mRequestedWidth/mRequestedHeight
         w.mAttrs.width = 300;
         w.mAttrs.height = 300;
-        w.computeFrame();
+        w.computeFrame(mTestDisplayFrames);
         // Explicit width and height without requested width/height
         // gets us nothing.
         assertFrame(w, 0, 0, 0, 0);
 
         w.mRequestedWidth = 300;
         w.mRequestedHeight = 300;
-        w.computeFrame();
+        w.computeFrame(mTestDisplayFrames);
         // With requestedWidth/Height we can freely choose our size within the
         // parent bounds.
         assertFrame(w, 0, 0, 300, 300);
@@ -128,14 +130,14 @@
         w.mRequestedWidth = -1;
         w.mAttrs.width = 100;
         w.mAttrs.height = 100;
-        w.computeFrame();
+        w.computeFrame(mTestDisplayFrames);
         assertFrame(w, 0, 0, 100, 100);
         w.mAttrs.flags = 0;
 
         // But sizes too large will be clipped to the containing frame
         w.mRequestedWidth = 1200;
         w.mRequestedHeight = 1200;
-        w.computeFrame();
+        w.computeFrame(mTestDisplayFrames);
         assertFrame(w, 0, 0, 1000, 1000);
 
         // Before they are clipped though windows will be shifted
@@ -143,7 +145,7 @@
         w.mAttrs.y = 300;
         w.mRequestedWidth = 1000;
         w.mRequestedHeight = 1000;
-        w.computeFrame();
+        w.computeFrame(mTestDisplayFrames);
         assertFrame(w, 0, 0, 1000, 1000);
 
         // If there is room to move around in the parent frame the window will be shifted according
@@ -153,18 +155,18 @@
         w.mRequestedWidth = 300;
         w.mRequestedHeight = 300;
         w.mAttrs.gravity = Gravity.RIGHT | Gravity.TOP;
-        w.computeFrame();
+        w.computeFrame(mTestDisplayFrames);
         assertFrame(w, 700, 0, 1000, 300);
         assertRelFrame(w, 700, 0, 1000, 300);
         w.mAttrs.gravity = Gravity.RIGHT | Gravity.BOTTOM;
-        w.computeFrame();
+        w.computeFrame(mTestDisplayFrames);
         assertFrame(w, 700, 700, 1000, 1000);
         assertRelFrame(w, 700, 700, 1000, 1000);
         // Window specified  x and y are interpreted as offsets in the opposite
         // direction of gravity
         w.mAttrs.x = 100;
         w.mAttrs.y = 100;
-        w.computeFrame();
+        w.computeFrame(mTestDisplayFrames);
         assertFrame(w, 600, 600, 900, 900);
         assertRelFrame(w, 600, 600, 900, 900);
     }
@@ -191,7 +193,7 @@
         final Rect pf = new Rect(0, 0, logicalWidth, logicalHeight);
         final WindowFrames windowFrames = w.getWindowFrames();
         windowFrames.setFrames(pf, pf);
-        w.computeFrame();
+        w.computeFrame(mTestDisplayFrames);
         // For non fullscreen tasks the containing frame is based off the
         // task bounds not the parent frame.
         assertEquals(resolvedTaskBounds, w.getFrame());
@@ -204,7 +206,7 @@
         final int cfBottom = logicalHeight / 2;
         final Rect cf = new Rect(0, 0, cfRight, cfBottom);
         windowFrames.setFrames(pf, pf);
-        w.computeFrame();
+        w.computeFrame(mTestDisplayFrames);
         assertEquals(resolvedTaskBounds, w.getFrame());
         assertEquals(0, w.getRelativeFrame().left);
         assertEquals(0, w.getRelativeFrame().top);
@@ -233,7 +235,7 @@
         final Rect pf = new Rect(0, 0, logicalWidth, logicalHeight);
         final WindowFrames windowFrames = w.getWindowFrames();
         windowFrames.setFrames(pf, pf);
-        w.computeFrame();
+        w.computeFrame(mTestDisplayFrames);
         // For non fullscreen tasks the containing frame is based off the
         // task bounds not the parent frame.
         assertFrame(w, taskLeft, taskTop, taskRight, taskBottom);
@@ -249,7 +251,7 @@
         task.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
         task.setBounds(null);
         windowFrames.setFrames(pf, pf);
-        w.computeFrame();
+        w.computeFrame(mTestDisplayFrames);
         assertFrame(w, cf);
     }
 
@@ -285,7 +287,7 @@
         final Rect winRect = new Rect(200, 200, 300, 500);
         task.setBounds(winRect);
         w.getWindowFrames().setFrames(pf, pf);
-        w.computeFrame();
+        w.computeFrame(mTestDisplayFrames);
         assertFrame(w, winRect.left, imeFrame.top - winRect.height(), winRect.right, imeFrame.top);
 
         // Now check that it won't get moved beyond the top
@@ -293,7 +295,7 @@
         task.setBounds(winRect);
         w.setBounds(winRect);
         w.getWindowFrames().setFrames(pf, pf);
-        w.computeFrame();
+        w.computeFrame(mTestDisplayFrames);
         assertFrame(w, winRect.left, 0, winRect.right, winRect.height());
 
         // Now we have status bar. Check that it won't go into the status bar area.
@@ -301,14 +303,14 @@
         statusBarFrame.bottom = 60;
         state.getSource(ITYPE_STATUS_BAR).setFrame(statusBarFrame);
         w.getWindowFrames().setFrames(pf, pf);
-        w.computeFrame();
+        w.computeFrame(mTestDisplayFrames);
         assertFrame(w, winRect.left, statusBarFrame.bottom, winRect.right,
                 statusBarFrame.bottom + winRect.height());
 
         // Check that it's moved back without ime insets
         state.removeSource(ITYPE_IME);
         w.getWindowFrames().setFrames(pf, pf);
-        w.computeFrame();
+        w.computeFrame(mTestDisplayFrames);
         assertEquals(winRect, w.getFrame());
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index d9aa871..83cdc3ba 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -107,9 +107,9 @@
         Task tappedTask = createTaskInRootTask(tappedRootTask, 0 /* userId */);
         spyOn(mWm.mAtmService);
 
-        mWm.handleTaskFocusChange(tappedTask);
+        mWm.handleTaskFocusChange(tappedTask, null /* window */);
 
-        verify(mWm.mAtmService).setFocusedTask(tappedTask.mTaskId);
+        verify(mWm.mAtmService).setFocusedTask(tappedTask.mTaskId, null);
     }
 
     @Test
@@ -128,9 +128,9 @@
         Task tappedTask = createTaskInRootTask(tappedRootTask, 0 /* userId */);
         spyOn(mWm.mAtmService);
 
-        mWm.handleTaskFocusChange(tappedTask);
+        mWm.handleTaskFocusChange(tappedTask, null /* window */);
 
-        verify(mWm.mAtmService, never()).setFocusedTask(tappedTask.mTaskId);
+        verify(mWm.mAtmService, never()).setFocusedTask(tappedTask.mTaskId, null);
     }
 
     @Test
@@ -151,9 +151,9 @@
         Task tappedTask = createTaskInRootTask(tappedRootTask, 0 /* userId */);
         spyOn(mWm.mAtmService);
 
-        mWm.handleTaskFocusChange(tappedTask);
+        mWm.handleTaskFocusChange(tappedTask, null /* window */);
 
-        verify(mWm.mAtmService).setFocusedTask(tappedTask.mTaskId);
+        verify(mWm.mAtmService).setFocusedTask(tappedTask.mTaskId, null);
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index d6a8401..39fe952 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -42,8 +42,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
-import static com.android.server.wm.DisplayArea.Type.ABOVE_TASKS;
-import static com.android.server.wm.Task.ActivityState.RESUMED;
+import static com.android.server.wm.ActivityRecord.State.RESUMED;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 import static com.android.server.wm.WindowContainer.SYNC_STATE_READY;
 
@@ -62,6 +61,7 @@
 
 import android.app.ActivityManager;
 import android.app.ActivityManager.RunningTaskInfo;
+import android.app.ActivityOptions;
 import android.app.ActivityTaskManager.RootTaskInfo;
 import android.app.IRequestFinishCallback;
 import android.app.PictureInPictureParams;
@@ -70,7 +70,6 @@
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Binder;
-import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
@@ -346,7 +345,7 @@
     @Test
     public void testDisplayAreaTransaction() {
         removeGlobalMinSizeRestriction();
-        final DisplayArea displayArea = new DisplayArea<>(mWm, ABOVE_TASKS, "DisplayArea");
+        final DisplayArea displayArea = mDisplayContent.getDefaultTaskDisplayArea();
         testTransaction(displayArea);
     }
 
@@ -364,7 +363,7 @@
                 .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
         testSetWindowingMode(rootTask);
 
-        final DisplayArea displayArea = new DisplayArea<>(mWm, ABOVE_TASKS, "DisplayArea");
+        final DisplayArea displayArea = mDisplayContent.getDefaultTaskDisplayArea();
         displayArea.setWindowingMode(WINDOWING_MODE_FREEFORM);
         testSetWindowingMode(displayArea);
     }
@@ -1256,21 +1255,24 @@
     @Test
     public void testStartTasksInTransaction() {
         WindowContainerTransaction wct = new WindowContainerTransaction();
-        Bundle testOptions = new Bundle();
-        testOptions.putInt("test", 20);
+        ActivityOptions testOptions = ActivityOptions.makeBasic();
+        testOptions.setTransientLaunch();
         wct.startTask(1, null /* options */);
-        wct.startTask(2, testOptions);
-        spyOn(mWm.mAtmService);
-        doReturn(START_CANCELED).when(mWm.mAtmService).startActivityFromRecents(anyInt(), any());
+        wct.startTask(2, testOptions.toBundle());
+        spyOn(mWm.mAtmService.mTaskSupervisor);
+        doReturn(START_CANCELED).when(mWm.mAtmService.mTaskSupervisor).startActivityFromRecents(
+                anyInt(), anyInt(), anyInt(), any());
         clearInvocations(mWm.mAtmService);
         mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
 
-        final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
-        verify(mWm.mAtmService, times(1)).startActivityFromRecents(eq(1), bundleCaptor.capture());
-        assertTrue(bundleCaptor.getValue().isEmpty());
+        verify(mWm.mAtmService.mTaskSupervisor, times(1)).startActivityFromRecents(
+                anyInt(), anyInt(), eq(1), any());
 
-        verify(mWm.mAtmService, times(1)).startActivityFromRecents(eq(2), bundleCaptor.capture());
-        assertEquals(20, bundleCaptor.getValue().getInt("test"));
+        final ArgumentCaptor<SafeActivityOptions> optionsCaptor =
+                ArgumentCaptor.forClass(SafeActivityOptions.class);
+        verify(mWm.mAtmService.mTaskSupervisor, times(1)).startActivityFromRecents(
+                anyInt(), anyInt(), eq(2), optionsCaptor.capture());
+        assertTrue(optionsCaptor.getValue().getOriginalOptions().getTransientLaunch());
     }
 
     /**
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index ed18d26..d3f2d14 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -23,6 +23,12 @@
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.wm.ActivityRecord.State.PAUSED;
+import static com.android.server.wm.ActivityRecord.State.PAUSING;
+import static com.android.server.wm.ActivityRecord.State.RESUMED;
+import static com.android.server.wm.ActivityRecord.State.STARTED;
+import static com.android.server.wm.ActivityRecord.State.STOPPED;
+import static com.android.server.wm.ActivityRecord.State.STOPPING;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -311,17 +317,17 @@
 
         callbackResult[0] = 0;
         activity.mVisibleRequested = false;
-        activity.setState(Task.ActivityState.PAUSED, "test");
+        activity.setState(PAUSED, "test");
         mWpc.computeOomAdjFromActivities(callback);
         assertEquals(paused, callbackResult[0]);
 
         callbackResult[0] = 0;
-        activity.setState(Task.ActivityState.STOPPING, "test");
+        activity.setState(STOPPING, "test");
         mWpc.computeOomAdjFromActivities(callback);
         assertEquals(stopping, callbackResult[0]);
 
         callbackResult[0] = 0;
-        activity.setState(Task.ActivityState.STOPPED, "test");
+        activity.setState(STOPPED, "test");
         mWpc.computeOomAdjFromActivities(callback);
         assertEquals(other, callbackResult[0]);
     }
@@ -332,25 +338,25 @@
         spyOn(tracker);
         final ActivityRecord activity = createActivityRecord(mWpc);
         activity.mVisibleRequested = true;
-        activity.setState(Task.ActivityState.STARTED, "test");
+        activity.setState(STARTED, "test");
 
         verify(tracker).onAnyActivityVisible(mWpc);
         assertTrue(mWpc.hasVisibleActivities());
 
-        activity.setState(Task.ActivityState.RESUMED, "test");
+        activity.setState(RESUMED, "test");
 
         verify(tracker).onActivityResumedWhileVisible(mWpc);
         assertTrue(tracker.hasResumedActivity(mWpc.mUid));
 
         activity.makeFinishingLocked();
-        activity.setState(Task.ActivityState.PAUSING, "test");
+        activity.setState(PAUSING, "test");
 
         assertFalse(tracker.hasResumedActivity(mWpc.mUid));
         assertTrue(mWpc.hasForegroundActivities());
 
         activity.setVisibility(false);
         activity.mVisibleRequested = false;
-        activity.setState(Task.ActivityState.STOPPED, "test");
+        activity.setState(STOPPED, "test");
 
         verify(tracker).onAllActivitiesInvisible(mWpc);
         assertFalse(mWpc.hasVisibleActivities());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 92b670e..e83db78 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -946,4 +946,19 @@
         assertNotNull(state.peekSource(ITYPE_IME));
         assertTrue(state.getSource(ITYPE_IME).isVisible());
     }
+
+    @Test
+    public void testRequestedVisibility() {
+        final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+        app.mActivityRecord.setVisible(false);
+        app.mActivityRecord.setVisibility(false /* visible */, false /* deferHidingClient */);
+        assertFalse(app.isVisibleRequested());
+
+        // It doesn't have a surface yet, but should still be visible requested.
+        app.setHasSurface(false);
+        app.mActivityRecord.setVisibility(true /* visible */, false /* deferHidingClient */);
+
+        assertFalse(app.isVisible());
+        assertTrue(app.isVisibleRequested());
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 5880899..050fd80 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -21,6 +21,7 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
@@ -29,11 +30,13 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.os.Process.SYSTEM_UID;
 import static android.view.View.VISIBLE;
+import static android.view.ViewRootImpl.INSETS_LAYOUT_GENERALIZATION;
 import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
 import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
 import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
 import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
@@ -83,10 +86,12 @@
 import android.util.SparseArray;
 import android.view.Display;
 import android.view.DisplayInfo;
+import android.view.Gravity;
 import android.view.IDisplayWindowInsetsController;
 import android.view.IWindow;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
+import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
 import android.view.View;
@@ -132,6 +137,9 @@
     DisplayInfo mDisplayInfo = new DisplayInfo();
     DisplayContent mDefaultDisplay;
 
+    static final int STATUS_BAR_HEIGHT = 10;
+    static final int NAV_BAR_HEIGHT = 15;
+
     /**
      * It is {@link #mDefaultDisplay} by default. If the test class or method is annotated with
      * {@link UseTestDisplay}, it will be an additional display.
@@ -268,6 +276,14 @@
         }
         if (addAll || ArrayUtils.contains(requestedWindows, W_STATUS_BAR)) {
             mStatusBarWindow = createCommonWindow(null, TYPE_STATUS_BAR, "mStatusBarWindow");
+            if (INSETS_LAYOUT_GENERALIZATION) {
+                mStatusBarWindow.mAttrs.height = STATUS_BAR_HEIGHT;
+                mStatusBarWindow.mAttrs.gravity = Gravity.TOP;
+                mStatusBarWindow.mAttrs.layoutInDisplayCutoutMode =
+                        LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+                mStatusBarWindow.setRequestedSize(WindowManager.LayoutParams.MATCH_PARENT,
+                        STATUS_BAR_HEIGHT);
+            }
         }
         if (addAll || ArrayUtils.contains(requestedWindows, W_NOTIFICATION_SHADE)) {
             mNotificationShadeWindow = createCommonWindow(null, TYPE_NOTIFICATION_SHADE,
@@ -275,6 +291,15 @@
         }
         if (addAll || ArrayUtils.contains(requestedWindows, W_NAVIGATION_BAR)) {
             mNavBarWindow = createCommonWindow(null, TYPE_NAVIGATION_BAR, "mNavBarWindow");
+            if (INSETS_LAYOUT_GENERALIZATION) {
+                mNavBarWindow.mAttrs.height = NAV_BAR_HEIGHT;
+                mNavBarWindow.mAttrs.gravity = Gravity.BOTTOM;
+                mNavBarWindow.mAttrs.paramsForRotation = new WindowManager.LayoutParams[4];
+                for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) {
+                    mNavBarWindow.mAttrs.paramsForRotation[rot] =
+                            getNavBarLayoutParamsForRotation(rot);
+                }
+            }
         }
         if (addAll || ArrayUtils.contains(requestedWindows, W_DOCK_DIVIDER)) {
             mDockedDividerWindow = createCommonWindow(null, TYPE_DOCK_DIVIDER,
@@ -302,6 +327,37 @@
         waitUntilHandlersIdle();
     }
 
+    private WindowManager.LayoutParams getNavBarLayoutParamsForRotation(int rotation) {
+        int width = WindowManager.LayoutParams.MATCH_PARENT;
+        int height = WindowManager.LayoutParams.MATCH_PARENT;
+        int gravity = Gravity.BOTTOM;
+        if (INSETS_LAYOUT_GENERALIZATION) {
+            switch (rotation) {
+                case ROTATION_UNDEFINED:
+                case Surface.ROTATION_0:
+                case Surface.ROTATION_180:
+                    height = NAV_BAR_HEIGHT;
+                    break;
+                case Surface.ROTATION_90:
+                    gravity = Gravity.RIGHT;
+                    width = NAV_BAR_HEIGHT;
+                    break;
+                case Surface.ROTATION_270:
+                    gravity = Gravity.LEFT;
+                    width = NAV_BAR_HEIGHT;
+                    break;
+            }
+        }
+        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+                WindowManager.LayoutParams.TYPE_NAVIGATION_BAR);
+        lp.width = width;
+        lp.height = height;
+        if (INSETS_LAYOUT_GENERALIZATION) {
+            lp.gravity = gravity;
+        }
+        return lp;
+    }
+
     void beforeCreateTestDisplay() {
         // Called before display is created.
     }
diff --git a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java
index bcd6ed7..824f91e 100644
--- a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java
+++ b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java
@@ -45,6 +45,7 @@
             // Test specifications for FrameworksMockingCoreTests.
             "android.app.activity.ActivityThreadClientTest",
             "android.view.DisplayTest",
+            "android.window.ConfigurationHelperTest",
             // Test specifications for FrameworksCoreTests.
             "android.app.servertransaction.", // all tests under the package.
             "android.view.CutoutSpecificationTest",
@@ -59,10 +60,8 @@
             "android.view.RoundedCornersTest",
             "android.view.WindowMetricsTest",
             "android.view.PendingInsetsControllerTest",
-            "android.window.WindowContextTest",
-            "android.window.WindowMetricsHelperTest",
+            "android.window.", // all tests under the package.
             "android.app.activity.ActivityThreadTest",
-            "android.window.WindowContextControllerTest"
     };
 
     public FrameworksTestsFilter(Bundle testArgs) {