Merge "Ensure previous background color is reapplied when showing a SurfaceView" into rvc-dev
diff --git a/apex/Android.bp b/apex/Android.bp
index cd34f98..e8f6e6b 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -84,6 +84,24 @@
     },
 }
 
+java_defaults {
+    name: "framework-module-stubs-lib-defaults-publicapi",
+    installable: false,
+    sdk_version: "module_current",
+}
+
+java_defaults {
+    name: "framework-module-stubs-lib-defaults-systemapi",
+    installable: false,
+    sdk_version: "module_current",
+}
+
+java_defaults {
+    name: "framework-module-stubs-lib-defaults-module_libs_api",
+    installable: false,
+    sdk_version: "module_current",
+}
+
 // The defaults for module_libs comes in two parts - defaults for API checks
 // and defaults for stub generation. This is because we want the API txt
 // files to *only* include the module_libs_api, but the stubs to include
diff --git a/apex/jobscheduler/service/java/com/android/server/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/TEST_MAPPING
index 8fbfb1d..d99830dc4 100644
--- a/apex/jobscheduler/service/java/com/android/server/TEST_MAPPING
+++ b/apex/jobscheduler/service/java/com/android/server/TEST_MAPPING
@@ -7,6 +7,7 @@
       ],
       "options": [
         {"include-filter": "com.android.server.DeviceIdleControllerTest"},
+        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
         {"exclude-annotation": "androidx.test.filters.FlakyTest"}
       ]
     }
diff --git a/apex/jobscheduler/service/java/com/android/server/deviceidle/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/deviceidle/TEST_MAPPING
index bc7a7d3..b76c582 100644
--- a/apex/jobscheduler/service/java/com/android/server/deviceidle/TEST_MAPPING
+++ b/apex/jobscheduler/service/java/com/android/server/deviceidle/TEST_MAPPING
@@ -4,6 +4,7 @@
       "name": "FrameworksMockingServicesTests",
       "options": [
         {"include-filter": "com.android.server.DeviceIdleControllerTest"},
+        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
         {"exclude-annotation": "androidx.test.filters.FlakyTest"}
       ]
     }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING
index e2e1180..484fec3 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING
+++ b/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING
@@ -3,6 +3,7 @@
         {
             "name": "CtsJobSchedulerTestCases",
             "options": [
+                {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
                 {"exclude-annotation": "androidx.test.filters.FlakyTest"},
                 {"exclude-annotation": "androidx.test.filters.LargeTest"}
             ]
@@ -11,6 +12,7 @@
             "name": "FrameworksMockingServicesTests",
             "options": [
                 {"include-filter": "com.android.server.job"},
+                {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
                 {"exclude-annotation": "androidx.test.filters.FlakyTest"}
             ]
         },
@@ -18,6 +20,7 @@
             "name": "FrameworksServicesTests",
             "options": [
                 {"include-filter": "com.android.server.job"},
+                {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
                 {"exclude-annotation": "androidx.test.filters.FlakyTest"}
             ]
         }
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING
index ba7572a..c5dc51c 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING
+++ b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING
@@ -4,6 +4,7 @@
       "name": "CtsUsageStatsTestCases",
       "options": [
         {"include-filter": "android.app.usage.cts.UsageStatsTest"},
+        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
         {"exclude-annotation": "androidx.test.filters.FlakyTest"}
       ]
     },
@@ -11,6 +12,7 @@
       "name": "FrameworksServicesTests",
       "options": [
         {"include-filter": "com.android.server.usage"},
+        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
         {"exclude-annotation": "androidx.test.filters.FlakyTest"}
       ]
     }
diff --git a/apex/media/framework/Android.bp b/apex/media/framework/Android.bp
index 821dd9e..99e82e7 100644
--- a/apex/media/framework/Android.bp
+++ b/apex/media/framework/Android.bp
@@ -132,19 +132,19 @@
 java_library {
     name: "framework-media-stubs-publicapi",
     srcs: [":framework-media-stubs-srcs-publicapi"],
-    sdk_version: "current",
+    defaults: ["framework-module-stubs-lib-defaults-publicapi"],
 }
 
 java_library {
     name: "framework-media-stubs-systemapi",
     srcs: [":framework-media-stubs-srcs-systemapi"],
-    sdk_version: "system_current",
+    defaults: ["framework-module-stubs-lib-defaults-systemapi"],
 }
 
 java_library {
     name: "framework-media-stubs-module_libs_api",
     srcs: [":framework-media-stubs-srcs-module_libs_api"],
-    sdk_version: "system_current",
+    defaults: ["framework-module-stubs-lib-defaults-module_libs_api"],
 }
 
 java_library {
diff --git a/apex/sdkextensions/framework/Android.bp b/apex/sdkextensions/framework/Android.bp
index 86f4ab7..707113b 100644
--- a/apex/sdkextensions/framework/Android.bp
+++ b/apex/sdkextensions/framework/Android.bp
@@ -86,7 +86,7 @@
 java_library {
     name: "framework-sdkextensions-stubs-publicapi",
     srcs: [":framework-sdkextensions-stubs-srcs-publicapi"],
-    sdk_version: "current",
+    defaults: ["framework-module-stubs-lib-defaults-publicapi"],
     visibility: [
         "//frameworks/base", // Framework
         "//frameworks/base/apex/sdkextensions", // sdkextensions SDK
@@ -96,7 +96,7 @@
 java_library {
     name: "framework-sdkextensions-stubs-systemapi",
     srcs: [":framework-sdkextensions-stubs-srcs-systemapi"],
-    sdk_version: "system_current",
+    defaults: ["framework-module-stubs-lib-defaults-systemapi"],
     visibility: [
         "//frameworks/base", // Framework
         "//frameworks/base/apex/sdkextensions", // sdkextensions SDK
@@ -106,7 +106,7 @@
 java_library {
     name: "framework-sdkextensions-stubs-module_libs_api",
     srcs: [":framework-sdkextensions-stubs-srcs-module_libs_api"],
-    sdk_version: "system_current",
+    defaults: ["framework-module-stubs-lib-defaults-module_libs_api"],
     visibility: [
         "//frameworks/base", // Framework
         "//frameworks/base/apex/sdkextensions", // sdkextensions SDK
diff --git a/api/system-current.txt b/api/system-current.txt
index a1fd0db..7e25382 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -6061,7 +6061,7 @@
     method public void release();
   }
 
-  public class InvalidPacketException extends java.lang.Exception {
+  public final class InvalidPacketException extends java.lang.Exception {
     ctor public InvalidPacketException(int);
     method public int getError();
     field public static final int ERROR_INVALID_IP_ADDRESS = -21; // 0xffffffeb
@@ -6255,7 +6255,7 @@
     field public static final int NET_CAPABILITY_PARTIAL_CONNECTIVITY = 24; // 0x18
   }
 
-  public static class NetworkCapabilities.Builder {
+  public static final class NetworkCapabilities.Builder {
     ctor public NetworkCapabilities.Builder();
     ctor public NetworkCapabilities.Builder(@NonNull android.net.NetworkCapabilities);
     method @NonNull public android.net.NetworkCapabilities.Builder addCapability(int);
diff --git a/api/test-current.txt b/api/test-current.txt
index 0bd8a19..ba6feed 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -535,6 +535,7 @@
     method public void setWindowingMode(int);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final int ACTIVITY_TYPE_ASSISTANT = 4; // 0x4
+    field public static final int ACTIVITY_TYPE_DREAM = 5; // 0x5
     field public static final int ACTIVITY_TYPE_HOME = 2; // 0x2
     field public static final int ACTIVITY_TYPE_RECENTS = 3; // 0x3
     field public static final int ACTIVITY_TYPE_STANDARD = 1; // 0x1
@@ -1833,7 +1834,7 @@
     field public static final int TRANSPORT_TEST = 7; // 0x7
   }
 
-  public static class NetworkCapabilities.Builder {
+  public static final class NetworkCapabilities.Builder {
     ctor public NetworkCapabilities.Builder();
     ctor public NetworkCapabilities.Builder(@NonNull android.net.NetworkCapabilities);
     method @NonNull public android.net.NetworkCapabilities.Builder addCapability(int);
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index bd3fee2d..21b56d3 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -4974,10 +4974,8 @@
     ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
             int configChanges, boolean getNonConfigInstance, String reason) {
         ActivityClientRecord r = mActivities.get(token);
-        Class<? extends Activity> activityClass = null;
         if (localLOGV) Slog.v(TAG, "Performing finish of " + r);
         if (r != null) {
-            activityClass = r.activity.getClass();
             r.activity.mConfigChangeFlags |= configChanges;
             if (finishing) {
                 r.activity.mFinished = true;
@@ -5030,7 +5028,6 @@
         synchronized (mResourcesManager) {
             mActivities.remove(token);
         }
-        StrictMode.decrementExpectedActivityCount(activityClass);
         return r;
     }
 
@@ -5050,6 +5047,7 @@
         ActivityClientRecord r = performDestroyActivity(token, finishing,
                 configChanges, getNonConfigInstance, reason);
         if (r != null) {
+            Class<? extends Activity> activityClass = r.activity.getClass();
             cleanUpPendingRemoveWindows(r, finishing);
             WindowManager wm = r.activity.getWindowManager();
             View v = r.activity.mDecor;
@@ -5074,14 +5072,14 @@
                 }
                 if (wtoken != null && r.mPendingRemoveWindow == null) {
                     WindowManagerGlobal.getInstance().closeAll(wtoken,
-                            r.activity.getClass().getName(), "Activity");
+                            activityClass.getName(), "Activity");
                 } else if (r.mPendingRemoveWindow != null) {
                     // We're preserving only one window, others should be closed so app views
                     // will be detached before the final tear down. It should be done now because
                     // some components (e.g. WebView) rely on detach callbacks to perform receiver
                     // unregister and other cleanup.
                     WindowManagerGlobal.getInstance().closeAllExceptView(token, v,
-                            r.activity.getClass().getName(), "Activity");
+                            activityClass.getName(), "Activity");
                 }
                 r.activity.mDecor = null;
             }
@@ -5093,18 +5091,23 @@
                 // about leaking windows, because that is a bug, so if they are
                 // using this recreate facility then they get to live with leaks.
                 WindowManagerGlobal.getInstance().closeAll(token,
-                        r.activity.getClass().getName(), "Activity");
+                        activityClass.getName(), "Activity");
             }
 
             // Mocked out contexts won't be participating in the normal
             // process lifecycle, but if we're running with a proper
             // ApplicationContext we need to have it tear down things
             // cleanly.
-            Context c = r.activity.getBaseContext();
-            if (c instanceof ContextImpl) {
-                ((ContextImpl) c).scheduleFinalCleanup(
-                        r.activity.getClass().getName(), "Activity");
+            final ContextImpl impl = ContextImpl.getImpl(r.activity);
+            if (impl != null) {
+                impl.scheduleFinalCleanup(activityClass.getName(), "Activity");
             }
+
+            r.activity = null;
+            r.window = null;
+            r.hideForNow = false;
+            r.nextIdle = null;
+            StrictMode.decrementExpectedActivityCount(activityClass);
         }
         if (finishing) {
             try {
@@ -5334,10 +5337,6 @@
 
         handleDestroyActivity(r.token, false, configChanges, true, reason);
 
-        r.activity = null;
-        r.window = null;
-        r.hideForNow = false;
-        r.nextIdle = null;
         // Merge any pending results and pending intents; don't just replace them
         if (pendingResults != null) {
             if (r.pendingResults == null) {
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index f590eb9..78d3581 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -48,8 +48,6 @@
     void clearData(String pkg, int uid, boolean fromApp);
     void enqueueTextToast(String pkg, IBinder token, CharSequence text, int duration, int displayId, @nullable ITransientNotificationCallback callback);
     void enqueueToast(String pkg, IBinder token, ITransientNotification callback, int duration, int displayId);
-    // TODO(b/144152069): Remove this after assessing impact on dogfood.
-    void enqueueTextOrCustomToast(String pkg, IBinder token, ITransientNotification callback, int duration, int displayId, boolean isCustom);
     void cancelToast(String pkg, IBinder token);
     void finishToken(String pkg, IBinder token);
 
diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java
index 6b40890..37e07de 100644
--- a/core/java/android/app/WindowConfiguration.java
+++ b/core/java/android/app/WindowConfiguration.java
@@ -141,6 +141,8 @@
     public static final int ACTIVITY_TYPE_RECENTS = 3;
     /** Assistant activity type. */
     public static final int ACTIVITY_TYPE_ASSISTANT = 4;
+    /** Dream activity type. */
+    public static final int ACTIVITY_TYPE_DREAM = 5;
 
     /** @hide */
     @IntDef(prefix = { "ACTIVITY_TYPE_" }, value = {
@@ -149,6 +151,7 @@
             ACTIVITY_TYPE_HOME,
             ACTIVITY_TYPE_RECENTS,
             ACTIVITY_TYPE_ASSISTANT,
+            ACTIVITY_TYPE_DREAM,
     })
     public @interface ActivityType {}
 
@@ -746,9 +749,11 @@
      * @hide
      */
     public boolean isAlwaysOnTop() {
-        return mWindowingMode == WINDOWING_MODE_PINNED || (mAlwaysOnTop == ALWAYS_ON_TOP_ON
-                && (mWindowingMode == WINDOWING_MODE_FREEFORM
-                    || mWindowingMode == WINDOWING_MODE_MULTI_WINDOW));
+        if (mWindowingMode == WINDOWING_MODE_PINNED) return true;
+        if (mActivityType == ACTIVITY_TYPE_DREAM) return true;
+        if (mAlwaysOnTop != ALWAYS_ON_TOP_ON) return false;
+        return mWindowingMode == WINDOWING_MODE_FREEFORM
+                    || mWindowingMode == WINDOWING_MODE_MULTI_WINDOW;
     }
 
     /**
@@ -798,7 +803,7 @@
 
     /** @hide */
     public static boolean supportSplitScreenWindowingMode(int activityType) {
-        return activityType != ACTIVITY_TYPE_ASSISTANT;
+        return activityType != ACTIVITY_TYPE_ASSISTANT && activityType != ACTIVITY_TYPE_DREAM;
     }
 
     /** @hide */
@@ -823,6 +828,7 @@
             case ACTIVITY_TYPE_HOME: return "home";
             case ACTIVITY_TYPE_RECENTS: return "recents";
             case ACTIVITY_TYPE_ASSISTANT: return "assistant";
+            case ACTIVITY_TYPE_DREAM: return "dream";
         }
         return String.valueOf(applicationType);
     }
diff --git a/core/java/android/bluetooth/BluetoothHearingAid.java b/core/java/android/bluetooth/BluetoothHearingAid.java
index e0674d7..fa62a02 100644
--- a/core/java/android/bluetooth/BluetoothHearingAid.java
+++ b/core/java/android/bluetooth/BluetoothHearingAid.java
@@ -379,6 +379,7 @@
     public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
             @ConnectionPolicy int connectionPolicy) {
         if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
+        verifyDeviceNotNull(device, "setConnectionPolicy");
         final IBluetoothHearingAid service = getService();
         try {
             if (service != null && isEnabled()
@@ -428,6 +429,7 @@
     @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
     public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
         if (VDBG) log("getConnectionPolicy(" + device + ")");
+        verifyDeviceNotNull(device, "getConnectionPolicy");
         final IBluetoothHearingAid service = getService();
         try {
             if (service != null && isEnabled()
@@ -504,6 +506,7 @@
         if (VDBG) {
             log("getHiSyncId(" + device + ")");
         }
+        verifyDeviceNotNull(device, "getConnectionPolicy");
         final IBluetoothHearingAid service = getService();
         try {
             if (service == null) {
@@ -577,6 +580,13 @@
         return false;
     }
 
+    private void verifyDeviceNotNull(BluetoothDevice device, String methodName) {
+        if (device == null) {
+            Log.e(TAG, methodName + ": device param is null");
+            throw new IllegalArgumentException("Device cannot be null");
+        }
+    }
+
     private boolean isValidDevice(BluetoothDevice device) {
         if (device == null) return false;
 
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 95385ee..b1d6c83 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -11215,6 +11215,17 @@
                 && hasWebURI();
     }
 
+    private boolean isImageCaptureIntent() {
+        return (MediaStore.ACTION_IMAGE_CAPTURE.equals(mAction)
+                || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(mAction)
+                || MediaStore.ACTION_VIDEO_CAPTURE.equals(mAction));
+    }
+
+    /** @hide */
+    public boolean isImplicitImageCaptureIntent() {
+        return mPackage == null && mComponent == null && isImageCaptureIntent();
+    }
+
     /**
      * @hide
      */
@@ -11241,9 +11252,7 @@
                 }
                 putParcelableArrayListExtra(EXTRA_STREAM, newStreams);
             }
-        } else if (MediaStore.ACTION_IMAGE_CAPTURE.equals(action)
-                || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(action)
-                || MediaStore.ACTION_VIDEO_CAPTURE.equals(action)) {
+        } else if (isImageCaptureIntent()) {
             final Uri output = getParcelableExtra(MediaStore.EXTRA_OUTPUT);
             if (output != null) {
                 putExtra(MediaStore.EXTRA_OUTPUT, maybeAddUserId(output, contentUserHint));
@@ -11349,9 +11358,7 @@
                 }
             } catch (ClassCastException e) {
             }
-        } else if (MediaStore.ACTION_IMAGE_CAPTURE.equals(action)
-                || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(action)
-                || MediaStore.ACTION_VIDEO_CAPTURE.equals(action)) {
+        } else if (isImageCaptureIntent()) {
             final Uri output;
             try {
                 output = getParcelableExtra(MediaStore.EXTRA_OUTPUT);
diff --git a/core/java/android/inputmethodservice/InlineSuggestionSession.java b/core/java/android/inputmethodservice/InlineSuggestionSession.java
index c31cb4e..9b3e8c9 100644
--- a/core/java/android/inputmethodservice/InlineSuggestionSession.java
+++ b/core/java/android/inputmethodservice/InlineSuggestionSession.java
@@ -166,9 +166,14 @@
             }
             return;
         }
-
+        // The IME doesn't have information about the virtual view id for the child views in the
+        // web view, so we are only comparing the parent view id here. This means that for cases
+        // where there are two input fields in the web view, they will have the same view id
+        // (although different virtual child id), and we will not be able to distinguish them.
+        final AutofillId imeClientFieldId = mClientAutofillIdSupplier.get();
         if (!mComponentName.getPackageName().equals(mClientPackageNameSupplier.get())
-                || !fieldId.equalsIgnoreSession(mClientAutofillIdSupplier.get())) {
+                || imeClientFieldId == null
+                || fieldId.getViewId() != imeClientFieldId.getViewId()) {
             if (DEBUG) {
                 Log.d(TAG,
                         "handleOnInlineSuggestionsResponse() called on the wrong package/field "
diff --git a/core/java/android/net/InvalidPacketException.java b/core/java/android/net/InvalidPacketException.java
index b3b0f11..1873d77 100644
--- a/core/java/android/net/InvalidPacketException.java
+++ b/core/java/android/net/InvalidPacketException.java
@@ -27,7 +27,7 @@
  * @hide
  */
 @SystemApi
-public class InvalidPacketException extends Exception {
+public final class InvalidPacketException extends Exception {
     private final int mError;
 
     // Must match SocketKeepalive#ERROR_INVALID_IP_ADDRESS.
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index fcfcebd..af9414c 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -415,6 +415,20 @@
             | (1 << NET_CAPABILITY_PARTIAL_CONNECTIVITY);
 
     /**
+     * Capabilities that are allowed for test networks. This list must be set so that it is safe
+     * for an unprivileged user to create a network with these capabilities via shell. As such,
+     * it must never contain capabilities that are generally useful to the system, such as
+     * INTERNET, IMS, SUPL, etc.
+     */
+    private static final long TEST_NETWORKS_ALLOWED_CAPABILITIES =
+            (1 << NET_CAPABILITY_NOT_METERED)
+            | (1 << NET_CAPABILITY_NOT_RESTRICTED)
+            | (1 << NET_CAPABILITY_NOT_VPN)
+            | (1 << NET_CAPABILITY_NOT_ROAMING)
+            | (1 << NET_CAPABILITY_NOT_CONGESTED)
+            | (1 << NET_CAPABILITY_NOT_SUSPENDED);
+
+    /**
      * Adds the given capability to this {@code NetworkCapability} instance.
      * Note that when searching for a network to satisfy a request, all capabilities
      * requested must be satisfied.
@@ -646,6 +660,21 @@
     }
 
     /**
+     * Test networks have strong restrictions on what capabilities they can have. Enforce these
+     * restrictions.
+     * @hide
+     */
+    public void restrictCapabilitesForTestNetwork() {
+        final long originalCapabilities = mNetworkCapabilities;
+        final NetworkSpecifier originalSpecifier = mNetworkSpecifier;
+        clearAll();
+        // Reset the transports to only contain TRANSPORT_TEST.
+        mTransportTypes = (1 << TRANSPORT_TEST);
+        mNetworkCapabilities = originalCapabilities & TEST_NETWORKS_ALLOWED_CAPABILITIES;
+        mNetworkSpecifier = originalSpecifier;
+    }
+
+    /**
      * Representing the transport type.  Apps should generally not care about transport.  A
      * request for a fast internet connection could be satisfied by a number of different
      * transports.  If any are specified here it will be satisfied a Network that matches
@@ -2000,7 +2029,7 @@
      */
     @SystemApi
     @TestApi
-    public static class Builder {
+    public static final class Builder {
         private final NetworkCapabilities mCaps;
 
         /**
diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl
index bbc936d..99bdfd1 100644
--- a/core/java/android/os/storage/IStorageManager.aidl
+++ b/core/java/android/os/storage/IStorageManager.aidl
@@ -194,4 +194,5 @@
     boolean needsCheckpoint() = 86;
     void abortChanges(in String message, boolean retry) = 87;
     void clearUserKeyAuth(int userId, int serialNumber, in byte[] token, in byte[] secret) = 88;
+    void fixupAppDir(in String path) = 89;
 }
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 1454aac..a68cc3d 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -2470,6 +2470,36 @@
         }
     }
 
+    /**
+     * Asks StorageManager to fixup the permissions of an application-private directory.
+     *
+     * On devices without sdcardfs, filesystem permissions aren't magically fixed up. This
+     * is problematic mostly in application-private directories, which are owned by the
+     * application itself; if another process with elevated permissions creates a file
+     * in these directories, the UID will be wrong, and the owning package won't be able
+     * to access the files.
+     *
+     * This API can be used to recursively fix up the permissions on the passed in path.
+     * The default platform user of this API is the DownloadProvider, which can download
+     * things in application-private directories on their behalf.
+     *
+     * This API doesn't require any special permissions, because it merely changes the
+     * permissions of a directory to what they should anyway be.
+     *
+     * @param path the path for which we should fix up the permissions
+     *
+     * @hide
+     */
+    public void fixupAppDir(@NonNull File path) {
+        try {
+            mStorageManager.fixupAppDir(path.getCanonicalPath());
+        } catch (IOException e) {
+            Log.e(TAG, "Failed to get canonical path for " + path.getPath(), e);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     /** {@hide} */
     private static void setCacheBehavior(File path, String name, boolean enabled)
             throws IOException {
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 4aeea10..62dd192 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -405,6 +405,13 @@
     // The actual zoom value may changes based on this initial zoom value.
     private float mInitialZoom = 1f;
 
+    // For calculating the line change slops while moving cursor/selection.
+    // The slop max/min value include line height and the slop on the upper/lower line.
+    private static final int LINE_CHANGE_SLOP_MAX_DP = 45;
+    private static final int LINE_CHANGE_SLOP_MIN_DP = 12;
+    private int mLineChangeSlopMax;
+    private int mLineChangeSlopMin;
+
     Editor(TextView textView) {
         mTextView = textView;
         // Synchronize the filter list, which places the undo input filter at the end.
@@ -430,6 +437,14 @@
             logCursor("Editor", "New magnifier is %s.",
                     mNewMagnifierEnabled ? "enabled" : "disabled");
         }
+
+        mLineChangeSlopMax = (int) TypedValue.applyDimension(
+                TypedValue.COMPLEX_UNIT_DIP, LINE_CHANGE_SLOP_MAX_DP,
+                mTextView.getContext().getResources().getDisplayMetrics());
+        mLineChangeSlopMin = (int) TypedValue.applyDimension(
+                TypedValue.COMPLEX_UNIT_DIP, LINE_CHANGE_SLOP_MIN_DP,
+                mTextView.getContext().getResources().getDisplayMetrics());
+
     }
 
     @VisibleForTesting
@@ -6018,7 +6033,14 @@
         }
     }
 
-    private int getCurrentLineAdjustedForSlop(Layout layout, int prevLine, float y) {
+    @VisibleForTesting
+    public void setLineChangeSlopMinMaxForTesting(final int min, final int max) {
+        mLineChangeSlopMin = min;
+        mLineChangeSlopMax = max;
+    }
+
+    @VisibleForTesting
+    public int getCurrentLineAdjustedForSlop(Layout layout, int prevLine, float y) {
         final int trueLine = mTextView.getLineAtCoordinate(y);
         if (layout == null || prevLine > layout.getLineCount()
                 || layout.getLineCount() <= 0 || prevLine < 0) {
@@ -6031,28 +6053,21 @@
             return trueLine;
         }
 
+        final int lineHeight = layout.getLineBottom(prevLine) - layout.getLineTop(prevLine);
+        int slop = (int)(LINE_SLOP_MULTIPLIER_FOR_HANDLEVIEWS
+                * (layout.getLineBottom(trueLine) - layout.getLineTop(trueLine)));
+        slop = Math.max(mLineChangeSlopMin,
+                Math.min(mLineChangeSlopMax, lineHeight + slop)) - lineHeight;
+        slop = Math.max(0, slop);
+
         final float verticalOffset = mTextView.viewportToContentVerticalOffset();
-        final int lineCount = layout.getLineCount();
-        final float slop = mTextView.getLineHeight() * LINE_SLOP_MULTIPLIER_FOR_HANDLEVIEWS;
-
-        final float firstLineTop = layout.getLineTop(0) + verticalOffset;
-        final float prevLineTop = layout.getLineTop(prevLine) + verticalOffset;
-        final float yTopBound = Math.max(prevLineTop - slop, firstLineTop + slop);
-
-        final float lastLineBottom = layout.getLineBottom(lineCount - 1) + verticalOffset;
-        final float prevLineBottom = layout.getLineBottom(prevLine) + verticalOffset;
-        final float yBottomBound = Math.min(prevLineBottom + slop, lastLineBottom - slop);
-
-        // Determine if we've moved lines based on y position and previous line.
-        int currLine;
-        if (y <= yTopBound) {
-            currLine = Math.max(prevLine - 1, 0);
-        } else if (y >= yBottomBound) {
-            currLine = Math.min(prevLine + 1, lineCount - 1);
-        } else {
-            currLine = prevLine;
+        if (trueLine > prevLine && y >= layout.getLineBottom(prevLine) + slop + verticalOffset) {
+            return trueLine;
         }
-        return currLine;
+        if (trueLine < prevLine && y <= layout.getLineTop(prevLine) - slop + verticalOffset) {
+            return trueLine;
+        }
+        return prevLine;
     }
 
     /**
diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java
index 8943da4..4f14539 100644
--- a/core/java/android/widget/Toast.java
+++ b/core/java/android/widget/Toast.java
@@ -144,9 +144,6 @@
     @Nullable
     private CharSequence mText;
 
-    // TODO(b/144152069): Remove this after assessing impact on dogfood.
-    private boolean mIsCustomToast;
-
     /**
      * Construct an empty Toast object.  You must call {@link #setView} before you
      * can call {@link #show}.
@@ -214,8 +211,7 @@
                     service.enqueueTextToast(pkg, mToken, mText, mDuration, displayId, callback);
                 }
             } else {
-                service.enqueueTextOrCustomToast(pkg, mToken, tn, mDuration, displayId,
-                        mIsCustomToast);
+                service.enqueueToast(pkg, mToken, tn, mDuration, displayId);
             }
         } catch (RemoteException e) {
             // Empty
@@ -253,7 +249,6 @@
      */
     @Deprecated
     public void setView(View view) {
-        mIsCustomToast = true;
         mNextView = view;
     }
 
diff --git a/core/java/com/android/internal/widget/ResolverDrawerLayout.java b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
index c83cab7..31527e8 100644
--- a/core/java/com/android/internal/widget/ResolverDrawerLayout.java
+++ b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
@@ -227,7 +227,7 @@
         }
 
         final int oldCollapsibleHeight = mCollapsibleHeight;
-        mCollapsibleHeight = Math.max(mCollapsibleHeight, getMaxCollapsedHeight());
+        mCollapsibleHeight = Math.min(mCollapsibleHeight, getMaxCollapsedHeight());
 
         if (updateCollapseOffset(oldCollapsibleHeight, !isDragging())) {
             return;
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index ed2c5b2..ab57e3d 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -2665,4 +2665,8 @@
     // Open: Settings > Sound > Do Not Disturb > People > Messages
     // OS: R
     DND_MESSAGES = 1839;
+
+    // Open: Settings > Sound > Do Not Disturb > Apps > <Choose App>
+    // OS: R
+    DND_APPS_BYPASSING = 1840;
 }
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index 2ef0c92..88f9fc2 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -702,7 +702,7 @@
     </style>
 
     <style name="Theme.Dream">
-        <item name="windowBackground">@null</item>
+        <item name="windowBackground">@color/black</item>
         <item name="windowDisablePreview">true</item>
     </style>
 
diff --git a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
index f81964c..1b5ce8fd 100644
--- a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
+++ b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
@@ -16,6 +16,7 @@
 
 package android.widget;
 
+import static android.text.Spanned.SPAN_INCLUSIVE_EXCLUSIVE;
 import static android.widget.espresso.TextViewActions.clickOnTextAtIndex;
 import static android.widget.espresso.TextViewActions.dragOnText;
 import static android.widget.espresso.TextViewAssertions.hasInsertionPointerAtIndex;
@@ -37,6 +38,9 @@
 import android.app.Instrumentation;
 import android.graphics.Rect;
 import android.text.Layout;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.style.AbsoluteSizeSpan;
 import android.util.ArraySet;
 import android.util.Log;
 import android.view.InputDevice;
@@ -489,6 +493,38 @@
     }
 
     @Test
+    public void testLineChangeSlop() throws Throwable {
+        TextView tv = mActivity.findViewById(R.id.textview);
+        Spannable s = new SpannableString("a\nb\nc");
+        s.setSpan(new AbsoluteSizeSpan(10), 2, 4, SPAN_INCLUSIVE_EXCLUSIVE);
+        s.setSpan(new AbsoluteSizeSpan(32), 4, 5, SPAN_INCLUSIVE_EXCLUSIVE);
+        mInstrumentation.runOnMainSync(() -> tv.setText(s));
+
+        Layout layout = tv.getLayout();
+        Editor editor = tv.getEditorForTesting();
+        final float verticalOffset = tv.getExtendedPaddingTop();
+        editor.setLineChangeSlopMinMaxForTesting(30, 65);
+        // Hit top part of upper line, jump to upper line.
+        assertThat(editor.getCurrentLineAdjustedForSlop(layout, 1, 5 + verticalOffset))
+                .isEqualTo(0);
+        // Hit bottom part of upper line, stay at current line.
+        assertThat(editor.getCurrentLineAdjustedForSlop(layout, 1, 40 + verticalOffset))
+                .isEqualTo(1);
+        // Hit current line, stay at current line.
+        assertThat(editor.getCurrentLineAdjustedForSlop(layout, 1, 70 + verticalOffset))
+                .isEqualTo(1);
+        // Hit top part of lower line, stay at current line.
+        assertThat(editor.getCurrentLineAdjustedForSlop(layout, 1, 85 + verticalOffset))
+                .isEqualTo(1);
+        // Hit bottom part of lower line, jump to lower line.
+        assertThat(editor.getCurrentLineAdjustedForSlop(layout, 1, 110 + verticalOffset))
+                .isEqualTo(2);
+        // Hit lower line of lower line, jump to target line.
+        assertThat(editor.getCurrentLineAdjustedForSlop(layout, 0, 110 + verticalOffset))
+                .isEqualTo(2);
+    }
+
+    @Test
     public void testCursorDrag_snapDistance() throws Throwable {
         String text = "line1: This is the 1st line: A\n"
                 + "line2: This is the 2nd line: B\n"
diff --git a/data/etc/hiddenapi-package-whitelist.xml b/data/etc/hiddenapi-package-whitelist.xml
index 5c89da0..98f5824 100644
--- a/data/etc/hiddenapi-package-whitelist.xml
+++ b/data/etc/hiddenapi-package-whitelist.xml
@@ -61,7 +61,6 @@
   <hidden-api-whitelisted-app package="com.android.terminal" />
   <hidden-api-whitelisted-app package="com.android.wallpaper" />
   <hidden-api-whitelisted-app package="jp.co.omronsoft.openwnn" />
-  <!-- STOPSHIP: Remove this when fixing all @hide usage for tethering.-->
-  <hidden-api-whitelisted-app package="com.android.networkstack.tethering" />
+  <!-- TODO: Remove NetworkStack whitelisting -->
   <hidden-api-whitelisted-app package="com.android.networkstack" />
 </config>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
index d17f242..a1fba4a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
@@ -174,7 +174,7 @@
 
     @Override
     public boolean isEnabled(BluetoothDevice device) {
-        if (mService == null) {
+        if (mService == null || device == null) {
             return false;
         }
         return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
@@ -182,7 +182,7 @@
 
     @Override
     public int getConnectionPolicy(BluetoothDevice device) {
-        if (mService == null) {
+        if (mService == null || device == null) {
             return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
@@ -191,7 +191,7 @@
     @Override
     public boolean setEnabled(BluetoothDevice device, boolean enabled) {
         boolean isEnabled = false;
-        if (mService == null) {
+        if (mService == null || device == null) {
             return false;
         }
         if (enabled) {
@@ -213,7 +213,7 @@
     }
 
     public long getHiSyncId(BluetoothDevice device) {
-        if (mService == null) {
+        if (mService == null || device == null) {
             return BluetoothHearingAid.HI_SYNC_ID_INVALID;
         }
         return mService.getHiSyncId(device);
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 07bd3a0..1369350 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
@@ -73,14 +73,19 @@
     public static final int SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED = 1 << 9;
     // The search feature is disabled (either by SUW/SysUI/device policy)
     public static final int SYSUI_STATE_SEARCH_DISABLED = 1 << 10;
-    // The notification panel is expanded and interactive (either locked or unlocked), and the
-    // quick settings is not expanded
+    // The notification panel is expanded and interactive (either locked or unlocked), and quick
+    // settings is expanded.
     public static final int SYSUI_STATE_QUICK_SETTINGS_EXPANDED = 1 << 11;
     // Winscope tracing is enabled
     public static final int SYSUI_STATE_TRACING_ENABLED = 1 << 12;
     // The Assistant gesture should be constrained. It is up to the launcher implementation to
     // decide how to constrain it
     public static final int SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED = 1 << 13;
+    // The bubble stack is expanded. This means that the home gesture should be ignored, since a
+    // swipe up is an attempt to close the bubble stack, but that the back gesture should remain
+    // enabled (since it's used to navigate back within the bubbled app, or to collapse the bubble
+    // stack.
+    public static final int SYSUI_STATE_BUBBLES_EXPANDED = 1 << 14;
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({SYSUI_STATE_SCREEN_PINNING,
@@ -96,7 +101,8 @@
             SYSUI_STATE_HOME_DISABLED,
             SYSUI_STATE_SEARCH_DISABLED,
             SYSUI_STATE_TRACING_ENABLED,
-            SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED
+            SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED,
+            SYSUI_STATE_BUBBLES_EXPANDED
     })
     public @interface SystemUiStateFlags {}
 
@@ -118,6 +124,7 @@
         str.add((flags & SYSUI_STATE_TRACING_ENABLED) != 0 ? "tracing" : "");
         str.add((flags & SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED) != 0
                 ? "asst_gesture_constrain" : "");
+        str.add((flags & SYSUI_STATE_BUBBLES_EXPANDED) != 0 ? "bubbles_expanded" : "");
         return str.toString();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 31f90c6..01c2faa 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -74,6 +74,7 @@
 import com.android.systemui.R;
 import com.android.systemui.bubbles.dagger.BubbleModule;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.model.SysUiState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.PinnedStackListenerForwarder;
@@ -174,6 +175,7 @@
 
     private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
     private IStatusBarService mBarService;
+    private SysUiState mSysUiState;
 
     // Used for determining view rect for touch interaction
     private Rect mTempRect = new Rect();
@@ -290,11 +292,12 @@
             NotifPipeline notifPipeline,
             FeatureFlags featureFlags,
             DumpManager dumpManager,
-            FloatingContentCoordinator floatingContentCoordinator) {
+            FloatingContentCoordinator floatingContentCoordinator,
+            SysUiState sysUiState) {
         this(context, notificationShadeWindowController, statusBarStateController, shadeController,
                 data, null /* synchronizer */, configurationController, interruptionStateProvider,
                 zenModeController, notifUserManager, groupManager, entryManager,
-                notifPipeline, featureFlags, dumpManager, floatingContentCoordinator);
+                notifPipeline, featureFlags, dumpManager, floatingContentCoordinator, sysUiState);
     }
 
     /**
@@ -315,7 +318,8 @@
             NotifPipeline notifPipeline,
             FeatureFlags featureFlags,
             DumpManager dumpManager,
-            FloatingContentCoordinator floatingContentCoordinator) {
+            FloatingContentCoordinator floatingContentCoordinator,
+            SysUiState sysUiState) {
         dumpManager.registerDumpable(TAG, this);
         mContext = context;
         mShadeController = shadeController;
@@ -340,6 +344,7 @@
         });
 
         configurationController.addCallback(this /* configurationListener */);
+        mSysUiState = sysUiState;
 
         mBubbleData = data;
         mBubbleData.setListener(mBubbleDataListener);
@@ -593,7 +598,8 @@
     private void ensureStackViewCreated() {
         if (mStackView == null) {
             mStackView = new BubbleStackView(
-                    mContext, mBubbleData, mSurfaceSynchronizer, mFloatingContentCoordinator);
+                    mContext, mBubbleData, mSurfaceSynchronizer, mFloatingContentCoordinator,
+                    mSysUiState);
             ViewGroup nsv = mNotificationShadeWindowController.getNotificationShadeView();
             int bubbleScrimIndex = nsv.indexOfChild(nsv.findViewById(R.id.scrim_for_bubble));
             int stackIndex = bubbleScrimIndex + 1;  // Show stack above bubble scrim.
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 541c8cf..7191a20 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -80,6 +80,8 @@
 import com.android.systemui.bubbles.animation.ExpandedAnimationController;
 import com.android.systemui.bubbles.animation.PhysicsAnimationLayout;
 import com.android.systemui.bubbles.animation.StackAnimationController;
+import com.android.systemui.model.SysUiState;
+import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.util.DismissCircleView;
 import com.android.systemui.util.FloatingContentCoordinator;
@@ -241,6 +243,7 @@
 
     private BubbleTouchHandler mTouchHandler;
     private BubbleController.BubbleExpandListener mExpandListener;
+    private SysUiState mSysUiState;
 
     private boolean mViewUpdatedRequested = false;
     private boolean mIsExpansionAnimating = false;
@@ -437,7 +440,8 @@
 
     public BubbleStackView(Context context, BubbleData data,
             @Nullable SurfaceSynchronizer synchronizer,
-            FloatingContentCoordinator floatingContentCoordinator) {
+            FloatingContentCoordinator floatingContentCoordinator,
+            SysUiState sysUiState) {
         super(context);
 
         mBubbleData = data;
@@ -445,6 +449,8 @@
         mTouchHandler = new BubbleTouchHandler(this, data, context);
         setOnTouchListener(mTouchHandler);
 
+        mSysUiState = sysUiState;
+
         Resources res = getResources();
         mMaxBubbles = res.getInteger(R.integer.bubbles_max_rendered);
         mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
@@ -1055,6 +1061,11 @@
         if (shouldExpand == mIsExpanded) {
             return;
         }
+
+        mSysUiState
+                .setFlag(QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED, shouldExpand)
+                .commitUpdate(mContext.getDisplayId());
+
         if (mIsExpanded) {
             animateCollapse();
             logBubbleEvent(mExpandedBubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
index 27c9e98..e84e932 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
@@ -21,6 +21,7 @@
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.bubbles.BubbleData;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.model.SysUiState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -62,7 +63,8 @@
             NotifPipeline notifPipeline,
             FeatureFlags featureFlags,
             DumpManager dumpManager,
-            FloatingContentCoordinator floatingContentCoordinator) {
+            FloatingContentCoordinator floatingContentCoordinator,
+            SysUiState sysUiState) {
         return new BubbleController(
                 context,
                 notificationShadeWindowController,
@@ -79,6 +81,7 @@
                 notifPipeline,
                 featureFlags,
                 dumpManager,
-                floatingContentCoordinator);
+                floatingContentCoordinator,
+                sysUiState);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index 3879c16..1aa7831 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -25,6 +25,7 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.annotation.Nullable;
+import android.app.ActivityTaskManager;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Rect;
@@ -129,6 +130,7 @@
 
     private int mDividerInsets;
     private final Display mDefaultDisplay;
+    private boolean mSupportSplitScreenMultiWindow;
 
     private int mDividerSize;
     private int mTouchElevation;
@@ -282,6 +284,8 @@
         final DisplayManager displayManager =
                 (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
         mDefaultDisplay = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
+        mSupportSplitScreenMultiWindow =
+                ActivityTaskManager.supportsSplitScreenMultiWindow(mContext);
     }
 
     @Override
@@ -354,6 +358,11 @@
 
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        if (!mSupportSplitScreenMultiWindow) {
+            super.onLayout(changed, left, top, right, bottom);
+            return;
+        }
+
         if (mFirstLayout) {
             // Wait for first layout so that the ViewRootImpl surface has been created.
             initializeSurfaceState();
@@ -1085,6 +1094,13 @@
         crop.offsetTo(-(otherTaskRect.left - otherRect.left),
                 -(otherTaskRect.top - otherRect.top));
         t.setWindowCrop(mTiles.mSecondarySurface, crop);
+        // Reposition home and recents surfaces or they would be positioned relatively to its
+        // parent (split-screen secondary task) position.
+        for (int i = mTiles.mHomeAndRecentsSurfaces.size() - 1; i >= 0; --i) {
+            t.setPosition(mTiles.mHomeAndRecentsSurfaces.get(i),
+                    mTiles.mHomeBounds.left - otherTaskRect.left,
+                    mTiles.mHomeBounds.top - otherTaskRect.top);
+        }
         final SurfaceControl dividerCtrl = getWindowSurfaceControl();
         if (dividerCtrl != null) {
             if (isHorizontalDivision()) {
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java
index c4089e5..6cb7f4f 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java
@@ -26,12 +26,15 @@
 
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.WindowConfiguration;
+import android.graphics.Rect;
 import android.os.RemoteException;
 import android.util.Log;
 import android.view.Display;
-import android.window.ITaskOrganizer;
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
+import android.window.ITaskOrganizer;
+
+import java.util.ArrayList;
 
 class SplitScreenTaskOrganizer extends ITaskOrganizer.Stub {
     private static final String TAG = "SplitScreenTaskOrganizer";
@@ -43,6 +46,8 @@
     SurfaceControl mSecondarySurface;
     SurfaceControl mPrimaryDim;
     SurfaceControl mSecondaryDim;
+    ArrayList<SurfaceControl> mHomeAndRecentsSurfaces = new ArrayList<>();
+    Rect mHomeBounds = new Rect();
     final Divider mDivider;
 
     SplitScreenTaskOrganizer(Divider divider) {
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
index 8724e49..6ed7afe 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
@@ -29,9 +29,9 @@
 import android.os.RemoteException;
 import android.util.Log;
 import android.view.Display;
+import android.view.WindowManagerGlobal;
 import android.window.IWindowContainer;
 import android.window.WindowContainerTransaction;
-import android.view.WindowManagerGlobal;
 import android.window.WindowOrganizer;
 
 import com.android.internal.annotations.GuardedBy;
@@ -157,6 +157,7 @@
         for (int i = homeStacks.size() - 1; i >= 0; --i) {
             wct.setBounds(homeStacks.get(i), homeBounds);
         }
+        layout.mTiles.mHomeBounds.set(homeBounds);
         return isHomeResizable;
     }
 
@@ -180,13 +181,17 @@
             if (rootTasks.isEmpty()) {
                 return false;
             }
+            tiles.mHomeAndRecentsSurfaces.clear();
             for (int i = rootTasks.size() - 1; i >= 0; --i) {
-                if (rootTasks.get(i).configuration.windowConfiguration.getWindowingMode()
+                final ActivityManager.RunningTaskInfo rootTask = rootTasks.get(i);
+                if (isHomeOrRecentTask(rootTask)) {
+                    tiles.mHomeAndRecentsSurfaces.add(rootTask.token.getLeash());
+                }
+                if (rootTask.configuration.windowConfiguration.getWindowingMode()
                         != WINDOWING_MODE_FULLSCREEN) {
                     continue;
                 }
-                wct.reparent(rootTasks.get(i).token, tiles.mSecondary.token,
-                        true /* onTop */);
+                wct.reparent(rootTask.token, tiles.mSecondary.token, true /* onTop */);
             }
             boolean isHomeResizable = applyHomeTasksMinimized(layout, null /* parent */, wct);
             WindowOrganizer.applyTransaction(wct);
@@ -213,6 +218,7 @@
             // Set launch root first so that any task created after getChildContainers and
             // before reparent (pretty unlikely) are put into fullscreen.
             TaskOrganizer.setLaunchRoot(Display.DEFAULT_DISPLAY, null);
+            tiles.mHomeAndRecentsSurfaces.clear();
             // TODO(task-org): Once task-org is more complete, consider using Appeared/Vanished
             //                 plus specific APIs to clean this up.
             List<ActivityManager.RunningTaskInfo> primaryChildren =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
index cf9d43e..d70484e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.statusbar.phone;
 
-import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
-
 import android.annotation.IntDef;
 import android.content.Context;
 import android.content.res.ColorStateList;
@@ -30,241 +28,98 @@
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.AttributeSet;
-import android.view.ViewTreeObserver;
-import android.view.accessibility.AccessibilityNodeInfo;
+import android.util.SparseArray;
+import android.view.ViewTreeObserver.OnPreDrawListener;
 
 import com.android.internal.graphics.ColorUtils;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.Dependency;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.KeyguardAffordanceView;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
-import com.android.systemui.statusbar.phone.ScrimController.ScrimVisibility;
-import com.android.systemui.statusbar.policy.AccessibilityController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
-import javax.inject.Inject;
-import javax.inject.Named;
-
 /**
  * Manages the different states and animations of the unlock icon.
  */
-public class LockIcon extends KeyguardAffordanceView implements
-        ViewTreeObserver.OnPreDrawListener {
+public class LockIcon extends KeyguardAffordanceView {
 
-    private static final int STATE_LOCKED = 0;
-    private static final int STATE_LOCK_OPEN = 1;
-    private static final int STATE_SCANNING_FACE = 2;
-    private static final int STATE_BIOMETRICS_ERROR = 3;
-    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    private final AccessibilityController mAccessibilityController;
-    private final KeyguardStateController mKeyguardStateController;
-    private final KeyguardBypassController mBypassController;
-    private final NotificationWakeUpCoordinator mWakeUpCoordinator;
-    private final HeadsUpManagerPhone mHeadsUpManager;
-
-    private int mLastState = 0;
-    private boolean mForceUpdate;
-    private boolean mTransientBiometricsError;
-    private boolean mIsFaceUnlockState;
-    private boolean mSimLocked;
-    private int mDensity;
+    static final int STATE_LOCKED = 0;
+    static final int STATE_LOCK_OPEN = 1;
+    static final int STATE_SCANNING_FACE = 2;
+    static final int STATE_BIOMETRICS_ERROR = 3;
+    private float mDozeAmount;
+    private int mIconColor;
+    private StateProvider mStateProvider;
+    private int mOldState;
     private boolean mPulsing;
     private boolean mDozing;
-    private boolean mDocked;
-    private boolean mBlockUpdates;
-    private int mIconColor;
-    private float mDozeAmount;
-    private boolean mBouncerShowingScrimmed;
-    private boolean mWakeAndUnlockRunning;
-    private boolean mKeyguardShowing;
-    private boolean mShowingLaunchAffordance;
     private boolean mKeyguardJustShown;
-    private boolean mUpdatePending;
-    private boolean mBouncerPreHideAnimation;
-    private int mStatusBarState = StatusBarState.SHADE;
+    private final SparseArray<Drawable> mDrawableCache = new SparseArray<>();
 
-    private final KeyguardStateController.Callback mKeyguardMonitorCallback =
-            new KeyguardStateController.Callback() {
-                @Override
-                public void onKeyguardShowingChanged() {
-                    boolean force = false;
-                    boolean wasShowing = mKeyguardShowing;
-                    mKeyguardShowing = mKeyguardStateController.isShowing();
-                    if (!wasShowing && mKeyguardShowing && mBlockUpdates) {
-                        mBlockUpdates = false;
-                        force = true;
-                    }
-                    if (!wasShowing && mKeyguardShowing) {
-                        mKeyguardJustShown = true;
-                    }
-                    update(force);
-                }
+    private final OnPreDrawListener mOnPreDrawListener = new OnPreDrawListener() {
+        @Override
+        public boolean onPreDraw() {
+            getViewTreeObserver().removeOnPreDrawListener(this);
 
-                @Override
-                public void onKeyguardFadingAwayChanged() {
-                    if (!mKeyguardStateController.isKeyguardFadingAway()) {
-                        mBouncerPreHideAnimation = false;
-                        if (mBlockUpdates) {
-                            mBlockUpdates = false;
-                            update(true /* force */);
-                        }
-                    }
-                }
+            int newState = mStateProvider.getState();
+            Drawable icon = getIcon(newState);
+            setImageDrawable(icon, false);
 
-                @Override
-                public void onUnlockedChanged() {
-                    update();
-                }
-            };
+            if (newState == STATE_SCANNING_FACE) {
+                announceForAccessibility(getResources().getString(
+                        R.string.accessibility_scanning_face));
+            }
 
-    @Inject
-    public LockIcon(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
-            AccessibilityController accessibilityController,
-            KeyguardBypassController bypassController,
-            NotificationWakeUpCoordinator wakeUpCoordinator,
-            KeyguardStateController keyguardStateController,
-            HeadsUpManagerPhone headsUpManager) {
+            if (icon instanceof AnimatedVectorDrawable) {
+                final AnimatedVectorDrawable animation = (AnimatedVectorDrawable) icon;
+                animation.forceAnimationOnUI();
+                animation.clearAnimationCallbacks();
+                animation.registerAnimationCallback(
+                        new Animatable2.AnimationCallback() {
+                            @Override
+                            public void onAnimationEnd(Drawable drawable) {
+                                if (getDrawable() == animation
+                                        && newState == mStateProvider.getState()
+                                        && newState == STATE_SCANNING_FACE) {
+                                    animation.start();
+                                } else {
+                                    Trace.endAsyncSection("LockIcon#Animation", newState);
+                                }
+                            }
+                        });
+                Trace.beginAsyncSection("LockIcon#Animation", newState);
+                animation.start();
+            }
+
+            return true;
+        }
+    };
+
+    public LockIcon(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mContext = context;
-        mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
-        mAccessibilityController = accessibilityController;
-        mBypassController = bypassController;
-        mWakeUpCoordinator = wakeUpCoordinator;
-        mKeyguardStateController = keyguardStateController;
-        mHeadsUpManager = headsUpManager;
     }
 
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        mKeyguardStateController.addCallback(mKeyguardMonitorCallback);
-        mSimLocked = mKeyguardUpdateMonitor.isSimPinSecure();
-        update();
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        mKeyguardStateController.removeCallback(mKeyguardMonitorCallback);
-    }
-
-    /**
-     * If we're currently presenting an authentication error message.
-     */
-    public void setTransientBiometricsError(boolean transientBiometricsError) {
-        mTransientBiometricsError = transientBiometricsError;
-        update();
+    void setStateProvider(StateProvider stateProvider) {
+        mStateProvider = stateProvider;
     }
 
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
-        final int density = newConfig.densityDpi;
-        if (density != mDensity) {
-            mDensity = density;
-            update();
-        }
-    }
-
-    public void update() {
-        update(false /* force */);
-    }
-
-    public void update(boolean force) {
-        if (force) {
-            mForceUpdate = true;
-        }
-        if (!mUpdatePending) {
-            mUpdatePending = true;
-            getViewTreeObserver().addOnPreDrawListener(this);
-        }
-    }
-
-    @Override
-    public boolean onPreDraw() {
-        mUpdatePending = false;
-        getViewTreeObserver().removeOnPreDrawListener(this);
-
-        int state = getState();
-        int lastState = mLastState;
-        boolean keyguardJustShown = mKeyguardJustShown;
-        mIsFaceUnlockState = state == STATE_SCANNING_FACE;
-        mLastState = state;
-        mKeyguardJustShown = false;
-
-        boolean shouldUpdate = lastState != state || mForceUpdate;
-        if (mBlockUpdates && canBlockUpdates()) {
-            shouldUpdate = false;
-        }
-        if (shouldUpdate) {
-            mForceUpdate = false;
-            @LockAnimIndex final int lockAnimIndex = getAnimationIndexForTransition(lastState,
-                    state, mPulsing, mDozing, keyguardJustShown);
-            boolean isAnim = lockAnimIndex != -1;
-            int iconRes = isAnim ? getThemedAnimationResId(lockAnimIndex) : getIconForState(state);
-
-            Drawable icon = mContext.getDrawable(iconRes);
-            final AnimatedVectorDrawable animation = icon instanceof AnimatedVectorDrawable
-                    ? (AnimatedVectorDrawable) icon
-                    : null;
-            setImageDrawable(icon, false);
-            if (mIsFaceUnlockState) {
-                announceForAccessibility(getContext().getString(
-                        R.string.accessibility_scanning_face));
-            }
-
-            if (animation != null && isAnim) {
-                animation.forceAnimationOnUI();
-                animation.clearAnimationCallbacks();
-                animation.registerAnimationCallback(new Animatable2.AnimationCallback() {
-                    @Override
-                    public void onAnimationEnd(Drawable drawable) {
-                        if (getDrawable() == animation && state == getState()
-                                && doesAnimationLoop(lockAnimIndex)) {
-                            animation.start();
-                        } else {
-                            Trace.endAsyncSection("LockIcon#Animation", state);
-                        }
-                    }
-                });
-                Trace.beginAsyncSection("LockIcon#Animation", state);
-                animation.start();
-            }
-        }
-        updateDarkTint();
-
-        updateIconVisibility();
-        updateClickability();
-
-        return true;
+        mDrawableCache.clear();
     }
 
     /**
      * Update the icon visibility
      * @return true if the visibility changed
      */
-    boolean updateIconVisibility() {
-        boolean onAodNotPulsingOrDocked = mDozing && (!mPulsing || mDocked);
-        boolean invisible = onAodNotPulsingOrDocked || mWakeAndUnlockRunning
-                || mShowingLaunchAffordance;
-        if (mBypassController.getBypassEnabled() && !mBouncerShowingScrimmed) {
-            if ((mHeadsUpManager.isHeadsUpGoingAway() || mHeadsUpManager.hasPinnedHeadsUp()
-                    || mStatusBarState == StatusBarState.KEYGUARD)
-                    && !mWakeUpCoordinator.getNotificationsFullyHidden()) {
-                invisible = true;
-            }
-        }
-        boolean wasInvisible = getVisibility() == INVISIBLE;
-        if (invisible != wasInvisible) {
-            setVisibility(invisible ? INVISIBLE : VISIBLE);
+    boolean updateIconVisibility(boolean visible) {
+        boolean wasVisible = getVisibility() == VISIBLE;
+        if (visible != wasVisible) {
+            setVisibility(visible ? VISIBLE : INVISIBLE);
             animate().cancel();
-            if (!invisible) {
+            if (visible) {
                 setScaleX(0);
                 setScaleY(0);
                 animate()
@@ -280,49 +135,47 @@
         return false;
     }
 
-    private boolean canBlockUpdates() {
-        return mKeyguardShowing || mKeyguardStateController.isKeyguardFadingAway();
+    void update(int oldState, boolean pulsing, boolean dozing, boolean keyguardJustShown) {
+        mOldState = oldState;
+        mPulsing = pulsing;
+        mDozing = dozing;
+        mKeyguardJustShown = keyguardJustShown;
+
+        getViewTreeObserver().addOnPreDrawListener(mOnPreDrawListener);
     }
 
-    private void updateClickability() {
-        if (mAccessibilityController == null) {
-            return;
+    void setDozeAmount(float dozeAmount) {
+        mDozeAmount = dozeAmount;
+        updateDarkTint();
+    }
+
+    void onThemeChange(int iconColor) {
+        mDrawableCache.clear();
+        mIconColor = iconColor;
+        updateDarkTint();
+    }
+
+    private void updateDarkTint() {
+        int color = ColorUtils.blendARGB(mIconColor, Color.WHITE, mDozeAmount);
+        setImageTintList(ColorStateList.valueOf(color));
+    }
+
+    private Drawable getIcon(int newState) {
+        @LockAnimIndex final int lockAnimIndex =
+                getAnimationIndexForTransition(mOldState, newState, mPulsing, mDozing,
+                        mKeyguardJustShown);
+
+        boolean isAnim = lockAnimIndex != -1;
+        int iconRes = isAnim ? getThemedAnimationResId(lockAnimIndex) : getIconForState(newState);
+
+        if (!mDrawableCache.contains(iconRes)) {
+            mDrawableCache.put(iconRes, getResources().getDrawable(iconRes));
         }
-        boolean canLock = mKeyguardStateController.isMethodSecure()
-                && mKeyguardStateController.canDismissLockScreen();
-        boolean clickToUnlock = mAccessibilityController.isAccessibilityEnabled();
-        setClickable(clickToUnlock);
-        setLongClickable(canLock && !clickToUnlock);
-        setFocusable(mAccessibilityController.isAccessibilityEnabled());
+
+        return mDrawableCache.get(iconRes);
     }
 
-    @Override
-    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
-        super.onInitializeAccessibilityNodeInfo(info);
-        boolean fingerprintRunning = mKeyguardUpdateMonitor.isFingerprintDetectionRunning();
-        // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong
-        // as long as primary auth, i.e. PIN/pattern/password, is not required), so it's ok to
-        // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the
-        // check of whether non-strong biometric is allowed
-        boolean unlockingAllowed = mKeyguardUpdateMonitor
-                        .isUnlockingWithBiometricAllowed(true /* isStrongBiometric */);
-        if (fingerprintRunning && unlockingAllowed) {
-            AccessibilityNodeInfo.AccessibilityAction unlock
-                    = new AccessibilityNodeInfo.AccessibilityAction(
-                    AccessibilityNodeInfo.ACTION_CLICK,
-                    getContext().getString(R.string.accessibility_unlock_without_fingerprint));
-            info.addAction(unlock);
-            info.setHintText(getContext().getString(
-                    R.string.accessibility_waiting_for_fingerprint));
-        } else if (mIsFaceUnlockState) {
-            //Avoid 'button' to be spoken for scanning face
-            info.setClassName(LockIcon.class.getName());
-            info.setContentDescription(getContext().getString(
-                R.string.accessibility_scanning_face));
-        }
-    }
-
-    private int getIconForState(int state) {
+    static int getIconForState(int state) {
         int iconRes;
         switch (state) {
             case STATE_LOCKED:
@@ -343,11 +196,7 @@
         return iconRes;
     }
 
-    private boolean doesAnimationLoop(@LockAnimIndex int lockAnimIndex) {
-        return lockAnimIndex == SCANNING;
-    }
-
-    private static int getAnimationIndexForTransition(int oldState, int newState, boolean pulsing,
+    static int getAnimationIndexForTransition(int oldState, int newState, boolean pulsing,
             boolean dozing, boolean keyguardJustShown) {
 
         // Never animate when screen is off
@@ -367,42 +216,10 @@
         return -1;
     }
 
-    public void setBouncerShowingScrimmed(boolean bouncerShowing) {
-        mBouncerShowingScrimmed = bouncerShowing;
-        if (mBypassController.getBypassEnabled()) {
-            update();
-        }
-    }
-
-    /**
-     * Animate padlock opening when bouncer challenge is solved.
-     */
-    public void onBouncerPreHideAnimation() {
-        mBouncerPreHideAnimation = true;
-        update();
-    }
-
-    void setIconColor(int iconColor) {
-        mIconColor = iconColor;
-        updateDarkTint();
-    }
-
-    void setSimLocked(boolean simLocked) {
-        mSimLocked = simLocked;
-    }
-
-    /** Set if the device is docked. */
-    public void setDocked(boolean docked) {
-        if (mDocked != docked) {
-            mDocked = docked;
-            update();
-        }
-    }
-
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({ERROR, UNLOCK, LOCK, SCANNING})
     @interface LockAnimIndex {}
-    private static final int ERROR = 0, UNLOCK = 1, LOCK = 2, SCANNING = 3;
+    static final int ERROR = 0, UNLOCK = 1, LOCK = 2, SCANNING = 3;
     private static final int[][] LOCK_ANIM_RES_IDS = new int[][] {
             {
                     R.anim.lock_to_error,
@@ -433,7 +250,7 @@
     private int getThemedAnimationResId(@LockAnimIndex int lockAnimIndex) {
         final String setting = TextUtils.emptyIfNull(
                 Settings.Secure.getString(getContext().getContentResolver(),
-                Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES));
+                        Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES));
         if (setting.contains("com.android.theme.icon_pack.circular.android")) {
             return LOCK_ANIM_RES_IDS[1][lockAnimIndex];
         } else if (setting.contains("com.android.theme.icon_pack.filled.android")) {
@@ -444,83 +261,8 @@
         return LOCK_ANIM_RES_IDS[0][lockAnimIndex];
     }
 
-    private int getState() {
-        KeyguardUpdateMonitor updateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
-        if ((mKeyguardStateController.canDismissLockScreen() || !mKeyguardShowing
-                || mKeyguardStateController.isKeyguardGoingAway()) && !mSimLocked) {
-            return STATE_LOCK_OPEN;
-        } else if (mTransientBiometricsError) {
-            return STATE_BIOMETRICS_ERROR;
-        } else if (updateMonitor.isFaceDetectionRunning() && !mPulsing) {
-            return STATE_SCANNING_FACE;
-        } else {
-            return STATE_LOCKED;
-        }
+    interface StateProvider {
+        int getState();
     }
 
-    /**
-     * When keyguard is in pulsing (AOD2) state.
-     * @param pulsing {@code true} when pulsing.
-     */
-    public void setPulsing(boolean pulsing) {
-        mPulsing = pulsing;
-        update();
-    }
-
-    private void updateDarkTint() {
-        int color = ColorUtils.blendARGB(mIconColor, Color.WHITE, mDozeAmount);
-        setImageTintList(ColorStateList.valueOf(color));
-    }
-
-    /**
-     * We need to hide the lock whenever there's a fingerprint unlock, otherwise you'll see the
-     * icon on top of the black front scrim.
-     * @param wakeAndUnlock are we wake and unlocking
-     * @param isUnlock are we currently unlocking
-     */
-    public void onBiometricAuthModeChanged(boolean wakeAndUnlock, boolean isUnlock) {
-        if (wakeAndUnlock) {
-            mWakeAndUnlockRunning = true;
-        }
-        if (isUnlock && mBypassController.getBypassEnabled() && canBlockUpdates()) {
-            // We don't want the icon to change while we are unlocking
-            mBlockUpdates = true;
-        }
-        update();
-    }
-
-    /**
-     * When we're launching an affordance, like double pressing power to open camera.
-     */
-    public void onShowingLaunchAffordanceChanged(boolean showing) {
-        mShowingLaunchAffordance = showing;
-        update();
-    }
-
-    /**
-     * Called whenever the scrims become opaque, transparent or semi-transparent.
-     */
-    public void onScrimVisibilityChanged(@ScrimVisibility int scrimsVisible) {
-        if (mWakeAndUnlockRunning
-                && scrimsVisible == ScrimController.TRANSPARENT) {
-            mWakeAndUnlockRunning = false;
-            update();
-        }
-    }
-
-    void setDozing(boolean dozing) {
-        mDozing = dozing;
-        update();
-    }
-
-    void setDozeAmount(float dozeAmount) {
-        mDozeAmount = dozeAmount;
-        updateDarkTint();
-    }
-
-    /** Set the StatusBarState. */
-    public void setStatusBarState(int statusBarState) {
-        mStatusBarState = statusBarState;
-        updateIconVisibility();
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java
index 2b1a8a4..f7c861b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java
@@ -16,11 +16,19 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static com.android.systemui.statusbar.phone.LockIcon.STATE_BIOMETRICS_ERROR;
+import static com.android.systemui.statusbar.phone.LockIcon.STATE_LOCKED;
+import static com.android.systemui.statusbar.phone.LockIcon.STATE_LOCK_OPEN;
+import static com.android.systemui.statusbar.phone.LockIcon.STATE_SCANNING_FACE;
+
+import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Color;
 import android.hardware.biometrics.BiometricSourceType;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityNodeInfo;
 
 import androidx.annotation.Nullable;
 
@@ -29,15 +37,18 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.KeyguardIndicationController;
+import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator.WakeUpListener;
 import com.android.systemui.statusbar.policy.AccessibilityController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import java.util.Optional;
 
@@ -59,6 +70,21 @@
     private final NotificationWakeUpCoordinator mNotificationWakeUpCoordinator;
     private final KeyguardBypassController mKeyguardBypassController;
     private final Optional<DockManager> mDockManager;
+    private final KeyguardStateController mKeyguardStateController;
+    private final Resources mResources;
+    private final HeadsUpManagerPhone mHeadsUpManagerPhone;
+    private boolean mKeyguardShowing;
+    private boolean mKeyguardJustShown;
+    private boolean mBlockUpdates;
+    private boolean mPulsing;
+    private boolean mDozing;
+    private boolean mSimLocked;
+    private boolean mTransientBiometricsError;
+    private boolean mDocked;
+    private boolean mWakeAndUnlockRunning;
+    private boolean mShowingLaunchAffordance;
+    private boolean mBouncerShowingScrimmed;
+    private int mStatusBarState = StatusBarState.SHADE;
     private LockIcon mLockIcon;
 
     private View.OnAttachStateChangeListener mOnAttachStateChangeListener =
@@ -69,10 +95,13 @@
             mConfigurationController.addCallback(mConfigurationListener);
             mNotificationWakeUpCoordinator.addListener(mWakeUpListener);
             mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback);
+            mKeyguardStateController.addCallback(mKeyguardMonitorCallback);
 
             mDockManager.ifPresent(dockManager -> dockManager.addListener(mDockEventListener));
 
+            mSimLocked = mKeyguardUpdateMonitor.isSimPinSecure();
             mConfigurationListener.onThemeChanged();
+            update();
         }
 
         @Override
@@ -81,7 +110,7 @@
             mConfigurationController.removeCallback(mConfigurationListener);
             mNotificationWakeUpCoordinator.removeListener(mWakeUpListener);
             mKeyguardUpdateMonitor.removeCallback(mUpdateMonitorCallback);
-
+            mKeyguardStateController.removeCallback(mKeyguardMonitorCallback);
 
             mDockManager.ifPresent(dockManager -> dockManager.removeListener(mDockEventListener));
         }
@@ -91,32 +120,44 @@
             new StatusBarStateController.StateListener() {
                 @Override
                 public void onDozingChanged(boolean isDozing) {
-                    mLockIcon.setDozing(isDozing);
+                    setDozing(isDozing);
                 }
 
                 @Override
                 public void onDozeAmountChanged(float linear, float eased) {
-                    mLockIcon.setDozeAmount(eased);
+                    if (mLockIcon != null) {
+                        mLockIcon.setDozeAmount(eased);
+                    }
                 }
 
                 @Override
                 public void onStateChanged(int newState) {
-                    mLockIcon.setStatusBarState(newState);
+                    setStatusBarState(newState);
                 }
             };
 
     private final ConfigurationListener mConfigurationListener = new ConfigurationListener() {
+        private int mDensity;
+
         @Override
         public void onThemeChanged() {
+            if (mLockIcon == null) {
+                return;
+            }
+
             TypedArray typedArray = mLockIcon.getContext().getTheme().obtainStyledAttributes(
                     null, new int[]{ R.attr.wallpaperTextColor }, 0, 0);
             int iconColor = typedArray.getColor(0, Color.WHITE);
             typedArray.recycle();
-            mLockIcon.setIconColor(iconColor);
+            mLockIcon.onThemeChange(iconColor);
         }
 
         @Override
         public void onDensityOrFontScaleChanged() {
+            if (mLockIcon == null) {
+                return;
+            }
+
             ViewGroup.LayoutParams lp = mLockIcon.getLayoutParams();
             if (lp == null) {
                 return;
@@ -125,24 +166,41 @@
             lp.height = mLockIcon.getResources().getDimensionPixelSize(
                     R.dimen.keyguard_lock_height);
             mLockIcon.setLayoutParams(lp);
-            mLockIcon.update(true /* force */);
+            update(true /* force */);
         }
 
         @Override
         public void onLocaleListChanged() {
+            if (mLockIcon == null) {
+                return;
+            }
+
             mLockIcon.setContentDescription(
                     mLockIcon.getResources().getText(R.string.accessibility_unlock_button));
-            mLockIcon.update(true /* force */);
+            update(true /* force */);
+        }
+
+        @Override
+        public void onConfigChanged(Configuration newConfig) {
+            final int density = newConfig.densityDpi;
+            if (density != mDensity) {
+                mDensity = density;
+                update();
+            }
         }
     };
 
     private final WakeUpListener mWakeUpListener = new WakeUpListener() {
         @Override
+        public void onPulseExpansionChanged(boolean expandingChanged) {
+        }
+
+        @Override
         public void onFullyHiddenChanged(boolean isFullyHidden) {
             if (mKeyguardBypassController.getBypassEnabled()) {
-                boolean changed = mLockIcon.updateIconVisibility();
+                boolean changed = updateIconVisibility();
                 if (changed) {
-                    mLockIcon.update();
+                    update();
                 }
             }
         }
@@ -152,30 +210,103 @@
             new KeyguardUpdateMonitorCallback() {
                 @Override
                 public void onSimStateChanged(int subId, int slotId, int simState) {
-                    mLockIcon.setSimLocked(mKeyguardUpdateMonitor.isSimPinSecure());
-                    mLockIcon.update();
+                    mSimLocked = mKeyguardUpdateMonitor.isSimPinSecure();
+                    update();
                 }
 
                 @Override
                 public void onKeyguardVisibilityChanged(boolean showing) {
-                    mLockIcon.update();
+                    update();
                 }
 
                 @Override
                 public void onBiometricRunningStateChanged(boolean running,
                         BiometricSourceType biometricSourceType) {
-                    mLockIcon.update();
+                    update();
                 }
 
                 @Override
                 public void onStrongAuthStateChanged(int userId) {
-                    mLockIcon.update();
+                    update();
                 }
             };
 
     private final DockManager.DockEventListener mDockEventListener =
-            event -> mLockIcon.setDocked(event == DockManager.STATE_DOCKED
-                    || event == DockManager.STATE_DOCKED_HIDE);
+            event -> {
+                boolean docked =
+                        event == DockManager.STATE_DOCKED || event == DockManager.STATE_DOCKED_HIDE;
+                if (docked != mDocked) {
+                    mDocked = docked;
+                    update();
+                }
+            };
+
+    private final KeyguardStateController.Callback mKeyguardMonitorCallback =
+            new KeyguardStateController.Callback() {
+                @Override
+                public void onKeyguardShowingChanged() {
+                    boolean force = false;
+                    boolean wasShowing = mKeyguardShowing;
+                    mKeyguardShowing = mKeyguardStateController.isShowing();
+                    if (!wasShowing && mKeyguardShowing && mBlockUpdates) {
+                        mBlockUpdates = false;
+                        force = true;
+                    }
+                    if (!wasShowing && mKeyguardShowing) {
+                        mKeyguardJustShown = true;
+                    }
+                    update(force);
+                }
+
+                @Override
+                public void onKeyguardFadingAwayChanged() {
+                    if (!mKeyguardStateController.isKeyguardFadingAway()) {
+                        if (mBlockUpdates) {
+                            mBlockUpdates = false;
+                            update(true /* force */);
+                        }
+                    }
+                }
+
+                @Override
+                public void onUnlockedChanged() {
+                    update();
+                }
+            };
+
+    private final View.AccessibilityDelegate mAccessibilityDelegate =
+            new View.AccessibilityDelegate() {
+                @Override
+                public void onInitializeAccessibilityNodeInfo(View host,
+                        AccessibilityNodeInfo info) {
+                    super.onInitializeAccessibilityNodeInfo(host, info);
+                    boolean fingerprintRunning =
+                            mKeyguardUpdateMonitor.isFingerprintDetectionRunning();
+                    // Only checking if unlocking with Biometric is allowed (no matter strong or
+                    // non-strong as long as primary auth, i.e. PIN/pattern/password, is not
+                    // required), so it's ok to pass true for isStrongBiometric to
+                    // isUnlockingWithBiometricAllowed() to bypass the check of whether non-strong
+                    // biometric is allowed
+                    boolean unlockingAllowed = mKeyguardUpdateMonitor
+                            .isUnlockingWithBiometricAllowed(true /* isStrongBiometric */);
+                    if (fingerprintRunning && unlockingAllowed) {
+                        AccessibilityNodeInfo.AccessibilityAction unlock =
+                                new AccessibilityNodeInfo.AccessibilityAction(
+                                AccessibilityNodeInfo.ACTION_CLICK,
+                                mResources.getString(
+                                        R.string.accessibility_unlock_without_fingerprint));
+                        info.addAction(unlock);
+                        info.setHintText(mResources.getString(
+                                R.string.accessibility_waiting_for_fingerprint));
+                    } else if (getState() == STATE_SCANNING_FACE) {
+                        //Avoid 'button' to be spoken for scanning face
+                        info.setClassName(LockIcon.class.getName());
+                        info.setContentDescription(mResources.getString(
+                                R.string.accessibility_scanning_face));
+                    }
+                }
+            };
+    private int mLastState;
 
     @Inject
     public LockscreenLockIconController(LockscreenGestureLogger lockscreenGestureLogger,
@@ -188,7 +319,10 @@
             ConfigurationController configurationController,
             NotificationWakeUpCoordinator notificationWakeUpCoordinator,
             KeyguardBypassController keyguardBypassController,
-            @Nullable DockManager dockManager) {
+            @Nullable DockManager dockManager,
+            KeyguardStateController keyguardStateController,
+            @Main Resources resources,
+            HeadsUpManagerPhone headsUpManagerPhone) {
         mLockscreenGestureLogger = lockscreenGestureLogger;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mLockPatternUtils = lockPatternUtils;
@@ -200,24 +334,31 @@
         mNotificationWakeUpCoordinator = notificationWakeUpCoordinator;
         mKeyguardBypassController = keyguardBypassController;
         mDockManager = dockManager == null ? Optional.empty() : Optional.of(dockManager);
+        mKeyguardStateController = keyguardStateController;
+        mResources = resources;
+        mHeadsUpManagerPhone = headsUpManagerPhone;
 
         mKeyguardIndicationController.setLockIconController(this);
     }
 
     /**
      * Associate the controller with a {@link LockIcon}
+     *
+     * TODO: change to an init method and inject the view.
      */
     public void attach(LockIcon lockIcon) {
         mLockIcon = lockIcon;
 
         mLockIcon.setOnClickListener(this::handleClick);
         mLockIcon.setOnLongClickListener(this::handleLongClick);
+        mLockIcon.setAccessibilityDelegate(mAccessibilityDelegate);
+        mLockIcon.setStateProvider(this::getState);
 
         if (mLockIcon.isAttachedToWindow()) {
             mOnAttachStateChangeListener.onViewAttachedToWindow(mLockIcon);
         }
         mLockIcon.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
-        mLockIcon.setStatusBarState(mStatusBarStateController.getState());
+        setStatusBarState(mStatusBarStateController.getState());
     }
 
     public LockIcon getView() {
@@ -228,8 +369,10 @@
      * Called whenever the scrims become opaque, transparent or semi-transparent.
      */
     public void onScrimVisibilityChanged(Integer scrimsVisible) {
-        if (mLockIcon != null) {
-            mLockIcon.onScrimVisibilityChanged(scrimsVisible);
+        if (mWakeAndUnlockRunning
+                && scrimsVisible == ScrimController.TRANSPARENT) {
+            mWakeAndUnlockRunning = false;
+            update();
         }
     }
 
@@ -237,55 +380,56 @@
      * Propagate {@link StatusBar} pulsing state.
      */
     public void setPulsing(boolean pulsing) {
-        if (mLockIcon != null) {
-            mLockIcon.setPulsing(pulsing);
-        }
+        mPulsing = pulsing;
+        update();
     }
 
     /**
-     * Called when the biometric authentication mode changes.
-     *
-     * @param wakeAndUnlock If the type is {@link BiometricUnlockController#isWakeAndUnlock()}
-     * @param isUnlock      If the type is {@link BiometricUnlockController#isBiometricUnlock()} ()
+     * We need to hide the lock whenever there's a fingerprint unlock, otherwise you'll see the
+     * icon on top of the black front scrim.
+     * @param wakeAndUnlock are we wake and unlocking
+     * @param isUnlock are we currently unlocking
      */
     public void onBiometricAuthModeChanged(boolean wakeAndUnlock, boolean isUnlock) {
-        if (mLockIcon != null) {
-            mLockIcon.onBiometricAuthModeChanged(wakeAndUnlock, isUnlock);
+        if (wakeAndUnlock) {
+            mWakeAndUnlockRunning = true;
         }
+        if (isUnlock && mKeyguardBypassController.getBypassEnabled() && canBlockUpdates()) {
+            // We don't want the icon to change while we are unlocking
+            mBlockUpdates = true;
+        }
+        update();
     }
 
     /**
      * When we're launching an affordance, like double pressing power to open camera.
      */
     public void onShowingLaunchAffordanceChanged(Boolean showing) {
-        if (mLockIcon != null) {
-            mLockIcon.onShowingLaunchAffordanceChanged(showing);
-        }
+        mShowingLaunchAffordance = showing;
+        update();
     }
 
     /** Sets whether the bouncer is showing. */
     public void setBouncerShowingScrimmed(boolean bouncerShowing) {
-        if (mLockIcon != null) {
-            mLockIcon.setBouncerShowingScrimmed(bouncerShowing);
+        mBouncerShowingScrimmed = bouncerShowing;
+        if (mKeyguardBypassController.getBypassEnabled()) {
+            update();
         }
     }
 
     /**
-     * When {@link KeyguardBouncer} starts to be dismissed and starts to play its animation.
+     * Animate padlock opening when bouncer challenge is solved.
      */
     public void onBouncerPreHideAnimation() {
-        if (mLockIcon != null) {
-            mLockIcon.onBouncerPreHideAnimation();
-        }
+        update();
     }
 
     /**
      * If we're currently presenting an authentication error message.
      */
     public void setTransientBiometricsError(boolean transientBiometricsError) {
-        if (mLockIcon != null) {
-            mLockIcon.setTransientBiometricsError(transientBiometricsError);
-        }
+        mTransientBiometricsError = transientBiometricsError;
+        update();
     }
 
     private boolean handleLongClick(View view) {
@@ -306,4 +450,90 @@
         }
         mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
     }
+
+    private void update() {
+        update(false /* force */);
+    }
+
+    private void update(boolean force) {
+        int state = getState();
+        boolean shouldUpdate = mLastState != state || force;
+        if (mBlockUpdates && canBlockUpdates()) {
+            shouldUpdate = false;
+        }
+        if (shouldUpdate && mLockIcon != null) {
+            mLockIcon.update(mLastState, mPulsing, mDozing, mKeyguardJustShown);
+        }
+        mLastState = state;
+        mKeyguardJustShown = false;
+        updateIconVisibility();
+        updateClickability();
+    }
+
+    private int getState() {
+        if ((mKeyguardStateController.canDismissLockScreen() || !mKeyguardShowing
+                || mKeyguardStateController.isKeyguardGoingAway()) && !mSimLocked) {
+            return STATE_LOCK_OPEN;
+        } else if (mTransientBiometricsError) {
+            return STATE_BIOMETRICS_ERROR;
+        } else if (mKeyguardUpdateMonitor.isFaceDetectionRunning() && !mPulsing) {
+            return STATE_SCANNING_FACE;
+        } else {
+            return STATE_LOCKED;
+        }
+    }
+
+    private boolean canBlockUpdates() {
+        return mKeyguardShowing || mKeyguardStateController.isKeyguardFadingAway();
+    }
+
+    private void setDozing(boolean isDozing) {
+        mDozing = isDozing;
+        update();
+    }
+
+    /** Set the StatusBarState. */
+    private void setStatusBarState(int statusBarState) {
+        mStatusBarState = statusBarState;
+        updateIconVisibility();
+    }
+
+    /**
+     * Update the icon visibility
+     * @return true if the visibility changed
+     */
+    private boolean updateIconVisibility() {
+        boolean onAodNotPulsingOrDocked = mDozing && (!mPulsing || mDocked);
+        boolean invisible = onAodNotPulsingOrDocked || mWakeAndUnlockRunning
+                || mShowingLaunchAffordance;
+        if (mKeyguardBypassController.getBypassEnabled() && !mBouncerShowingScrimmed) {
+            if ((mHeadsUpManagerPhone.isHeadsUpGoingAway()
+                    || mHeadsUpManagerPhone.hasPinnedHeadsUp()
+                    || mStatusBarState == StatusBarState.KEYGUARD)
+                    && !mNotificationWakeUpCoordinator.getNotificationsFullyHidden()) {
+                invisible = true;
+            }
+        }
+
+        if (mLockIcon == null) {
+            return false;
+        }
+
+        return mLockIcon.updateIconVisibility(!invisible);
+    }
+
+    private void updateClickability() {
+        if (mAccessibilityController == null) {
+            return;
+        }
+        boolean canLock = mKeyguardStateController.isMethodSecure()
+                && mKeyguardStateController.canDismissLockScreen();
+        boolean clickToUnlock = mAccessibilityController.isAccessibilityEnabled();
+        if (mLockIcon != null) {
+            mLockIcon.setClickable(clickToUnlock);
+            mLockIcon.setLongClickable(canLock && !clickToUnlock);
+            mLockIcon.setFocusable(mAccessibilityController.isAccessibilityEnabled());
+        }
+    }
+
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
index 56aae17..c637123 100644
--- a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
+++ b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
@@ -34,7 +34,6 @@
 import com.android.systemui.qs.customize.QSCustomizer;
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
-import com.android.systemui.statusbar.phone.LockIcon;
 
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
@@ -148,11 +147,6 @@
         KeyguardMessageArea createKeyguardMessageArea();
 
         /**
-         * Creates the keyguard LockIcon.
-         */
-        LockIcon createLockIcon();
-
-        /**
          * Creates the QSPanel.
          */
         QSPanel createQSPanel();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index 6e612d7..4f16031 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -62,7 +62,9 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.model.SysUiState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationRemoveInterceptor;
@@ -136,6 +138,9 @@
     @Mock
     private FloatingContentCoordinator mFloatingContentCoordinator;
 
+    private SysUiState mSysUiState;
+    private boolean mSysUiStateBubblesExpanded;
+
     @Captor
     private ArgumentCaptor<NotificationEntryListener> mEntryListenerCaptor;
     @Captor
@@ -229,6 +234,11 @@
         mZenModeConfig.suppressedVisualEffects = 0;
         when(mZenModeController.getConfig()).thenReturn(mZenModeConfig);
 
+        mSysUiState = new SysUiState();
+        mSysUiState.addCallback(sysUiFlags ->
+                mSysUiStateBubblesExpanded =
+                        (sysUiFlags & QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED) != 0);
+
         TestableNotificationInterruptStateProviderImpl interruptionStateProvider =
                 new TestableNotificationInterruptStateProviderImpl(mContext.getContentResolver(),
                         mock(PowerManager.class),
@@ -257,7 +267,8 @@
                 mNotifPipeline,
                 mFeatureFlagsOldPipeline,
                 mDumpManager,
-                mFloatingContentCoordinator);
+                mFloatingContentCoordinator,
+                mSysUiState);
         mBubbleController.setBubbleStateChangeListener(mBubbleStateChangeListener);
         mBubbleController.setExpandListener(mBubbleExpandListener);
 
@@ -277,6 +288,7 @@
         assertTrue(mBubbleController.hasBubbles());
 
         verify(mBubbleStateChangeListener).onHasBubblesChanged(true);
+        assertFalse(mSysUiStateBubblesExpanded);
     }
 
     @Test
@@ -284,6 +296,7 @@
         assertFalse(mBubbleController.hasBubbles());
         mBubbleController.updateBubble(mRow.getEntry());
         assertTrue(mBubbleController.hasBubbles());
+        assertFalse(mSysUiStateBubblesExpanded);
     }
 
     @Test
@@ -300,6 +313,8 @@
         assertNull(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()));
         verify(mNotificationEntryManager, times(2)).updateNotifications(anyString());
         verify(mBubbleStateChangeListener).onHasBubblesChanged(false);
+
+        assertFalse(mSysUiStateBubblesExpanded);
     }
 
     @Test
@@ -323,6 +338,8 @@
         verify(mNotificationEntryManager, times(1)).performRemoveNotification(
                 eq(mRow.getEntry().getSbn()), anyInt());
         assertFalse(mBubbleController.hasBubbles());
+
+        assertFalse(mSysUiStateBubblesExpanded);
     }
 
     @Test
@@ -340,6 +357,8 @@
         verify(mNotificationEntryManager, times(3)).updateNotifications(any());
         assertNull(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()));
         assertNull(mBubbleData.getBubbleWithKey(mRow2.getEntry().getKey()));
+
+        assertFalse(mSysUiStateBubblesExpanded);
     }
 
     @Test
@@ -363,6 +382,8 @@
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
         assertTrue(mNotificationShadeWindowController.getBubbleExpanded());
 
+        assertTrue(mSysUiStateBubblesExpanded);
+
         // Make sure the notif is suppressed
         assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
                 mRow.getEntry()));
@@ -372,6 +393,8 @@
         verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getEntry().getKey());
         assertFalse(mBubbleController.isStackExpanded());
         assertFalse(mNotificationShadeWindowController.getBubbleExpanded());
+
+        assertFalse(mSysUiStateBubblesExpanded);
     }
 
     @Test
@@ -395,6 +418,8 @@
         assertTrue(mBubbleController.isStackExpanded());
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey());
 
+        assertTrue(mSysUiStateBubblesExpanded);
+
         // Last added is the one that is expanded
         assertEquals(mRow2.getEntry(), mBubbleData.getSelectedBubble().getEntry());
         assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
@@ -416,6 +441,8 @@
         // Collapse
         mBubbleController.collapseStack();
         assertFalse(mBubbleController.isStackExpanded());
+
+        assertFalse(mSysUiStateBubblesExpanded);
     }
 
     @Test
@@ -437,6 +464,8 @@
         assertTrue(mBubbleController.isStackExpanded());
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
 
+        assertTrue(mSysUiStateBubblesExpanded);
+
         // Notif is suppressed after expansion
         assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
                 mRow.getEntry()));
@@ -463,6 +492,8 @@
         assertTrue(mBubbleController.isStackExpanded());
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
 
+        assertTrue(mSysUiStateBubblesExpanded);
+
         // Notif is suppressed after expansion
         assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
                 mRow.getEntry()));
@@ -493,6 +524,8 @@
         BubbleStackView stackView = mBubbleController.getStackView();
         mBubbleController.expandStack();
 
+        assertTrue(mSysUiStateBubblesExpanded);
+
         assertTrue(mBubbleController.isStackExpanded());
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey());
 
@@ -522,6 +555,8 @@
         verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getEntry().getKey());
         verify(mBubbleStateChangeListener).onHasBubblesChanged(false);
         assertFalse(mBubbleController.hasBubbles());
+
+        assertFalse(mSysUiStateBubblesExpanded);
     }
 
     @Test
@@ -541,6 +576,8 @@
 
         // # of bubbles should change
         verify(mBubbleStateChangeListener).onHasBubblesChanged(true /* hasBubbles */);
+
+        assertFalse(mSysUiStateBubblesExpanded);
     }
 
     @Test
@@ -559,6 +596,8 @@
 
         // # of bubbles should change
         verify(mBubbleStateChangeListener).onHasBubblesChanged(true /* hasBubbles */);
+
+        assertTrue(mSysUiStateBubblesExpanded);
     }
 
     @Test
@@ -579,6 +618,8 @@
 
         // # of bubbles should change
         verify(mBubbleStateChangeListener).onHasBubblesChanged(true /* hasBubbles */);
+
+        assertFalse(mSysUiStateBubblesExpanded);
     }
 
     @Test
@@ -605,6 +646,8 @@
 
         // # of bubbles should change
         verify(mBubbleStateChangeListener).onHasBubblesChanged(true /* hasBubbles */);
+
+        assertFalse(mSysUiStateBubblesExpanded);
     }
 
     @Test
@@ -619,6 +662,8 @@
                 mRow.getEntry().getKey(), mRow.getEntry(), REASON_APP_CANCEL);
 
         mBubbleController.expandStackAndSelectBubble(key);
+
+        assertTrue(mSysUiStateBubblesExpanded);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
index 6244644..a31e3f8d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
@@ -58,6 +58,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.model.SysUiState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -132,6 +133,9 @@
     private KeyguardBypassController mKeyguardBypassController;
     @Mock
     private FloatingContentCoordinator mFloatingContentCoordinator;
+
+    private SysUiState mSysUiState = new SysUiState();
+
     @Captor
     private ArgumentCaptor<NotifCollectionListener> mNotifListenerCaptor;
     private TestableBubbleController mBubbleController;
@@ -242,7 +246,8 @@
                 mNotifPipeline,
                 mFeatureFlagsNewPipeline,
                 mDumpManager,
-                mFloatingContentCoordinator);
+                mFloatingContentCoordinator,
+                mSysUiState);
         mBubbleController.addNotifCallback(mNotifCallback);
         mBubbleController.setBubbleStateChangeListener(mBubbleStateChangeListener);
         mBubbleController.setExpandListener(mBubbleExpandListener);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java
index d3d90c4..f486102 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.model.SysUiState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -52,12 +53,13 @@
             NotifPipeline notifPipeline,
             FeatureFlags featureFlags,
             DumpManager dumpManager,
-            FloatingContentCoordinator floatingContentCoordinator) {
+            FloatingContentCoordinator floatingContentCoordinator,
+            SysUiState sysUiState) {
         super(context,
                 notificationShadeWindowController, statusBarStateController, shadeController,
                 data, Runnable::run, configurationController, interruptionStateProvider,
                 zenModeController, lockscreenUserManager, groupManager, entryManager,
-                notifPipeline, featureFlags, dumpManager, floatingContentCoordinator);
+                notifPipeline, featureFlags, dumpManager, floatingContentCoordinator, sysUiState);
         setInflateSynchronously(true);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenIconControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenIconControllerTest.java
index 487885a..85b5d70 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenIconControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenIconControllerTest.java
@@ -21,6 +21,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.content.res.Resources;
 import android.view.View;
 
 import androidx.test.filters.SmallTest;
@@ -35,6 +36,7 @@
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.policy.AccessibilityController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -71,6 +73,12 @@
     private KeyguardBypassController mKeyguardBypassController;
     @Mock
     private DockManager mDockManager;
+    @Mock
+    private KeyguardStateController mKeyguardStateController;
+    @Mock
+    private Resources mResources;
+    @Mock
+    private HeadsUpManagerPhone mHeadsUpManagerPhone;
 
 
     @Before
@@ -81,7 +89,8 @@
                 mLockscreenGestureLogger, mKeyguardUpdateMonitor, mLockPatternUtils,
                 mShadeController, mAccessibilityController, mKeyguardIndicationController,
                 mStatusBarStateController, mConfigurationController, mNotificationWakeUpCoordinator,
-                mKeyguardBypassController, mDockManager);
+                mKeyguardBypassController, mDockManager, mKeyguardStateController, mResources,
+                mHeadsUpManagerPhone);
 
         mLockIconController.attach(mLockIcon);
     }
diff --git a/packages/Tethering/common/TetheringLib/Android.bp b/packages/Tethering/common/TetheringLib/Android.bp
index 2fbba68..00d0d9c 100644
--- a/packages/Tethering/common/TetheringLib/Android.bp
+++ b/packages/Tethering/common/TetheringLib/Android.bp
@@ -60,6 +60,7 @@
     hostdex: true, // for hiddenapi check
     visibility: ["//frameworks/base/packages/Tethering:__subpackages__"],
     apex_available: ["com.android.tethering"],
+    permitted_packages: ["android.net"],
 }
 
 stubs_defaults {
diff --git a/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java b/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java
index 5de8171..4ba2c3d 100644
--- a/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java
+++ b/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java
@@ -199,7 +199,10 @@
             return false;
         }
 
-        if (!mImeInputViewStarted || !autofillId.equalsIgnoreSession(mImeFieldId)) {
+        // TODO(b/151846600): IME doesn't have access to the virtual id of the webview, so we
+        //  only compare the view id for now.
+        if (!mImeInputViewStarted || mImeFieldId == null
+                || autofillId.getViewId() != mImeFieldId.getViewId()) {
             if (sDebug) {
                 Log.d(TAG,
                         "onInlineSuggestionsResponseLocked not sent because input view is not "
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index ec84ae7..76a8e14 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -40,6 +40,7 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_TEST;
 import static android.net.NetworkCapabilities.TRANSPORT_VPN;
 import static android.net.NetworkPolicyManager.RULE_NONE;
 import static android.net.NetworkPolicyManager.uidRulesToString;
@@ -50,6 +51,7 @@
 
 import static java.util.Map.Entry;
 
+import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
@@ -2702,10 +2704,18 @@
 
             switch (msg.what) {
                 case NetworkAgent.EVENT_NETWORK_CAPABILITIES_CHANGED: {
-                    final NetworkCapabilities networkCapabilities = (NetworkCapabilities) msg.obj;
+                    NetworkCapabilities networkCapabilities = (NetworkCapabilities) msg.obj;
                     if (networkCapabilities.hasConnectivityManagedCapability()) {
                         Slog.wtf(TAG, "BUG: " + nai + " has CS-managed capability.");
                     }
+                    if (networkCapabilities.hasTransport(TRANSPORT_TEST)) {
+                        // Make sure the original object is not mutated. NetworkAgent normally
+                        // makes a copy of the capabilities when sending the message through
+                        // the Messenger, but if this ever changes, not making a defensive copy
+                        // here will give attack vectors to clients using this code path.
+                        networkCapabilities = new NetworkCapabilities(networkCapabilities);
+                        networkCapabilities.restrictCapabilitesForTestNetwork();
+                    }
                     updateCapabilities(nai.getCurrentScore(), nai, networkCapabilities);
                     break;
                 }
@@ -5778,7 +5788,16 @@
     public Network registerNetworkAgent(Messenger messenger, NetworkInfo networkInfo,
             LinkProperties linkProperties, NetworkCapabilities networkCapabilities,
             int currentScore, NetworkAgentConfig networkAgentConfig, int providerId) {
-        enforceNetworkFactoryPermission();
+        if (networkCapabilities.hasTransport(TRANSPORT_TEST)) {
+            enforceAnyPermissionOf(Manifest.permission.MANAGE_TEST_NETWORKS);
+            // Strictly, sanitizing here is unnecessary as the capabilities will be sanitized in
+            // the call to mixInCapabilities below anyway, but sanitizing here means the NAI never
+            // sees capabilities that may be malicious, which might prevent mistakes in the future.
+            networkCapabilities = new NetworkCapabilities(networkCapabilities);
+            networkCapabilities.restrictCapabilitesForTestNetwork();
+        } else {
+            enforceNetworkFactoryPermission();
+        }
 
         LinkProperties lp = new LinkProperties(linkProperties);
         lp.ensureDirectlyConnectedRoutes();
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 0bf81e0..bb94460 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -155,6 +155,7 @@
 import com.android.internal.util.Preconditions;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.pm.Installer;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
 import com.android.server.storage.AppFuseBridge;
 import com.android.server.storage.StorageSessionController;
 import com.android.server.storage.StorageSessionController.ExternalStorageServiceException;
@@ -3268,6 +3269,25 @@
         }
     }
 
+    @Override
+    public void fixupAppDir(String path) {
+        final Matcher matcher = KNOWN_APP_DIR_PATHS.matcher(path);
+        if (matcher.matches()) {
+            AndroidPackage pkg = mPmInternal.getPackage(matcher.group(3));
+            if (pkg != null) {
+                try {
+                    mVold.fixupAppDir(path + "/", pkg.getUid());
+                } catch (RemoteException | ServiceSpecificException e) {
+                    Log.e(TAG, "Failed to fixup app dir for " + pkg.getPackageName(), e);
+                }
+            } else {
+                Log.e(TAG, "Can't find package belonging to " + path);
+            }
+        } else {
+            Log.e(TAG, "Path " + path + " is not a valid application-specific directory");
+        }
+    }
+
     /** Not thread safe */
     class AppFuseMountScope extends AppFuseBridge.MountScope {
         private boolean mMounted = false;
diff --git a/services/core/java/com/android/server/am/BugReportHandlerUtil.java b/services/core/java/com/android/server/am/BugReportHandlerUtil.java
index 03f4a54..ba89fce 100644
--- a/services/core/java/com/android/server/am/BugReportHandlerUtil.java
+++ b/services/core/java/com/android/server/am/BugReportHandlerUtil.java
@@ -16,20 +16,15 @@
 
 package com.android.server.am;
 
-import static android.app.AppOpsManager.OP_NONE;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
 
-import android.app.Activity;
 import android.app.BroadcastOptions;
-import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.os.Binder;
-import android.os.BugreportManager;
-import android.os.BugreportParams;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.text.TextUtils;
@@ -115,17 +110,9 @@
         options.setBackgroundActivityStartsAllowed(true);
         final long identity = Binder.clearCallingIdentity();
         try {
-            // Handler app's BroadcastReceiver should call setResultCode(Activity.RESULT_OK) to
-            // let ResultBroadcastReceiver know the handler app is available.
-            context.sendOrderedBroadcastAsUser(intent,
-                    UserHandle.of(handlerUser),
+            context.sendBroadcastAsUser(intent, UserHandle.of(handlerUser),
                     android.Manifest.permission.DUMP,
-                    OP_NONE, options.toBundle(),
-                    new ResultBroadcastReceiver(),
-                    /* scheduler= */ null,
-                    Activity.RESULT_CANCELED,
-                    /* initialData= */ null,
-                    /* initialExtras= */ null);
+                    options.toBundle());
         } catch (RuntimeException e) {
             Slog.e(TAG, "Error while trying to launch bugreport handler app.", e);
             return false;
@@ -189,19 +176,4 @@
             Binder.restoreCallingIdentity(identity);
         }
     }
-
-    private static class ResultBroadcastReceiver extends BroadcastReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (getResultCode() == Activity.RESULT_OK) {
-                return;
-            }
-
-            Slog.w(TAG, "Request bug report because handler app seems to be not available.");
-            BugreportManager bugreportManager = context.getSystemService(BugreportManager.class);
-            bugreportManager.requestBugreport(
-                    new BugreportParams(BugreportParams.BUGREPORT_MODE_INTERACTIVE),
-                    /* shareTitle= */null, /* shareDescription= */ null);
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java
index 58e332a..c1fbcfb 100644
--- a/services/core/java/com/android/server/location/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/GnssLocationProvider.java
@@ -653,9 +653,6 @@
         mWakeupIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(ALARM_WAKEUP), 0);
         mTimeoutIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(ALARM_TIMEOUT), 0);
 
-        mNetworkConnectivityHandler = new GnssNetworkConnectivityHandler(context,
-                GnssLocationProvider.this::onNetworkAvailable, mLooper);
-
         // App ops service to keep track of who is accessing the GPS
         mAppOps = mContext.getSystemService(AppOpsManager.class);
 
@@ -677,6 +674,9 @@
         mNIHandler = new GpsNetInitiatedHandler(context,
                 mNetInitiatedListener,
                 mSuplEsEnabled);
+        mNetworkConnectivityHandler = new GnssNetworkConnectivityHandler(context,
+                GnssLocationProvider.this::onNetworkAvailable, mLooper, mNIHandler);
+
         sendMessage(INITIALIZE_HANDLER, 0, null);
 
         mGnssStatusListenerHelper = new GnssStatusListenerHelper(mContext, mHandler) {
diff --git a/services/core/java/com/android/server/location/GnssNetworkConnectivityHandler.java b/services/core/java/com/android/server/location/GnssNetworkConnectivityHandler.java
index 93227bd..5d6474b 100644
--- a/services/core/java/com/android/server/location/GnssNetworkConnectivityHandler.java
+++ b/services/core/java/com/android/server/location/GnssNetworkConnectivityHandler.java
@@ -29,12 +29,22 @@
 import android.provider.Telephony.Carriers;
 import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.PreciseCallState;
+import android.telephony.PhoneStateListener;
 import android.util.Log;
 
+import com.android.internal.location.GpsNetInitiatedHandler;
+
 import java.net.InetAddress;
 import java.net.UnknownHostException;
 import java.util.Arrays;
+import java.util.Map;
 import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Iterator;
 
 /**
  * Handles network connection requests and network state change updates for AGPS data download.
@@ -86,6 +96,9 @@
     private HashMap<Network, NetworkAttributes> mAvailableNetworkAttributes =
             new HashMap<>(HASH_MAP_INITIAL_CAPACITY_TO_TRACK_CONNECTED_NETWORKS);
 
+    // Phone State Listeners to track all the active sub IDs
+    private HashMap<Integer, SubIdPhoneStateListener> mPhoneStateListeners;
+
     private final ConnectivityManager mConnMgr;
 
     private final Handler mHandler;
@@ -94,6 +107,9 @@
     private int mAGpsDataConnectionState;
     private InetAddress mAGpsDataConnectionIpAddr;
     private int mAGpsType;
+    private int mActiveSubId = -1;
+    private final GpsNetInitiatedHandler mNiHandler;
+
 
     private final Context mContext;
 
@@ -166,18 +182,109 @@
 
     GnssNetworkConnectivityHandler(Context context,
             GnssNetworkListener gnssNetworkListener,
-            Looper looper) {
+            Looper looper,
+            GpsNetInitiatedHandler niHandler) {
         mContext = context;
         mGnssNetworkListener = gnssNetworkListener;
 
+    SubscriptionManager subManager = mContext.getSystemService(SubscriptionManager.class);
+        if (subManager != null) {
+            subManager.addOnSubscriptionsChangedListener(mOnSubscriptionsChangeListener);
+        }
+
         PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
         mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_KEY);
 
         mHandler = new Handler(looper);
+        mNiHandler = niHandler;
         mConnMgr = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
         mSuplConnectivityCallback = createSuplConnectivityCallback();
     }
 
+    /**
+     * SubId Phone State Listener is used cache the last active Sub ID when a call is made,
+     * which will be used during an emergency call to set the Network Specifier to the particular
+     * sub when an emergency supl connection is requested
+     */
+    private final class SubIdPhoneStateListener extends PhoneStateListener {
+        private Integer mSubId;
+        SubIdPhoneStateListener(Integer subId) {
+            mSubId = subId;
+        }
+        @Override
+        public void onPreciseCallStateChanged(PreciseCallState state) {
+            if (state.PRECISE_CALL_STATE_ACTIVE == state.getForegroundCallState()) {
+                mActiveSubId = mSubId;
+                if (DEBUG) Log.d(TAG, "mActiveSubId: " + mActiveSubId);
+            }
+        }
+    };
+
+    /**
+     * Subscription Changed Listener is used to get all active subscriptions and create a
+     * Phone State Listener for each Sub ID that we find in the active subscription list
+     */
+    private final SubscriptionManager.OnSubscriptionsChangedListener mOnSubscriptionsChangeListener
+            = new SubscriptionManager.OnSubscriptionsChangedListener() {
+        @Override
+        public void onSubscriptionsChanged() {
+            if (mPhoneStateListeners == null) {
+                // Capacity=2 Load-Factor=1.0, as typically no more than 2 SIMs
+                mPhoneStateListeners = new HashMap<Integer, SubIdPhoneStateListener>(2,1);
+            }
+            SubscriptionManager subManager = mContext.getSystemService(SubscriptionManager.class);
+            TelephonyManager telManager = mContext.getSystemService(TelephonyManager.class);
+            if (subManager != null && telManager != null) {
+                List<SubscriptionInfo> subscriptionInfoList =
+                        subManager.getActiveSubscriptionInfoList();
+                HashSet<Integer> activeSubIds = new HashSet<Integer>();
+                if (subscriptionInfoList != null) {
+                    if (DEBUG) Log.d(TAG, "Active Sub List size: " + subscriptionInfoList.size());
+                    // populate phone state listeners with all new active subs
+                    for (SubscriptionInfo subInfo : subscriptionInfoList) {
+                        activeSubIds.add(subInfo.getSubscriptionId());
+                        if (!mPhoneStateListeners.containsKey(subInfo.getSubscriptionId())) {
+                            TelephonyManager subIdTelManager =
+                                    telManager.createForSubscriptionId(subInfo.getSubscriptionId());
+                            if (subIdTelManager != null) {
+                                if (DEBUG) Log.d(TAG, "Listener sub" + subInfo.getSubscriptionId());
+                                SubIdPhoneStateListener subIdPhoneStateListener =
+                                        new SubIdPhoneStateListener(subInfo.getSubscriptionId());
+                                mPhoneStateListeners.put(subInfo.getSubscriptionId(),
+                                        subIdPhoneStateListener);
+                                subIdTelManager.listen(subIdPhoneStateListener,
+                                        PhoneStateListener.LISTEN_PRECISE_CALL_STATE);
+                            }
+                        }
+                    }
+                }
+                // clean up phone state listeners than no longer have active subs
+                Iterator<Map.Entry<Integer, SubIdPhoneStateListener> > iterator =
+                        mPhoneStateListeners.entrySet().iterator();
+                while (iterator.hasNext()) {
+                    Map.Entry<Integer, SubIdPhoneStateListener> element = iterator.next();
+                    if (!activeSubIds.contains(element.getKey())) {
+                        TelephonyManager subIdTelManager =
+                                telManager.createForSubscriptionId(element.getKey());
+                        if (subIdTelManager != null) {
+                            if (DEBUG) Log.d(TAG, "unregister listener sub " + element.getKey());
+                            subIdTelManager.listen(element.getValue(),
+                                                   PhoneStateListener.LISTEN_NONE);
+                            // removes the element from mPhoneStateListeners
+                            iterator.remove();
+                        } else {
+                            Log.e(TAG, "Telephony Manager for Sub " + element.getKey() + " null");
+                        }
+                    }
+                }
+                // clean up cached active phone call sub if it is no longer an active sub
+                if (!activeSubIds.contains(mActiveSubId)) {
+                    mActiveSubId = -1;
+                }
+            }
+        }
+    };
+
     void registerNetworkCallbacks() {
         // register for connectivity change events.
         NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder();
@@ -467,6 +574,12 @@
         NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder();
         networkRequestBuilder.addCapability(getNetworkCapability(mAGpsType));
         networkRequestBuilder.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
+        // During an emergency call, and when we have cached the Active Sub Id, we set the
+        // Network Specifier so that the network request goes to the correct Sub Id
+        if (mNiHandler.getInEmergency() && mActiveSubId >= 0) {
+            if (DEBUG) Log.d(TAG, "Adding Network Specifier: " + Integer.toString(mActiveSubId));
+            networkRequestBuilder.setNetworkSpecifier(Integer.toString(mActiveSubId));
+        }
         NetworkRequest networkRequest = networkRequestBuilder.build();
         mConnMgr.requestNetwork(
                 networkRequest,
@@ -598,6 +711,15 @@
         }
         TelephonyManager phone = (TelephonyManager)
                 mContext.getSystemService(Context.TELEPHONY_SERVICE);
+        // During an emergency call with an active sub id, get the Telephony Manager specific
+        // to the active sub to get the correct value from getServiceState and getNetworkType
+        if (mNiHandler.getInEmergency() && mActiveSubId >= 0) {
+            TelephonyManager subIdTelManager =
+                    phone.createForSubscriptionId(mActiveSubId);
+            if (subIdTelManager != null) {
+                phone = subIdTelManager;
+            }
+        }
         ServiceState serviceState = phone.getServiceState();
         String projection = null;
         String selection = null;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 60c3875..7f805be 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -139,6 +139,7 @@
 import android.app.usage.UsageStatsManagerInternal;
 import android.companion.ICompanionDeviceManager;
 import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.ContentProvider;
@@ -387,10 +388,9 @@
      * still post toasts created with
      * {@link android.widget.Toast#makeText(Context, CharSequence, int)} and its variants while
      * in the background.
-     *
-     * TODO(b/144152069): Add @EnabledAfter(Q) to target R+ after assessing impact on dogfood
      */
     @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
     private static final long CHANGE_BACKGROUND_CUSTOM_TOAST_BLOCK = 128611929L;
 
     private IActivityManager mAm;
@@ -2751,24 +2751,18 @@
         @Override
         public void enqueueTextToast(String pkg, IBinder token, CharSequence text, int duration,
                 int displayId, @Nullable ITransientNotificationCallback callback) {
-            enqueueToast(pkg, token, text, null, duration, displayId, callback, false);
+            enqueueToast(pkg, token, text, null, duration, displayId, callback);
         }
 
         @Override
         public void enqueueToast(String pkg, IBinder token, ITransientNotification callback,
                 int duration, int displayId) {
-            enqueueToast(pkg, token, null, callback, duration, displayId, null, true);
-        }
-
-        @Override
-        public void enqueueTextOrCustomToast(String pkg, IBinder token,
-                ITransientNotification callback, int duration, int displayId, boolean isCustom) {
-            enqueueToast(pkg, token, null, callback, duration, displayId, null, isCustom);
+            enqueueToast(pkg, token, null, callback, duration, displayId, null);
         }
 
         private void enqueueToast(String pkg, IBinder token, @Nullable CharSequence text,
                 @Nullable ITransientNotification callback, int duration, int displayId,
-                @Nullable ITransientNotificationCallback textCallback, boolean isCustom) {
+                @Nullable ITransientNotificationCallback textCallback) {
             if (DBG) {
                 Slog.i(TAG, "enqueueToast pkg=" + pkg + " token=" + token
                         + " duration=" + duration + " displayId=" + displayId);
@@ -2807,11 +2801,15 @@
             }
 
             boolean isAppRenderedToast = (callback != null);
-            if (isAppRenderedToast && isCustom && !isSystemToast
-                    && !isPackageInForegroundForToast(pkg, callingUid)) {
+            if (isAppRenderedToast && !isSystemToast && !isPackageInForegroundForToast(pkg,
+                    callingUid)) {
                 boolean block;
                 long id = Binder.clearCallingIdentity();
                 try {
+                    // CHANGE_BACKGROUND_CUSTOM_TOAST_BLOCK is gated on targetSdk, so block will be
+                    // false for apps with targetSdk < R. For apps with targetSdk R+, text toasts
+                    // are not app-rendered, so isAppRenderedToast == true means it's a custom
+                    // toast.
                     block = mPlatformCompat.isChangeEnabledByPackageName(
                             CHANGE_BACKGROUND_CUSTOM_TOAST_BLOCK, pkg,
                             callingUser.getIdentifier());
@@ -2824,11 +2822,6 @@
                     Binder.restoreCallingIdentity(id);
                 }
                 if (block) {
-                    // TODO(b/144152069): Remove informative toast
-                    mUiHandler.post(() -> Toast.makeText(getContext(),
-                            "Background custom toast blocked for package " + pkg + ".\n"
-                                    + "See g.co/dev/toast.",
-                            Toast.LENGTH_SHORT).show());
                     Slog.w(TAG, "Blocking custom toast from package " + pkg
                             + " due to package not in the foreground");
                     return;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 4e5d6c4..e605eeb 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -5070,15 +5070,16 @@
      * action and a {@code android.intent.category.BROWSABLE} category</li>
      * </ul>
      */
-    int updateFlagsForResolve(int flags, int userId, int callingUid, boolean wantInstantApps) {
+    int updateFlagsForResolve(int flags, int userId, int callingUid, boolean wantInstantApps,
+            boolean matchSystemOnly) {
         return updateFlagsForResolve(flags, userId, callingUid,
-                wantInstantApps, false /*onlyExposedExplicitly*/);
+                wantInstantApps, matchSystemOnly, false /*onlyExposedExplicitly*/);
     }
 
     int updateFlagsForResolve(int flags, int userId, int callingUid,
-            boolean wantInstantApps, boolean onlyExposedExplicitly) {
+            boolean wantInstantApps, boolean onlyExposedExplicitly, boolean matchSystemOnly) {
         // Safe mode means we shouldn't match any third-party components
-        if (mSafeMode) {
+        if (mSafeMode || matchSystemOnly) {
             flags |= PackageManager.MATCH_SYSTEM_ONLY;
         }
         if (getInstantAppPackageName(callingUid) != null) {
@@ -6170,7 +6171,8 @@
 
             if (!mUserManager.exists(userId)) return null;
             final int callingUid = Binder.getCallingUid();
-            flags = updateFlagsForResolve(flags, userId, filterCallingUid, resolveForStart);
+            flags = updateFlagsForResolve(flags, userId, filterCallingUid, resolveForStart,
+                    intent.isImplicitImageCaptureIntent() /*matchSystemOnly*/);
             mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                     false /*requireFullPermission*/, false /*checkShell*/, "resolve intent");
 
@@ -6202,7 +6204,8 @@
         intent = updateIntentForResolve(intent);
         final String resolvedType = intent.resolveTypeIfNeeded(mContext.getContentResolver());
         final int flags = updateFlagsForResolve(
-                0, userId, callingUid, false /*includeInstantApps*/);
+                0, userId, callingUid, false /*includeInstantApps*/,
+                intent.isImplicitImageCaptureIntent() /*matchSystemOnly*/);
         final List<ResolveInfo> query = queryIntentActivitiesInternal(intent, resolvedType, flags,
                 userId);
         synchronized (mLock) {
@@ -6523,7 +6526,8 @@
                 android.provider.Settings.Global.getInt(mContext.getContentResolver(),
                         android.provider.Settings.Global.DEVICE_PROVISIONED, 0) == 1;
         flags = updateFlagsForResolve(
-                flags, userId, callingUid, false /*includeInstantApps*/);
+                flags, userId, callingUid, false /*includeInstantApps*/,
+                intent.isImplicitImageCaptureIntent() /*matchSystemOnly*/);
         intent = updateIntentForResolve(intent);
         // writer
         synchronized (mLock) {
@@ -6735,7 +6739,8 @@
             }
             synchronized (mLock) {
                 int flags = updateFlagsForResolve(0, parent.id, callingUid,
-                        false /*includeInstantApps*/);
+                        false /*includeInstantApps*/,
+                        intent.isImplicitImageCaptureIntent() /*matchSystemOnly*/);
                 CrossProfileDomainInfo xpDomainInfo = getCrossProfileDomainPreferredLpr(
                         intent, resolvedType, flags, sourceUserId, parent.id);
                 return xpDomainInfo != null;
@@ -6821,7 +6826,8 @@
         }
 
         flags = updateFlagsForResolve(flags, userId, filterCallingUid, resolveForStart,
-                comp != null || pkgName != null /*onlyExposedExplicitly*/);
+                comp != null || pkgName != null /*onlyExposedExplicitly*/,
+                intent.isImplicitImageCaptureIntent() /*matchSystemOnly*/);
         if (comp != null) {
             final List<ResolveInfo> list = new ArrayList<>(1);
             final ActivityInfo ai = getActivityInfo(comp, flags, userId);
@@ -7606,7 +7612,8 @@
             String resolvedType, int flags, int userId) {
         if (!mUserManager.exists(userId)) return Collections.emptyList();
         final int callingUid = Binder.getCallingUid();
-        flags = updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/);
+        flags = updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/,
+                intent.isImplicitImageCaptureIntent() /*matchSystemOnly*/);
         mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 false /*requireFullPermission*/, false /*checkShell*/,
                 "query intent activity options");
@@ -7792,7 +7799,8 @@
                 false /*requireFullPermission*/, false /*checkShell*/,
                 "query intent receivers");
         final String instantAppPkgName = getInstantAppPackageName(callingUid);
-        flags = updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/);
+        flags = updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/,
+                intent.isImplicitImageCaptureIntent() /*matchSystemOnly*/);
         ComponentName comp = intent.getComponent();
         if (comp == null) {
             if (intent.getSelector() != null) {
@@ -7882,7 +7890,8 @@
     private ResolveInfo resolveServiceInternal(Intent intent, String resolvedType, int flags,
             int userId, int callingUid) {
         if (!mUserManager.exists(userId)) return null;
-        flags = updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/);
+        flags = updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/,
+                false /* matchSystemOnly */);
         List<ResolveInfo> query = queryIntentServicesInternal(
                 intent, resolvedType, flags, userId, callingUid, false /*includeInstantApps*/);
         if (query != null) {
@@ -7913,7 +7922,8 @@
                 false /*checkShell*/,
                 "query intent receivers");
         final String instantAppPkgName = getInstantAppPackageName(callingUid);
-        flags = updateFlagsForResolve(flags, userId, callingUid, includeInstantApps);
+        flags = updateFlagsForResolve(flags, userId, callingUid, includeInstantApps,
+                false /* matchSystemOnly */);
         ComponentName comp = intent.getComponent();
         if (comp == null) {
             if (intent.getSelector() != null) {
@@ -8050,7 +8060,8 @@
         if (!mUserManager.exists(userId)) return Collections.emptyList();
         final int callingUid = Binder.getCallingUid();
         final String instantAppPkgName = getInstantAppPackageName(callingUid);
-        flags = updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/);
+        flags = updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/,
+                false /* matchSystemOnly */);
         ComponentName comp = intent.getComponent();
         if (comp == null) {
             if (intent.getSelector() != null) {
diff --git a/services/core/java/com/android/server/power/TEST_MAPPING b/services/core/java/com/android/server/power/TEST_MAPPING
index acf3f79..74958b6 100644
--- a/services/core/java/com/android/server/power/TEST_MAPPING
+++ b/services/core/java/com/android/server/power/TEST_MAPPING
@@ -3,6 +3,7 @@
     {
       "name": "CtsBatterySavingTestCases",
       "options": [
+        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
         {"exclude-annotation": "androidx.test.filters.LargeTest"},
         {"exclude-annotation": "androidx.test.filters.FlakyTest"}
       ]
@@ -11,6 +12,7 @@
       "name": "FrameworksMockingServicesTests",
       "options": [
         {"include-filter": "com.android.server.power"},
+        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
         {"exclude-annotation": "androidx.test.filters.FlakyTest"}
       ]
     },
@@ -18,6 +20,7 @@
       "name": "FrameworksServicesTests",
       "options": [
         {"include-filter": "com.android.server.power"},
+        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
         {"exclude-annotation": "androidx.test.filters.FlakyTest"},
         {
           "exclude-filter": "com.android.server.power.PowerManagerServiceTest#testWakefulnessAwake_ShouldWakeUpWhenPluggedIn"
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index fb22481..c6f375e 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -302,9 +302,6 @@
 
     private static final int TRANSLUCENT_TIMEOUT_MSG = FIRST_ACTIVITY_STACK_MSG + 1;
 
-    // TODO(task-hierarchy): remove when tiles can be actual parents
-    TaskTile mTile = null;
-
     private final Handler mHandler;
 
     private class ActivityStackHandler extends Handler {
@@ -550,10 +547,10 @@
     }
 
     ActivityStack(ActivityTaskManagerService atmService, int id, int activityType,
-            ActivityInfo info, Intent intent) {
+            ActivityInfo info, Intent intent, boolean createdByOrganizer) {
         this(atmService, id, info, intent, null /*voiceSession*/, null /*voiceInteractor*/,
                 null /*taskDescription*/, null /*stack*/);
-
+        mCreatedByOrganizer = createdByOrganizer;
         setActivityType(activityType);
     }
 
@@ -600,20 +597,11 @@
     }
 
     @Override
-    public void resolveTileOverrideConfiguration(Configuration newParentConfig) {
-        super.resolveTileOverrideConfiguration(newParentConfig);
-        if (mTile != null) {
-            // If this is a virtual child of a tile, simulate the parent-child relationship
-            mTile.updateResolvedConfig(getResolvedOverrideConfiguration());
-        }
-    }
-
-    @Override
     public void onConfigurationChanged(Configuration newParentConfig) {
         // Calling Task#onConfigurationChanged() for leaf task since the ops in this method are
         // particularly for ActivityStack, like preventing bounds changes when inheriting certain
         // windowing mode.
-        if (!isRootTask() || this instanceof TaskTile) {
+        if (!isRootTask()) {
             super.onConfigurationChanged(newParentConfig);
             return;
         }
@@ -688,6 +676,9 @@
 
     @Override
     public void setWindowingMode(int windowingMode) {
+        // Reset the cached result of toString()
+        stringName = null;
+
         // Calling Task#setWindowingMode() for leaf task since this is the a specialization of
         // {@link #setWindowingMode(int)} for ActivityStack.
         if (!isRootTask()) {
@@ -741,7 +732,6 @@
         final int currentOverrideMode = getRequestedOverrideWindowingMode();
         final DisplayContent display = getDisplay();
         final Task topTask = getTopMostTask();
-        final ActivityStack splitScreenStack = display.getRootSplitScreenPrimaryTask();
         int windowingMode = preferredWindowingMode;
         if (preferredWindowingMode == WINDOWING_MODE_UNDEFINED
                 && isTransientWindowingMode(currentMode)) {
@@ -755,14 +745,14 @@
             windowingMode = display.validateWindowingMode(windowingMode,
                     null /* ActivityRecord */, topTask, getActivityType());
         }
-        if (splitScreenStack == this
+        if (display.getRootSplitScreenPrimaryTask() == this
                 && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
             // Resolution to split-screen secondary for the primary split-screen stack means
             // we want to leave split-screen mode.
             windowingMode = mRestoreOverrideWindowingMode;
         }
 
-        final boolean alreadyInSplitScreenMode = display.hasSplitScreenPrimaryTask();
+        final boolean alreadyInSplitScreenMode = display.isSplitScreenModeActivated();
 
         // Don't send non-resizeable notifications if the windowing mode changed was a side effect
         // of us entering split-screen mode.
@@ -830,7 +820,7 @@
                 return;
             }
 
-            if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY && splitScreenStack != null) {
+            if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY && alreadyInSplitScreenMode) {
                 // We already have a split-screen stack in this display, so just move the tasks over.
                 // TODO: Figure-out how to do all the stuff in
                 // AMS.setTaskWindowingModeSplitScreenPrimary
@@ -1062,7 +1052,7 @@
         final DisplayContent display = getDisplay();
 
         if (inSplitScreenSecondaryWindowingMode()) {
-            // If the stack is in split-screen seconardy mode, we need to make sure we move the
+            // If the stack is in split-screen secondary mode, we need to make sure we move the
             // primary split-screen stack forward in the case it is currently behind a fullscreen
             // stack so both halves of the split-screen appear on-top and the fullscreen stack isn't
             // cutting between them.
@@ -1084,12 +1074,13 @@
             display.moveHomeStackToFront(reason + " returnToHome");
         }
 
-        final boolean movingTask = task != null;
-        display.positionStackAtTop(this, !movingTask /* includingParents */, reason);
-        if (movingTask) {
-            // This also moves the entire hierarchy branch to top, including parents
-            positionChildAtTop(task);
+        if (isRootTask()) {
+            display.positionStackAtTop(this, false /* includingParents */, reason);
         }
+        if (task == null) {
+            task = this;
+        }
+        task.getParent().positionChildAt(POSITION_TOP, task, true /* includingParents */);
     }
 
     /**
@@ -1115,12 +1106,6 @@
         }
     }
 
-    @Override
-    boolean isFocusable() {
-        // Special check for tile which isn't really in the hierarchy
-        return mTile != null ? mTile.isFocusable() : super.isFocusable();
-    }
-
     boolean isTopActivityFocusable() {
         final ActivityRecord r = topRunningActivity();
         return r != null ? r.isFocusable()
@@ -3541,6 +3526,10 @@
 
     @Override
     void onChildPositionChanged(WindowContainer child) {
+        if (isOrganized()) {
+            mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged(this, false /* force */);
+        }
+
         if (!mChildren.contains(child)) {
             return;
         }
@@ -3571,11 +3560,6 @@
         if (oldDisplay != null && oldDisplay.isRemoving()) {
             postReparent();
         }
-        if (mTile != null && getSurfaceControl() != null) {
-            // by now, the TaskStack should already have been reparented, so we can reparent its
-            // surface here
-            reparentSurfaceControl(getPendingTransaction(), mTile.getSurfaceControl());
-        }
     }
 
     void reparent(DisplayContent newParent, boolean onTop) {
@@ -3613,16 +3597,7 @@
 
     @Override
     void getRelativeDisplayedPosition(Point outPos) {
-        // check for tile which is "virtually" a parent.
-        if (mTile != null) {
-            final Rect dispBounds = getDisplayedBounds();
-            outPos.set(dispBounds.left, dispBounds.top);
-            final Rect parentBounds = mTile.getBounds();
-            outPos.offset(-parentBounds.left, -parentBounds.top);
-        } else {
-            super.getRelativeDisplayedPosition(outPos);
-        }
-
+        super.getRelativeDisplayedPosition(outPos);
         final int outset = getStackOutset();
         outPos.x -= outset;
         outPos.y -= outset;
@@ -3632,16 +3607,6 @@
         if (mSurfaceControl == null) {
             return;
         }
-        if (mTile != null) {
-            // Tile controls crop, so the app needs to be able to draw its background outside of
-            // the stack bounds for when the tile crop gets bigger than the stack.
-            if (mLastSurfaceSize.equals(0, 0)) {
-                return;
-            }
-            transaction.setWindowCrop(mSurfaceControl, null);
-            mLastSurfaceSize.set(0, 0);
-            return;
-        }
 
         final Rect stackBounds = getDisplayedBounds();
         int width = stackBounds.width();
@@ -3665,9 +3630,6 @@
 
     @Override
     void onDisplayChanged(DisplayContent dc) {
-        if (mTile != null && dc != mTile.getDisplay()) {
-            mTile.removeChild(this);
-        }
         super.onDisplayChanged(dc);
         if (isRootTask()) {
             updateSurfaceBounds();
@@ -3810,49 +3772,6 @@
         return shouldSleepActivities() || mAtmService.mShuttingDown;
     }
 
-    TaskTile getTile() {
-        return mTile;
-    }
-
-    /**
-     * Don't call this directly. instead use {@link TaskTile#addChild} or
-     * {@link TaskTile#removeChild}.
-     */
-    void setTile(TaskTile tile) {
-        TaskTile origTile = mTile;
-        mTile = tile;
-        final ConfigurationContainer parent = getParent();
-        if (parent != null) {
-            onConfigurationChanged(parent.getConfiguration());
-        }
-
-        // Reparent to tile surface or back to original parent
-        if (getSurfaceControl() == null) {
-            return;
-        }
-        if (mTile != null) {
-            // don't use reparentSurfaceControl because we need to bypass taskorg check
-            mSurfaceAnimator.reparent(getPendingTransaction(), mTile.getSurfaceControl());
-        } else if (mTile == null && origTile != null) {
-            mSurfaceAnimator.reparent(getPendingTransaction(), getParentSurfaceControl());
-        }
-    }
-
-    @Override
-    public SurfaceControl getParentSurfaceControl() {
-        // Tile is a "virtual" parent, so we need to intercept the parent surface here
-        return mTile != null ? mTile.getSurfaceControl() : super.getParentSurfaceControl();
-    }
-
-    @Override
-    void removeImmediately() {
-        // TODO(task-hierarchy): remove this override when tiles are in hierarchy
-        if (mTile != null) {
-            mTile.removeChild(this);
-        }
-        super.removeImmediately();
-    }
-
     @Override
     public void dumpDebug(ProtoOutputStream proto, long fieldId,
             @WindowTraceLogLevel int logLevel) {
@@ -3897,7 +3816,7 @@
             proto.write(SURFACE_HEIGHT, mSurfaceControl.getHeight());
         }
 
-        proto.write(CREATED_BY_ORGANIZER, this instanceof TaskTile);
+        proto.write(CREATED_BY_ORGANIZER, mCreatedByOrganizer);
 
         proto.end(token);
     }
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index 57f357d..4652f49 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -423,6 +423,10 @@
 
             final ActivityStack toStack = mToDisplay.getOrCreateStack(
                     null, mTmpOptions, task, task.getActivityType(), mOnTop);
+            if (task == toStack) {
+                // The task was reused as the root task.
+                return;
+            }
 
             if (mOnTop) {
                 final boolean isTopTask = task == mTopTask;
@@ -1704,7 +1708,7 @@
                 mRootWindowContainer.getLaunchStack(null, aOptions, task, onTop);
         final WindowContainer parent = task.getParent();
 
-        if (parent == stack) {
+        if (parent == stack || task == stack) {
             // Nothing else to do since it is already restored in the right stack.
             return true;
         }
@@ -2237,7 +2241,7 @@
         final boolean isSecondaryDisplayPreferred =
                 (preferredDisplayId != DEFAULT_DISPLAY && preferredDisplayId != INVALID_DISPLAY);
         final boolean inSplitScreenMode = actualStack != null
-                && actualStack.getDisplay().hasSplitScreenPrimaryTask();
+                && actualStack.getDisplay().isSplitScreenModeActivated();
         if (((!inSplitScreenMode && preferredWindowingMode != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)
                 && !isSecondaryDisplayPreferred) || !task.isActivityTypeStandardOrUndefined()) {
             return;
@@ -2284,16 +2288,14 @@
         if (!task.supportsSplitScreenWindowingMode() || forceNonResizable) {
             // Dismiss docked stack. If task appeared to be in docked stack but is not resizable -
             // we need to move it to top of fullscreen stack, otherwise it will be covered.
-
-            final ActivityStack dockedStack =
-                    task.getStack().getDisplay().getRootSplitScreenPrimaryTask();
-            if (dockedStack != null) {
+            final DisplayContent display = task.getStack().getDisplay();
+            if (display.isSplitScreenModeActivated()) {
                 // Display a warning toast that we tried to put an app that doesn't support
                 // split-screen in split-screen.
                 mService.getTaskChangeNotificationController()
                         .notifyActivityDismissingDockedStack();
-                dockedStack.getDisplay().onSplitScreenModeDismissed();
-                dockedStack.getDisplay().ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS,
+                display.onSplitScreenModeDismissed();
+                display.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS,
                         true /* notifyClients */);
             }
             return;
@@ -2602,7 +2604,7 @@
                         "startActivityFromRecents: Task " + taskId + " not found.");
             } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
                     && task.getWindowingMode() != windowingMode) {
-                mService.moveTaskToSplitScreenPrimaryTile(task, true /* toTop */);
+                mService.moveTaskToSplitScreenPrimaryTask(task, true /* toTop */);
             }
 
             if (windowingMode != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 14ca7cb..da1c045 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -30,7 +30,6 @@
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.app.WaitResult.LAUNCH_STATE_COLD;
 import static android.app.WaitResult.LAUNCH_STATE_HOT;
-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.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -78,6 +77,7 @@
 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.REPARENT_MOVE_STACK_TO_FRONT;
+import static com.android.server.wm.WindowContainer.POSITION_TOP;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -1566,7 +1566,7 @@
         }
 
         if (!mAvoidMoveToFront && mDoResume) {
-            mTargetStack.moveToFront("reuseOrNewTask");
+            mTargetStack.getStack().moveToFront("reuseOrNewTask", targetTask);
             if (mOptions != null) {
                 if (mPreferredWindowingMode != WINDOWING_MODE_UNDEFINED) {
                     mTargetStack.setWindowingMode(mPreferredWindowingMode);
@@ -2364,6 +2364,7 @@
     private void setTargetStackIfNeeded(ActivityRecord intentActivity) {
         mTargetStack = intentActivity.getRootTask();
         mTargetStack.mLastPausedActivity = null;
+        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
         // the same behavior as if a new instance was being started, which means not bringing it
@@ -2374,7 +2375,7 @@
             final ActivityRecord curTop = (focusStack == null)
                     ? null : focusStack.topRunningNonDelayedActivityLocked(mNotTop);
             final Task topTask = curTop != null ? curTop.getTask() : null;
-            differentTopTask = topTask != intentActivity.getTask()
+            differentTopTask = topTask != intentTask
                     || (focusStack != null && topTask != focusStack.getTopMostTask());
         } else {
             // The existing task should always be different from those in other displays.
@@ -2391,7 +2392,6 @@
                     intentActivity.setTaskToAffiliateWith(mSourceRecord.getTask());
                 }
 
-                final Task intentTask = intentActivity.getTask();
                 final ActivityStack launchStack =
                         getLaunchStack(mStartActivity, mLaunchFlags, intentTask, mOptions);
                 if (launchStack == null || launchStack == mTargetStack) {
@@ -2400,6 +2400,14 @@
                     // new intent has delivered.
                     final boolean isSplitScreenTopStack = mTargetStack.isTopSplitScreenStack();
 
+                    // TODO(b/151572268): Figure out a better way to move tasks in above 2-levels
+                    //  tasks hierarchies.
+                    if (mTargetStack != intentTask
+                            && mTargetStack != intentTask.getParent().asTask()) {
+                        intentTask.getParent().positionChildAt(POSITION_TOP, intentTask,
+                                false /* includingParents */);
+                        intentTask = intentTask.getParent().asTask();
+                    }
                     // We only want to move to the front, if we aren't going to launch on a
                     // different stack. If we launch on a different stack, we will put the
                     // task on top there.
@@ -2420,8 +2428,8 @@
         // Need to update mTargetStack because if task was moved out of it, the original stack may
         // be destroyed.
         mTargetStack = intentActivity.getRootTask();
-        mSupervisor.handleNonResizableTaskIfNeeded(intentActivity.getTask(),
-                WINDOWING_MODE_UNDEFINED, DEFAULT_DISPLAY, mTargetStack);
+        mSupervisor.handleNonResizableTaskIfNeeded(intentTask, WINDOWING_MODE_UNDEFINED,
+                DEFAULT_DISPLAY, mTargetStack);
     }
 
     private void resumeTargetStackIfNeeded() {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 214a676..a5b0026 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -31,12 +31,12 @@
 import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.app.ActivityTaskManager.RESIZE_MODE_PRESERVE_WINDOW;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
 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_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.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
@@ -122,6 +122,7 @@
 import static com.android.server.wm.Task.LOCK_TASK_AUTH_DONT_LOCK;
 import static com.android.server.wm.Task.REPARENT_KEEP_STACK_AT_FRONT;
 import static com.android.server.wm.Task.REPARENT_LEAVE_STACK_IN_PLACE;
+import static com.android.server.wm.WindowContainer.POSITION_TOP;
 
 import android.Manifest;
 import android.annotation.IntDef;
@@ -144,7 +145,6 @@
 import android.app.IAssistDataReceiver;
 import android.app.INotificationManager;
 import android.app.IRequestFinishCallback;
-import android.window.ITaskOrganizerController;
 import android.app.ITaskStackListener;
 import android.app.Notification;
 import android.app.NotificationManager;
@@ -230,9 +230,9 @@
 import android.view.IRecentsAnimationRunner;
 import android.view.RemoteAnimationAdapter;
 import android.view.RemoteAnimationDefinition;
+import android.view.WindowManager;
 import android.window.IWindowOrganizerController;
 import android.window.WindowContainerTransaction;
-import android.view.WindowManager;
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
@@ -1274,9 +1274,13 @@
         a.colorMode = ActivityInfo.COLOR_MODE_DEFAULT;
         a.flags |= ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS;
 
+        final ActivityOptions options = ActivityOptions.makeBasic();
+        options.setLaunchActivityType(ACTIVITY_TYPE_DREAM);
+
         try {
             getActivityStartController().obtainStarter(intent, "dream")
                     .setActivityInfo(a)
+                    .setActivityOptions(options.toBundle())
                     .setIsDream(true)
                     .execute();
             return true;
@@ -2349,16 +2353,18 @@
                 }
 
                 final ActivityStack stack = task.getStack();
-                // Convert some windowing-mode changes into root-task reparents for split-screen.
-                if (stack.getTile() != null) {
-                    stack.getDisplay().onSplitScreenModeDismissed();
-                }
                 if (toTop) {
                     stack.moveToFront("setTaskWindowingMode", task);
                 }
-                stack.setWindowingMode(windowingMode);
-                stack.getDisplay().ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS,
-                        true /* notifyClients */);
+                // Convert some windowing-mode changes into root-task reparents for split-screen.
+                if (stack.inSplitScreenWindowingMode()) {
+                    stack.getDisplay().onSplitScreenModeDismissed();
+
+                } else {
+                    stack.setWindowingMode(windowingMode);
+                    stack.getDisplay().ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS,
+                            true /* notifyClients */);
+                }
                 return true;
             } finally {
                 Binder.restoreCallingIdentity(ident);
@@ -2755,24 +2761,22 @@
         }
 
         final int prevMode = task.getWindowingMode();
-        moveTaskToSplitScreenPrimaryTile(task, toTop);
+        moveTaskToSplitScreenPrimaryTask(task, toTop);
         return prevMode != task.getWindowingMode();
     }
 
-    void moveTaskToSplitScreenPrimaryTile(Task task, boolean toTop) {
-        ActivityStack stack = task.getStack();
-        TaskTile tile = null;
-        for (int i = stack.getDisplay().getStackCount() - 1; i >= 0; --i) {
-            tile = stack.getDisplay().getStackAt(i).asTile();
-            if (tile != null && tile.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
-                break;
-            }
+    void moveTaskToSplitScreenPrimaryTask(Task task, boolean toTop) {
+        final DisplayContent display = task.getDisplayContent();
+        final ActivityStack primarySplitTask = display.getRootSplitScreenPrimaryTask();
+        if (primarySplitTask == null) {
+            throw new IllegalStateException("Can't enter split without associated organized task");
         }
-        if (tile == null) {
-            throw new IllegalStateException("Can't enter split without associated tile");
+
+        if (toTop) {
+            display.positionStackAt(POSITION_TOP, primarySplitTask, false /* includingParents */);
         }
         WindowContainerTransaction wct = new WindowContainerTransaction();
-        wct.reparent(stack.mRemoteToken, tile.mRemoteToken, toTop);
+        wct.reparent(task.getStack().mRemoteToken, primarySplitTask.mRemoteToken, toTop);
         mWindowOrganizerController.applyTransaction(wct);
     }
 
@@ -3239,7 +3243,8 @@
 
                 final ActivityStack stack = r.getRootTask();
                 final Task task = stack.getDisplay().createStack(stack.getWindowingMode(),
-                        stack.getActivityType(), !ON_TOP, ainfo, intent);
+                        stack.getActivityType(), !ON_TOP, ainfo, intent,
+                        false /* createdByOrganizer */);
 
                 if (!mRecentTasks.addToBottom(task)) {
                     // The app has too many tasks already and we can't add any more
@@ -4278,19 +4283,9 @@
         try {
             synchronized (mGlobalLock) {
                 final DisplayContent dc = mRootWindowContainer.getDefaultDisplay();
-                TaskTile primary = null;
-                TaskTile secondary = null;
-                for (int i = dc.getStackCount() - 1; i >= 0; --i) {
-                    final TaskTile t = dc.getStackAt(i).asTile();
-                    if (t == null) {
-                        continue;
-                    }
-                    if (t.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
-                        primary = t;
-                    } else if (t.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
-                        secondary = t;
-                    }
-                }
+                final Task primary = dc.getRootSplitScreenPrimaryTask();
+                final Task secondary = dc.getTask(t -> t.mCreatedByOrganizer && t.isRootTask()
+                        && t.inSplitScreenSecondaryWindowingMode());
                 if (primary == null || secondary == null) {
                     return;
                 }
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index 33dd9cf..1036af6 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -17,6 +17,7 @@
 package com.android.server.wm;
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
@@ -467,6 +468,10 @@
         return getActivityType() == ACTIVITY_TYPE_ASSISTANT;
     }
 
+    public boolean isActivityTypeDream() {
+        return getActivityType() == ACTIVITY_TYPE_DREAM;
+    }
+
     public boolean isActivityTypeStandard() {
         return getActivityType() == ACTIVITY_TYPE_STANDARD;
     }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 7df731b..ede4569 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -200,7 +200,6 @@
 import android.view.Gravity;
 import android.view.IDisplayWindowInsetsController;
 import android.view.ISystemGestureExclusionListener;
-import android.window.ITaskOrganizer;
 import android.view.IWindow;
 import android.view.InputChannel;
 import android.view.InputDevice;
@@ -217,6 +216,7 @@
 import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.view.WindowManagerPolicyConstants.PointerEventListener;
+import android.window.WindowContainerTransaction;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
@@ -456,6 +456,8 @@
 
     private final Configuration mTmpConfiguration = new Configuration();
 
+    private ArrayList<Task> mTmpTasks = new ArrayList<>();
+
     /** Remove this display when animation on it has completed. */
     private boolean mDeferredRemoval;
 
@@ -654,8 +656,8 @@
     private final RootWindowContainer.FindTaskResult
             mTmpFindTaskResult = new RootWindowContainer.FindTaskResult();
 
-    // When non-null, new stacks get put into this tile.
-    TaskTile mLaunchTile = null;
+    // When non-null, new tasks get put into this root task.
+    Task mLaunchRootTask = null;
 
     private final Consumer<WindowState> mUpdateWindowsForAnimator = w -> {
         WindowStateAnimator winAnimator = w.mWinAnimator;
@@ -2128,12 +2130,13 @@
     }
 
     /** @return The primary split-screen task, and {@code null} otherwise. */
-    ActivityStack getRootSplitScreenPrimaryTask() {
+    @Nullable ActivityStack getRootSplitScreenPrimaryTask() {
         return mTaskContainers.getRootSplitScreenPrimaryTask();
     }
 
-    boolean hasSplitScreenPrimaryTask() {
-        return getRootSplitScreenPrimaryTask() != null;
+    boolean isSplitScreenModeActivated() {
+        Task task = getRootSplitScreenPrimaryTask();
+        return task != null && task.hasChild();
     }
 
     ActivityStack getRootPinnedTask() {
@@ -2600,7 +2603,7 @@
         }
         amendWindowTapExcludeRegion(mTouchExcludeRegion);
         // TODO(multi-display): Support docked stacks on secondary displays.
-        if (mDisplayId == DEFAULT_DISPLAY && getRootSplitScreenPrimaryTask() != null) {
+        if (mDisplayId == DEFAULT_DISPLAY && isSplitScreenModeActivated()) {
             mDividerControllerLocked.getTouchRegion(mTmpRect);
             mTmpRegion.set(mTmpRect);
             mTouchExcludeRegion.op(mTmpRegion, Op.UNION);
@@ -2623,8 +2626,7 @@
         // If the task is home stack and it is resizable and visible (top of its root task), we want
         // to exclude the docked stack from touch so we need the entire screen area and not just a
         // small portion which the home stack currently is resized to.
-        if (task.isActivityTypeHome() && task.isVisible() && task.getStack().getTile() != null
-                && task.isResizeable()) {
+        if (task.isActivityTypeHome() && task.isVisible() && task.isResizeable()) {
             mDisplayContent.getBounds(mTmpRect);
         } else {
             task.getDimBounds(mTmpRect);
@@ -4328,15 +4330,8 @@
 
         @VisibleForTesting
         ActivityStack getTopStack() {
-            // TODO(task-hierarchy): Just grab index -1 once tiles are in hierarchy.
-            for (int i = mTaskContainers.getChildCount() - 1; i >= 0; --i) {
-                final ActivityStack child = mTaskContainers.getChildAt(i);
-                if (child instanceof TaskTile) {
-                    continue;
-                }
-                return child;
-            }
-            return null;
+            final int count = mTaskContainers.getChildCount();
+            return count > 0 ? mTaskContainers.getChildAt(count - 1) : null;
         }
 
         int getIndexOf(ActivityStack stack) {
@@ -4375,10 +4370,6 @@
         }
 
         private void addStackReferenceIfNeeded(ActivityStack stack) {
-            // TODO(task-hierarchy): Remove when tiles are in hierarchy.
-            if (stack instanceof TaskTile) {
-                return;
-            }
             if (stack.isActivityTypeHome()) {
                 if (mRootHomeTask != null) {
                     if (!stack.isDescendantOf(mRootHomeTask)) {
@@ -4390,27 +4381,26 @@
                     mRootHomeTask = stack;
                 }
             }
+
+            if (!stack.isRootTask()) {
+                return;
+            }
             final int windowingMode = stack.getWindowingMode();
             if (windowingMode == WINDOWING_MODE_PINNED) {
                 if (mRootPinnedTask != null) {
-                    if (!stack.isDescendantOf(mRootPinnedTask)) {
-                        throw new IllegalArgumentException(
-                                "addStackReferenceIfNeeded: pinned stack=" + mRootPinnedTask
-                                        + " already exist on display=" + this + " stack=" + stack);
-                    }
-                } else {
-                    mRootPinnedTask = stack;
+                    throw new IllegalArgumentException(
+                            "addStackReferenceIfNeeded: pinned stack=" + mRootPinnedTask
+                                    + " already exist on display=" + this + " stack=" + stack);
                 }
+                mRootPinnedTask = stack;
             } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
                 if (mRootSplitScreenPrimaryTask != null) {
-                    if (!stack.isDescendantOf(mRootSplitScreenPrimaryTask)) {
-                        throw new IllegalArgumentException("addStackReferenceIfNeeded:"
-                                + " split-screen-primary" + " stack=" + mRootSplitScreenPrimaryTask
-                                + " already exist on display=" + this + " stack=" + stack);
-                    }
-                } else {
-                    mRootSplitScreenPrimaryTask = stack;
+                    throw new IllegalArgumentException(
+                            "addStackReferenceIfNeeded: split screen primary stack="
+                                    + mRootSplitScreenPrimaryTask
+                                    + " already exist on display=" + this + " stack=" + stack);
                 }
+                mRootSplitScreenPrimaryTask = stack;
             }
         }
 
@@ -4495,6 +4485,10 @@
          */
         private int findPositionForStack(int requestedPosition, ActivityStack stack,
                 boolean adding) {
+            if (stack.isActivityTypeDream()) {
+                return POSITION_TOP;
+            }
+
             if (stack.inPinnedWindowingMode()) {
                 return POSITION_TOP;
             }
@@ -4645,10 +4639,9 @@
                 // Apps and their containers are not allowed to specify an orientation while using
                 // root tasks...except for the home stack if it is not resizable and currently
                 // visible (top of) its root task.
-                if (mRootHomeTask != null && mRootHomeTask.isVisible()
-                        && mRootHomeTask.getTile() != null) {
+                if (mRootHomeTask != null && mRootHomeTask.isVisible()) {
                     final Task topMost = mRootHomeTask.getTopMostTask();
-                    final boolean resizable = topMost == null && topMost.isResizeable();
+                    final boolean resizable = topMost != null && topMost.isResizeable();
                     if (!(resizable && mRootHomeTask.matchParentBounds())) {
                         final int orientation = mRootHomeTask.getOrientation();
                         if (orientation != SCREEN_ORIENTATION_UNSET) {
@@ -4792,17 +4785,6 @@
                 mSplitScreenDividerAnchor = null;
             }
         }
-
-        @Override
-        void onChildPositionChanged(WindowContainer child) {
-            // TODO(task-hierarchy): Move functionality to TaskTile when it's a proper parent.
-            TaskTile tile = ((ActivityStack) child).getTile();
-            if (tile == null) {
-                return;
-            }
-            mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged(
-                    tile, false /* force */);
-        }
     }
 
     private class WindowContainers extends DisplayChildWindowContainer<WindowContainer> {
@@ -4983,7 +4965,7 @@
         private boolean skipImeWindowsDuringTraversal(DisplayContent dc) {
             // We skip IME windows so they're processed just above their target, except
             // in split-screen mode where we process the IME containers above the docked divider.
-            return dc.mInputMethodTarget != null && !dc.hasSplitScreenPrimaryTask();
+            return dc.mInputMethodTarget != null && !dc.isSplitScreenModeActivated();
         }
 
         /** Like {@link #forAllWindows}, but ignores {@link #skipImeWindowsDuringTraversal} */
@@ -5657,6 +5639,14 @@
         mAtmService.updateSleepIfNeededLocked();
     }
 
+    void addStackReferenceIfNeeded(ActivityStack stack) {
+        mTaskContainers.addStackReferenceIfNeeded(stack);
+    }
+
+    void removeStackReferenceIfNeeded(ActivityStack stack) {
+        mTaskContainers.removeStackReferenceIfNeeded(stack);
+    }
+
     void onStackRemoved(ActivityStack stack) {
         if (ActivityTaskManagerDebugConfig.DEBUG_STACK) {
             Slog.v(TAG_STACK, "removeStack: detaching " + stack + " from displayId=" + mDisplayId);
@@ -5703,6 +5693,14 @@
                     "positionStackAt: Can only have one task on display=" + this);
         }
 
+        final boolean movingToTop = wasContained && position >= getStackCount() - 1;
+        // Reset mPreferredTopFocusableStack before positioning to top or {@link
+        // ActivityStackSupervisor#updateTopResumedActivityIfNeeded()} won't update the top
+        // resumed activity.
+        if (movingToTop && stack.isFocusable()) {
+            mPreferredTopFocusableStack = null;
+        }
+
         // Since positionChildAt() is called during the creation process of pinned stacks,
         // ActivityStack#getStack() can be null.
         positionStackAt(position, stack, includingParents);
@@ -5712,7 +5710,7 @@
         // we are looking for top focusable stack. The condition {@code wasContained} restricts the
         // preferred stack is set only when moving an existing stack to top instead of adding a new
         // stack that may be too early (e.g. in the middle of launching or reparenting).
-        if (wasContained && position >= getStackCount() - 1 && stack.isFocusableAndVisible()) {
+        if (movingToTop && stack.isFocusableAndVisible()) {
             mPreferredTopFocusableStack = stack;
         } else if (mPreferredTopFocusableStack == stack) {
             mPreferredTopFocusableStack = null;
@@ -5755,18 +5753,54 @@
     /**
      * Returns an existing stack compatible with the windowing mode and activity type or creates one
      * if a compatible stack doesn't exist.
+     * @see #getOrCreateStack(int, int, boolean, Intent, Task, boolean)
+     */
+    ActivityStack getOrCreateStack(int windowingMode, int activityType, boolean onTop) {
+        return getOrCreateStack(windowingMode, activityType, onTop, null /* intent */,
+                null /* candidateTask */, false /* createdByOrganizer */);
+    }
+
+    /**
+     * When two level tasks are required for given windowing mode and activity type, returns an
+     * existing compatible root task or creates a new one.
+     * For one level task, the candidate task would be reused to also be the root task or create
+     * a new root task if no candidate task.
      * @see #getStack(int, int)
      * @see #createStack(int, int, boolean)
      */
-    ActivityStack getOrCreateStack(int windowingMode, int activityType,
-            boolean onTop) {
+    ActivityStack getOrCreateStack(int windowingMode, int activityType, boolean onTop,
+            Intent intent, Task candidateTask, boolean createdByOrganizer) {
         if (!alwaysCreateStack(windowingMode, activityType)) {
             ActivityStack stack = getStack(windowingMode, activityType);
             if (stack != null) {
                 return stack;
             }
+        } else if (candidateTask != null) {
+            final ActivityStack stack = (ActivityStack) candidateTask;
+            final int position = onTop ? POSITION_TOP : POSITION_BOTTOM;
+            if (isSplitScreenModeActivated()) {
+                final Task splitRootSecondary = getTask(t -> t.mCreatedByOrganizer && t.isRootTask()
+                        && t.inSplitScreenSecondaryWindowingMode());
+                if (stack.getParent() == null) {
+                    splitRootSecondary.addChild(stack, position);
+                } else if (stack.getParent() != splitRootSecondary) {
+                    stack.reparent(splitRootSecondary, position);
+                }
+            } else if (stack.getDisplay() != this || !stack.isRootTask()) {
+                if (stack.getParent() == null) {
+                    addStack(stack, position);
+                } else {
+                    stack.reparent(this, onTop);
+                }
+            }
+            // Update windowing mode if necessary, e.g. moving a pinned task to fullscreen.
+            if (candidateTask.getWindowingMode() != windowingMode) {
+                candidateTask.setWindowingMode(windowingMode);
+            }
+            return stack;
         }
-        return createStack(windowingMode, activityType, onTop);
+        return createStack(windowingMode, activityType, onTop, null /*info*/, intent,
+                createdByOrganizer);
     }
 
     /**
@@ -5784,7 +5818,8 @@
         // UNDEFINED windowing mode is a valid result and means that the new stack will inherit
         // it's display's windowing mode.
         windowingMode = validateWindowingMode(windowingMode, r, candidateTask, activityType);
-        return getOrCreateStack(windowingMode, activityType, onTop);
+        return getOrCreateStack(windowingMode, activityType, onTop, null /* intent */,
+                candidateTask, false /* createdByOrganizer */);
     }
 
     @VisibleForTesting
@@ -5793,7 +5828,8 @@
     }
 
     ActivityStack createStack(int windowingMode, int activityType, boolean onTop) {
-        return createStack(windowingMode, activityType, onTop, null /*info*/, null /*intent*/);
+        return createStack(windowingMode, activityType, onTop, null /* info */, null /* intent */,
+                false /* createdByOrganizer */);
     }
 
     /**
@@ -5805,25 +5841,29 @@
      *                     {@link WindowConfiguration#ACTIVITY_TYPE_UNDEFINED} then the stack will
      *                     be created in {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD}.
      * @param onTop If true the stack will be created at the top of the display, else at the bottom.
+     * @param info The started activity info.
+     * @param intent The intent that started this task.
+     * @param createdByOrganizer @{code true} if this is created by task organizer, @{code false}
+     *                          otherwise.
      * @return The newly created stack.
      */
     ActivityStack createStack(int windowingMode, int activityType, boolean onTop, ActivityInfo info,
-            Intent intent) {
+            Intent intent, boolean createdByOrganizer) {
         if (mSingleTaskInstance && getStackCount() > 0) {
             // Create stack on default display instead since this display can only contain 1 stack.
             // TODO: Kinda a hack, but better that having the decision at each call point. Hoping
             // this goes away once ActivityView is no longer using virtual displays.
             return mRootWindowContainer.getDefaultDisplay().createStack(
-                    windowingMode, activityType, onTop, info, intent);
+                    windowingMode, activityType, onTop, info, intent, createdByOrganizer);
         }
 
-        if (activityType == ACTIVITY_TYPE_UNDEFINED) {
+        if (activityType == ACTIVITY_TYPE_UNDEFINED && !createdByOrganizer) {
             // Can't have an undefined stack type yet...so re-map to standard. Anyone that wants
-            // anything else should be passing it in anyways...
+            // anything else should be passing it in anyways...except for the task organizer.
             activityType = ACTIVITY_TYPE_STANDARD;
         }
 
-        if (activityType != ACTIVITY_TYPE_STANDARD) {
+        if (activityType != ACTIVITY_TYPE_STANDARD && activityType != ACTIVITY_TYPE_UNDEFINED) {
             // For now there can be only one stack of a particular non-standard activity type on a
             // display. So, get that ignoring whatever windowing mode it is currently in.
             ActivityStack stack = getStack(WINDOWING_MODE_UNDEFINED, activityType);
@@ -5842,39 +5882,39 @@
         }
 
         final int stackId = getNextStackId();
-        return createStackUnchecked(windowingMode, activityType, stackId, onTop, info, intent);
+        return createStackUnchecked(windowingMode, activityType, stackId, onTop, info, intent,
+                createdByOrganizer);
     }
 
-    /** @return the tile to create the next stack in. */
-    private TaskTile updateLaunchTile(int windowingMode) {
+    /** @return the root task to create the next task in. */
+    private Task updateLaunchRootTask(int windowingMode) {
         if (!isSplitScreenWindowingMode(windowingMode)) {
-            // Only split-screen windowing modes interact with tiles.
+            // Only split-screen windowing modes can do this currently...
             return null;
         }
         for (int i = getStackCount() - 1; i >= 0; --i) {
-            final TaskTile t = getStackAt(i).asTile();
-            if (t == null || t.getRequestedOverrideWindowingMode() != windowingMode) {
+            final Task t = getStackAt(i);
+            if (!t.mCreatedByOrganizer || t.getRequestedOverrideWindowingMode() != windowingMode) {
                 continue;
             }
-            // If not already set, pick a launch tile which is not the one we are launching
-            // into.
-            if (mLaunchTile == null) {
+            // If not already set, pick a launch root which is not the one we are launching into.
+            if (mLaunchRootTask == null) {
                 for (int j = 0, n = getStackCount(); j < n; ++j) {
-                    TaskTile tt = getStackAt(j).asTile();
-                    if (tt != t) {
-                        mLaunchTile = tt;
+                    final Task tt = getStackAt(j);
+                    if (tt.mCreatedByOrganizer && tt != t) {
+                        mLaunchRootTask = tt;
                         break;
                     }
                 }
             }
             return t;
         }
-        return mLaunchTile;
+        return mLaunchRootTask;
     }
 
     @VisibleForTesting
-    ActivityStack createStackUnchecked(int windowingMode, int activityType,
-            int stackId, boolean onTop, ActivityInfo info, Intent intent) {
+    ActivityStack createStackUnchecked(int windowingMode, int activityType, int stackId,
+            boolean onTop, ActivityInfo info, Intent intent, boolean createdByOrganizer) {
         if (windowingMode == WINDOWING_MODE_PINNED && activityType != ACTIVITY_TYPE_STANDARD) {
             throw new IllegalArgumentException("Stack with windowing mode cannot with non standard "
                     + "activity type.");
@@ -5884,19 +5924,25 @@
             info.applicationInfo = new ApplicationInfo();
         }
 
-        TaskTile tile = updateLaunchTile(windowingMode);
-        if (tile != null) {
-            // Since this stack will be put into a tile, its windowingMode will be inherited.
+        // Task created by organizer are added as root.
+        Task launchRootTask = createdByOrganizer ? null : updateLaunchRootTask(windowingMode);
+        if (launchRootTask != null) {
+            // Since this stack will be put into a root task, its windowingMode will be inherited.
             windowingMode = WINDOWING_MODE_UNDEFINED;
         }
+
         final ActivityStack stack = (ActivityStack) Task.create(mAtmService, stackId, activityType,
-                info, intent);
-        addStack(stack, onTop ? POSITION_TOP : POSITION_BOTTOM);
-        stack.setWindowingMode(windowingMode, false /* animate */, false /* showRecents */,
-                false /* enteringSplitScreenMode */, false /* deferEnsuringVisibility */,
-                true /* creating */);
-        if (tile != null) {
-            tile.addChild(stack, 0 /* index */);
+                info, intent, createdByOrganizer);
+        if (launchRootTask != null) {
+            launchRootTask.addChild(stack, onTop ? POSITION_TOP : POSITION_BOTTOM);
+            if (onTop) {
+                positionStackAtTop((ActivityStack) launchRootTask, false /* includingParents */);
+            }
+        } else {
+            addStack(stack, onTop ? POSITION_TOP : POSITION_BOTTOM);
+            stack.setWindowingMode(windowingMode, false /* animate */, false /* showRecents */,
+                    false /* enteringSplitScreenMode */, false /* deferEnsuringVisibility */,
+                    true /* creating */);
         }
         return stack;
     }
@@ -6031,7 +6077,7 @@
         mTmpFindTaskResult.clear();
         for (int stackNdx = getStackCount() - 1; stackNdx >= 0; --stackNdx) {
             final ActivityStack stack = getStackAt(stackNdx);
-            if (!r.hasCompatibleActivityType(stack)) {
+            if (!r.hasCompatibleActivityType(stack) && stack.isLeafTask()) {
                 if (DEBUG_TASKS) {
                     Slog.d(TAG_TASKS, "Skipping stack: (mismatch activity/stack) " + stack);
                 }
@@ -6103,7 +6149,15 @@
             final int activityType = activityTypes[j];
             for (int i = getStackCount() - 1; i >= 0; --i) {
                 final ActivityStack stack = getStackAt(i);
-                if (stack.getActivityType() == activityType) {
+                // Collect the root tasks that are currently being organized.
+                if (stack.isOrganized()) {
+                    for (int k = stack.getChildCount() - 1; k >= 0; --k) {
+                        final ActivityStack childStack = (ActivityStack) stack.getChildAt(k);
+                        if (childStack.getActivityType() == activityType) {
+                            stacks.add(childStack);
+                        }
+                    }
+                } else if (stack.getActivityType() == activityType) {
                     stacks.add(stack);
                 }
             }
@@ -6117,13 +6171,8 @@
     void onSplitScreenModeDismissed() {
         mAtmService.deferWindowLayout();
         try {
-            mLaunchTile = null;
-            for (int i = getStackCount() - 1; i >= 0; --i) {
-                final TaskTile t = getStackAt(i).asTile();
-                if (t != null) {
-                    t.removeAllChildren();
-                }
-            }
+            mLaunchRootTask = null;
+            moveSplitScreenTasksToFullScreen();
         } finally {
             final ActivityStack topFullscreenStack =
                     getTopStackInWindowingMode(WINDOWING_MODE_FULLSCREEN);
@@ -6141,6 +6190,24 @@
         }
     }
 
+    private void moveSplitScreenTasksToFullScreen() {
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
+        mTmpTasks.clear();
+        forAllTasks(task -> {
+            if (task.mCreatedByOrganizer && task.inSplitScreenWindowingMode() && task.hasChild()) {
+                mTmpTasks.add(task);
+            }
+        });
+
+        for (int i = mTmpTasks.size() - 1; i >= 0; i--) {
+            final Task root = mTmpTasks.get(i);
+            for (int j = 0; j < root.getChildCount(); j++) {
+                wct.reparent(root.getChildAt(j).mRemoteToken, null, true /* toTop */);
+            }
+        }
+        mAtmService.mWindowOrganizerController.applyTransaction(wct);
+    }
+
     /**
      * Returns true if the {@param windowingMode} is supported based on other parameters passed in.
      * @param windowingMode The windowing mode we are checking support for.
@@ -6253,7 +6320,7 @@
             }
         }
 
-        final boolean inSplitScreenMode = hasSplitScreenPrimaryTask();
+        final boolean inSplitScreenMode = isSplitScreenModeActivated();
         if (!inSplitScreenMode
                 && windowingMode == WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY) {
             // Switch to the display's windowing mode if we are not in split-screen mode and we are
@@ -6278,10 +6345,6 @@
     }
 
     boolean isTopNotPinnedStack(ActivityStack stack) {
-        // TODO(task-hierarchy): Remove when tiles are in hierarchy.
-        if (stack instanceof TaskTile) {
-            return false;
-        }
         for (int i = getStackCount() - 1; i >= 0; --i) {
             final ActivityStack current = getStackAt(i);
             if (!current.inPinnedWindowingMode()) {
@@ -6503,7 +6566,7 @@
                     // If default display is in split-window mode, set windowing mode of the stack
                     // to split-screen secondary. Otherwise, set the windowing mode to undefined by
                     // default to let stack inherited the windowing mode from the new display.
-                    final int windowingMode = toDisplay.hasSplitScreenPrimaryTask()
+                    final int windowingMode = toDisplay.isSplitScreenModeActivated()
                             ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
                             : WINDOWING_MODE_UNDEFINED;
                     stack.reparent(toDisplay, true /* onTop */);
@@ -6607,27 +6670,35 @@
      *         already top-most.
      */
     ActivityStack getStackAbove(ActivityStack stack) {
-        final int stackIndex = getIndexOf(stack) + 1;
-        return (stackIndex < getStackCount()) ? getStackAt(stackIndex) : null;
+        final WindowContainer wc = stack.getParent();
+        final int index = wc.mChildren.indexOf(stack) + 1;
+        return (index < wc.mChildren.size()) ? (ActivityStack) wc.mChildren.get(index) : null;
     }
 
     /**
      * Adjusts the {@param stack} behind the last visible stack in the display if necessary.
      * Generally used in conjunction with {@link #moveStackBehindStack}.
      */
+    // TODO(b/151575894): Remove special stack movement methods.
     void moveStackBehindBottomMostVisibleStack(ActivityStack stack) {
         if (stack.shouldBeVisible(null)) {
             // Skip if the stack is already visible
             return;
         }
 
-        // Move the stack to the bottom to not affect the following visibility checks
-        positionStackAtBottom(stack);
+        final boolean isRootTask = stack.isRootTask();
+        if (isRootTask) {
+            // Move the stack to the bottom to not affect the following visibility checks
+            positionStackAtBottom(stack);
+        } else {
+            stack.getParent().positionChildAt(POSITION_BOTTOM, stack, false /* includingParents */);
+        }
 
         // Find the next position where the stack should be placed
-        final int numStacks = getStackCount();
+        final int numStacks = isRootTask ? getStackCount() : stack.getParent().getChildCount();
         for (int stackNdx = 0; stackNdx < numStacks; stackNdx++) {
-            final ActivityStack s = getStackAt(stackNdx);
+            final ActivityStack s = isRootTask ? getStackAt(stackNdx)
+                    : (ActivityStack) stack.getParent().getChildAt(stackNdx);
             if (s == stack) {
                 continue;
             }
@@ -6636,7 +6707,12 @@
                     || winMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
             if (s.shouldBeVisible(null) && isValidWindowingMode) {
                 // Move the provided stack to behind this stack
-                positionStackAt(stack, Math.max(0, stackNdx - 1));
+                final int position = Math.max(0, stackNdx - 1);
+                if (isRootTask) {
+                    positionStackAt(stack, position);
+                } else {
+                    stack.getParent().positionChildAt(position, stack, false /*includingParents */);
+                }
                 break;
             }
         }
@@ -6652,15 +6728,25 @@
             return;
         }
 
+        final WindowContainer parent = stack.getParent();
+        if (parent == null || parent != behindStack.getParent()) {
+            return;
+        }
+
         // Note that positionChildAt will first remove the given stack before inserting into the
         // list, so we need to adjust the insertion index to account for the removed index
         // TODO: Remove this logic when WindowContainer.positionChildAt() is updated to adjust the
         //       position internally
-        final int stackIndex = getIndexOf(stack);
-        final int behindStackIndex = getIndexOf(behindStack);
+        final int stackIndex = parent.mChildren.indexOf(stack);
+        final int behindStackIndex = parent.mChildren.indexOf(behindStack);
         final int insertIndex = stackIndex <= behindStackIndex
                 ? behindStackIndex - 1 : behindStackIndex;
-        positionStackAt(stack, Math.max(0, insertIndex));
+        final int position = Math.max(0, insertIndex);
+        if (stack.isRootTask()) {
+            positionStackAt(stack, position);
+        } else {
+            parent.positionChildAt(position, stack, false /* includingParents */);
+        }
     }
 
     void ensureActivitiesVisible(ActivityRecord starting, int configChanges,
@@ -6697,19 +6783,6 @@
         return getHomeActivityForUser(mRootWindowContainer.mCurrentUser);
     }
 
-    // TODO(task-hierarchy): Remove when tiles are in hierarchy.
-    void addTile(TaskTile tile) {
-        mTaskContainers.addChild(tile, POSITION_BOTTOM);
-        ITaskOrganizer organizer = mAtmService.mTaskOrganizerController.getTaskOrganizer(
-                tile.getWindowingMode());
-        tile.setTaskOrganizer(organizer);
-    }
-
-    // TODO(task-hierarchy): Remove when tiles are in hierarchy.
-    void removeTile(TaskTile tile) {
-        mTaskContainers.removeChild(tile);
-    }
-
     @Nullable
     ActivityRecord getHomeActivityForUser(int userId) {
         final ActivityStack homeStack = getRootHomeTask();
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 5e88fb0..4da4a79 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1527,25 +1527,7 @@
                 && (mNotificationShade.getAttrs().privateFlags
                 & PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION) != 0;
 
-        // When the navigation bar isn't visible, we put up a fake input window to catch all
-        // touch events. This way we can detect when the user presses anywhere to bring back the
-        // nav bar and ensure the application doesn't see the event.
-        if (navVisible || navAllowedHidden) {
-            if (mInputConsumer != null) {
-                mInputConsumer.dismiss();
-                mHandler.sendMessage(
-                        mHandler.obtainMessage(MSG_DISPOSE_INPUT_CONSUMER, mInputConsumer));
-                mInputConsumer = null;
-            }
-        } else if (mInputConsumer == null && mStatusBar != null && canHideNavigationBar()) {
-            mInputConsumer = mDisplayContent.getInputMonitor().createInputConsumer(
-                    mHandler.getLooper(),
-                    INPUT_CONSUMER_NAVIGATION,
-                    HideNavInputEventReceiver::new);
-            // As long as mInputConsumer is active, hover events are not dispatched to the app
-            // and the pointer icon is likely to become stale. Hide it to avoid confusion.
-            InputManager.getInstance().setPointerIconType(PointerIcon.TYPE_NULL);
-        }
+        updateHideNavInputEventReceiver(navVisible, navAllowedHidden);
 
         // For purposes of positioning and showing the nav bar, if we have decided that it can't
         // be hidden (because of the screen aspect ratio), then take that into account.
@@ -1567,6 +1549,28 @@
         mLastNotificationShadeForcesShowingNavigation = notificationShadeForcesShowingNavigation;
     }
 
+    void updateHideNavInputEventReceiver(boolean navVisible, boolean navAllowedHidden) {
+        // When the navigation bar isn't visible, we put up a fake input window to catch all
+        // touch events. This way we can detect when the user presses anywhere to bring back the
+        // nav bar and ensure the application doesn't see the event.
+        if (navVisible || navAllowedHidden) {
+            if (mInputConsumer != null) {
+                mInputConsumer.dismiss();
+                mHandler.sendMessage(
+                        mHandler.obtainMessage(MSG_DISPOSE_INPUT_CONSUMER, mInputConsumer));
+                mInputConsumer = null;
+            }
+        } else if (mInputConsumer == null && mStatusBar != null && canHideNavigationBar()) {
+            mInputConsumer = mDisplayContent.getInputMonitor().createInputConsumer(
+                    mHandler.getLooper(),
+                    INPUT_CONSUMER_NAVIGATION,
+                    HideNavInputEventReceiver::new);
+            // As long as mInputConsumer is active, hover events are not dispatched to the app
+            // and the pointer icon is likely to become stale. Hide it to avoid confusion.
+            InputManager.getInstance().setPointerIconType(PointerIcon.TYPE_NULL);
+        }
+    }
+
     private static void updateInsetsStateForDisplayCutout(DisplayFrames displayFrames,
             InsetsState state) {
         if (displayFrames.mDisplayCutout.getDisplayCutout().isEmpty()) {
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index bb02789..ac6e75c 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -25,6 +25,7 @@
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
 import static android.view.SyncRtSurfaceTransactionApplier.applyParams;
+import static android.view.WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_TOUCH;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
 
@@ -91,6 +92,12 @@
                 || focusedWin != getNavControlTarget(focusedWin)
                 || focusedWin.getRequestedInsetsState().getSource(ITYPE_NAVIGATION_BAR)
                         .isVisible());
+        updateHideNavInputEventReceiver();
+    }
+
+    private void updateHideNavInputEventReceiver() {
+        mPolicy.updateHideNavInputEventReceiver(!isHidden(ITYPE_NAVIGATION_BAR),
+                mFocusedWin.mAttrs.insetsFlags.behavior != BEHAVIOR_SHOW_BARS_BY_TOUCH);
     }
 
     boolean isHidden(@InternalInsetsType int type) {
@@ -169,6 +176,7 @@
         if (windowState == getNavControlTarget(mFocusedWin)) {
             mNavBar.setVisible(state.getSource(ITYPE_NAVIGATION_BAR).isVisible());
         }
+        updateHideNavInputEventReceiver();
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 44034ed..6b39fd2 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -406,12 +406,11 @@
             // show on top of the lock screen. In this can we want to dismiss the docked
             // stack since it will be complicated/risky to try to put the activity on top
             // of the lock screen in the right fullscreen configuration.
-            final ActivityStack stack =
-                    mRootWindowContainer.getDefaultDisplay().getRootSplitScreenPrimaryTask();
-            if (stack == null) {
+            final DisplayContent display = mRootWindowContainer.getDefaultDisplay();
+            if (!display.isSplitScreenModeActivated()) {
                 return;
             }
-            mRootWindowContainer.getDefaultDisplay().onSplitScreenModeDismissed();
+            display.onSplitScreenModeDismissed();
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index bd5666d..244ba82 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -21,6 +21,7 @@
 import static android.app.ActivityManager.RECENT_WITH_EXCLUDED;
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
@@ -1298,6 +1299,7 @@
         switch (task.getActivityType()) {
             case ACTIVITY_TYPE_HOME:
             case ACTIVITY_TYPE_RECENTS:
+            case ACTIVITY_TYPE_DREAM:
                 // Ignore certain activity types completely
                 return false;
             case ACTIVITY_TYPE_ASSISTANT:
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index adafdec..9089240 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -388,11 +388,12 @@
                     // surfaces needs to be done immediately.
                     mWindowManager.executeAppTransition();
 
-                    if (targetStack.getTile() != null) {
+                    final Task rootTask = targetStack.getRootTask();
+                    if (rootTask.isOrganized()) {
                         // Client state may have changed during the recents animation, so force
                         // send task info so the client can synchronize its state.
                         mService.mTaskOrganizerController.dispatchTaskInfoChanged(
-                                targetStack.mTile, true /* force */);
+                                rootTask, true /* force */);
                     }
                 } catch (Exception e) {
                     Slog.e(TAG, "Failed to clean up recents activity", e);
diff --git a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
index 45f8a15..420997a 100644
--- a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
+++ b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
@@ -20,7 +20,6 @@
 import static com.android.server.wm.ActivityStack.TAG_TASKS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ADD_REMOVE;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TASKS;
-import static com.android.server.wm.Task.REPARENT_LEAVE_STACK_IN_PLACE;
 
 import android.app.ActivityOptions;
 import android.content.Intent;
@@ -233,29 +232,6 @@
         }
 
         final ActivityTaskManagerService atmService = mTargetStack.mAtmService;
-        final ArrayList<Task> createdTasks = new ArrayList<>();
-        while (!mPendingReparentActivities.isEmpty()) {
-            final ActivityRecord r = mPendingReparentActivities.remove(0);
-            final ActivityRecord bottom = mTargetStack.getBottomMostActivity();
-            final Task targetTask;
-            if (bottom != null && r.taskAffinity.equals(bottom.getTask().affinity)) {
-                // If the activity currently at the bottom has the same task affinity as
-                // the one we are moving, then merge it into the same task.
-                targetTask = bottom.getTask();
-                if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Start pushing activity "
-                        + r + " out to bottom task " + targetTask);
-            } else {
-                targetTask = mTargetStack.reuseOrCreateTask(
-                        r.info, null /*intent*/, false /*toTop*/);
-                targetTask.affinityIntent = r.intent;
-                createdTasks.add(targetTask);
-                if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Start pushing activity "
-                        + r + " out to new task " + targetTask);
-            }
-            r.reparent(targetTask, 0 /* position */, "resetTargetTaskIfNeeded");
-            atmService.mStackSupervisor.mRecentTasks.add(targetTask);
-        }
-
         DisplayContent display = mTargetStack.getDisplay();
         final boolean singleTaskInstanceDisplay = display.isSingleTaskInstance();
         if (singleTaskInstanceDisplay) {
@@ -264,16 +240,33 @@
 
         final int windowingMode = mTargetStack.getWindowingMode();
         final int activityType = mTargetStack.getActivityType();
-        if (!singleTaskInstanceDisplay && !display.alwaysCreateStack(windowingMode, activityType)) {
-            return;
-        }
 
-        while (!createdTasks.isEmpty()) {
-            final Task targetTask = createdTasks.remove(createdTasks.size() - 1);
-            final ActivityStack targetStack = display.getOrCreateStack(
-                    windowingMode, activityType, false /* onTop */);
-            targetTask.reparent(targetStack, false /* toTop */, REPARENT_LEAVE_STACK_IN_PLACE,
-                    false /* animate */, true /* deferResume */, "resetTargetTask");
+        while (!mPendingReparentActivities.isEmpty()) {
+            final ActivityRecord r = mPendingReparentActivities.remove(0);
+            final boolean alwaysCreateTask = DisplayContent.alwaysCreateStack(windowingMode,
+                    activityType);
+            final Task task = alwaysCreateTask
+                    ? display.getBottomMostTask() : mTargetStack.getBottomMostTask();
+            Task targetTask = null;
+            if (task != null && r.taskAffinity.equals(task.affinity)) {
+                // If the activity currently at the bottom has the same task affinity as
+                // the one we are moving, then merge it into the same task.
+                targetTask = task;
+                if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Start pushing activity "
+                        + r + " out to bottom task " + targetTask);
+            }
+            if (targetTask == null) {
+                if (alwaysCreateTask) {
+                    targetTask = display.getOrCreateStack(windowingMode, activityType,
+                            false /* onTop */);
+                } else {
+                    targetTask = mTargetStack.reuseOrCreateTask(r.info, null /*intent*/,
+                            false /*toTop*/);
+                }
+                targetTask.affinityIntent = r.intent;
+            }
+            r.reparent(targetTask, 0 /* position */, "resetTargetTaskIfNeeded");
+            atmService.mStackSupervisor.mRecentTasks.add(targetTask);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index ebf1bc9..b2920ee 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -18,6 +18,7 @@
 
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
@@ -1970,8 +1971,7 @@
         final int focusStackId = topFocusedStack != null
                 ? topFocusedStack.getRootTaskId() : INVALID_TASK_ID;
         // We dismiss the docked stack whenever we switch users.
-        final ActivityStack dockedStack = getDefaultDisplay().getRootSplitScreenPrimaryTask();
-        if (dockedStack != null) {
+        if (getDefaultDisplay().isSplitScreenModeActivated()) {
             getDefaultDisplay().onSplitScreenModeDismissed();
         }
         // Also dismiss the pinned stack whenever we switch users. Removing the pinned stack will
@@ -2110,20 +2110,18 @@
             final ActivityStack stack;
             if (singleActivity) {
                 stack = r.getRootTask();
+                stack.setWindowingMode(WINDOWING_MODE_PINNED);
             } else {
-                // In the case of multiple activities, we will create a new stack for it and then
-                // move the PIP activity into the stack.
-                // We will then perform a windowing mode change for both scenarios.
-                stack = display.createStack(
-                        r.getRootTask().getRequestedOverrideWindowingMode(),
-                        r.getActivityType(), ON_TOP, r.info, r.intent);
+                // In the case of multiple activities, we will create a new task for it and then
+                // move the PIP activity into the task.
+                stack = display.createStack(WINDOWING_MODE_PINNED, r.getActivityType(), ON_TOP,
+                        r.info, r.intent, false /* createdByOrganizer */);
+
                 // There are multiple activities in the task and moving the top activity should
                 // reveal/leave the other activities in their original task.
                 r.reparent(stack, MAX_VALUE, "moveActivityToStack");
             }
 
-            stack.setWindowingMode(WINDOWING_MODE_PINNED);
-
             // Reset the state that indicates it can enter PiP while pausing after we've moved it
             // to the pinned stack
             r.supportsEnterPipOnTaskSwitch = false;
@@ -2799,16 +2797,19 @@
         if (stack == null && r != null) {
             stack = r.getRootTask();
         }
+        int windowingMode = launchParams != null ? launchParams.mWindowingMode
+                : WindowConfiguration.WINDOWING_MODE_UNDEFINED;
         if (stack != null) {
             display = stack.getDisplay();
             if (display != null && canLaunchOnDisplay(r, display.mDisplayId)) {
-                int windowingMode = launchParams != null ? launchParams.mWindowingMode
-                        : WindowConfiguration.WINDOWING_MODE_UNDEFINED;
                 if (windowingMode == WindowConfiguration.WINDOWING_MODE_UNDEFINED) {
                     windowingMode = display.resolveWindowingMode(r, options, candidateTask,
                             activityType);
                 }
-                if (stack.isCompatible(windowingMode, activityType)) {
+                // Always allow organized tasks that created by organizer since the activity type
+                // of an organized task is decided by the activity type of its top child, which
+                // could be incompatible with the given windowing mode and activity type.
+                if (stack.isCompatible(windowingMode, activityType) || stack.mCreatedByOrganizer) {
                     return stack;
                 }
                 if (windowingMode == WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY
@@ -2826,6 +2827,10 @@
 
         if (display == null || !canLaunchOnDisplay(r, display.mDisplayId)) {
             display = getDefaultDisplay();
+            if (windowingMode == WindowConfiguration.WINDOWING_MODE_UNDEFINED) {
+                windowingMode = display.resolveWindowingMode(r, options, candidateTask,
+                        activityType);
+            }
         }
 
         return display.getOrCreateStack(r, options, candidateTask, activityType, onTop);
@@ -2915,11 +2920,10 @@
             case ACTIVITY_TYPE_HOME: return r.isActivityTypeHome();
             case ACTIVITY_TYPE_RECENTS: return r.isActivityTypeRecents();
             case ACTIVITY_TYPE_ASSISTANT: return r.isActivityTypeAssistant();
+            case ACTIVITY_TYPE_DREAM: return r.isActivityTypeDream();
         }
-        // TODO(task-hierarchy): Find another way to differentiate tile from normal stack once it is
-        //                       part of the hierarchy
-        if (stack instanceof TaskTile) {
-            // Don't launch directly into tiles.
+        if (stack.mCreatedByOrganizer) {
+            // Don't launch directly into task created by organizer...but why can't we?
             return false;
         }
         // There is a 1-to-1 relationship between stack and task when not in
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index bfe3147..f19c106 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -117,7 +117,6 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
-import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Debug;
 import android.os.IBinder;
@@ -492,6 +491,17 @@
     PictureInPictureParams mPictureInPictureParams = new PictureInPictureParams.Builder().build();
 
     /**
+     * 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>
+     */
+    boolean mCreatedByOrganizer;
+
+    /**
      * Don't use constructor directly. Use {@link #create(ActivityTaskManagerService, int,
      * ActivityInfo, Intent, TaskDescription)} instead.
      */
@@ -1356,7 +1366,7 @@
         if (applicationType != ACTIVITY_TYPE_UNDEFINED || !hasChild()) {
             return applicationType;
         }
-        return getChildAt(0).getActivityType();
+        return getTopChild().getActivityType();
     }
 
     @Override
@@ -1369,6 +1379,12 @@
 
         ProtoLog.v(WM_DEBUG_ADD_REMOVE, "addChild: %s at top.", this);
 
+        // A rootable task that is now being added to be the child of an organized task. Making
+        // sure the stack references is keep updated.
+        if (mTaskOrganizer != null && mCreatedByOrganizer && child.asTask() != null) {
+            mDisplayContent.addStackReferenceIfNeeded((ActivityStack) child);
+        }
+
         // Make sure the list of display UID whitelists is updated
         // now that this record is in a new task.
         mRootWindowContainer.updateUIDsPresentOnDisplay();
@@ -1409,6 +1425,11 @@
 
     @Override
     void removeChild(WindowContainer child) {
+        // A rootable child task that is now being removed from an organized task. Making sure
+        // the stack references is keep updated.
+        if (mTaskOrganizer != null && mCreatedByOrganizer && child.asTask() != null) {
+            mDisplayContent.removeStackReferenceIfNeeded((ActivityStack) child);
+        }
         removeChild(child, "removeChild");
     }
 
@@ -1456,8 +1477,9 @@
                 mStackSupervisor.removeTask(this, false /* killProcess */,
                         !REMOVE_FROM_RECENTS, reason);
             }
-        } else if (!mReuseTask) {
+        } else if (!mReuseTask && !mCreatedByOrganizer) {
             // Remove entire task if it doesn't have any activity left and it isn't marked for reuse
+            // or created by task organizer.
             if (!isRootTask) {
                 getStack().removeChild(this, reason);
             }
@@ -1869,7 +1891,12 @@
         final Task parentTask = getParent().asTask();
         if (parentTask != null) {
             parentTask.onActivityStateChanged(record, state, reason);
-            return;
+            // We still want to update the resumed activity if the parent task is created by
+            // organizer in order to keep the information synced once got reparented out from the
+            // organized task.
+            if (!parentTask.mCreatedByOrganizer) {
+                return;
+            }
         }
 
         if (record == mResumedActivity && state != RESUMED) {
@@ -2303,18 +2330,30 @@
         return Configuration.reduceScreenLayout(sourceScreenLayout, longSize, shortSize);
     }
 
-    void resolveTileOverrideConfiguration(Configuration newParentConfig) {
+    private void resolveOrganizedOverrideConfiguration(Configuration newParentConfig) {
         super.resolveOverrideConfiguration(newParentConfig);
+        if (!isOrganized()) {
+            return;
+        }
+
+        final Task root = getRootTask();
+        if (root == this) {
+            return;
+        }
+
+        // Ensure to have the same windowing mode for the child tasks that controlled by task org.
+        getResolvedOverrideConfiguration().windowConfiguration
+                .setWindowingMode(root.getWindowingMode());
     }
 
     @Override
     void resolveOverrideConfiguration(Configuration newParentConfig) {
-        if (!isLeafTask()) {
-            resolveTileOverrideConfiguration(newParentConfig);
+        if (!isLeafTask() || mCreatedByOrganizer) {
+            resolveOrganizedOverrideConfiguration(newParentConfig);
             return;
         }
         mTmpBounds.set(getResolvedOverrideConfiguration().windowConfiguration.getBounds());
-        resolveTileOverrideConfiguration(newParentConfig);
+        resolveOrganizedOverrideConfiguration(newParentConfig);
         int windowingMode =
                 getResolvedOverrideConfiguration().windowConfiguration.getWindowingMode();
         if (windowingMode == WINDOWING_MODE_UNDEFINED) {
@@ -2403,7 +2442,9 @@
     }
 
     Rect updateOverrideConfigurationFromLaunchBounds() {
-        final Rect bounds = getLaunchBounds();
+        // If the task is controlled by another organized task, do not set override
+        // configurations and let its parent (organized task) to control it;
+        final Rect bounds = isOrganized() && !isRootTask() ? null : getLaunchBounds();
         setBounds(bounds);
         if (bounds != null && !bounds.isEmpty()) {
             // TODO: Review if we actually want to do this - we are setting the launch bounds
@@ -2587,7 +2628,7 @@
         // preserve POSITION_BOTTOM/POSITION_TOP positions if they are still valid.
         if (suggestedPosition == POSITION_BOTTOM && minPosition == 0) {
             return POSITION_BOTTOM;
-        } else if (suggestedPosition == POSITION_TOP && maxPosition == (size - 1)) {
+        } else if (suggestedPosition == POSITION_TOP && maxPosition >= (size - 1)) {
             return POSITION_TOP;
         }
         // Reset position based on minimum/maximum possible positions.
@@ -3385,14 +3426,12 @@
         info.supportsSplitScreenMultiWindow = supportsSplitScreenWindowingMode();
         info.configuration.setTo(getConfiguration());
         info.token = mRemoteToken;
-        // Get's the first non-undefined activity type among this and children. Can't use
-        // configuration.windowConfiguration because that would only be this level.
-        info.topActivityType = getActivityType();
 
         //TODO (AM refactor): Just use local once updateEffectiveIntent is run during all child
         //                    order changes.
         final Task top = getTopMostTask();
         info.resizeMode = top != null ? top.mResizeMode : mResizeMode;
+        info.topActivityType = top.getActivityType();
 
         if (mPictureInPictureParams.empty()) {
             info.pictureInPictureParams = null;
@@ -3423,10 +3462,6 @@
         return this;
     }
 
-    TaskTile asTile() {
-        return null;
-    }
-
     // TODO(task-merge): Figure-out how this should work with hierarchy tasks.
     boolean shouldBeVisible(ActivityRecord starting) {
         return true;
@@ -3722,8 +3757,9 @@
     }
 
     static Task create(ActivityTaskManagerService service, int taskId, int activityType,
-            ActivityInfo info, Intent intent) {
-        return getTaskFactory().create(service, taskId, activityType, info, intent);
+            ActivityInfo info, Intent intent, boolean createdByOrganizer) {
+        return getTaskFactory().create(service, taskId, activityType, info, intent,
+                createdByOrganizer);
     }
 
     static Task create(ActivityTaskManagerService service, int taskId, ActivityInfo info,
@@ -3745,8 +3781,9 @@
      */
     static class TaskFactory {
         Task create(ActivityTaskManagerService service, int taskId, int activityType,
-                ActivityInfo info, Intent intent) {
-            return new ActivityStack(service, taskId, activityType, info, intent);
+                ActivityInfo info, Intent intent, boolean createdByOrganizer) {
+            return new ActivityStack(service, taskId, activityType, info, intent,
+                    createdByOrganizer);
         }
 
         Task create(ActivityTaskManagerService service, int taskId, ActivityInfo info,
@@ -4022,21 +4059,29 @@
     @Override
     boolean isOrganized() {
         final Task rootTask = getRootTask();
-        // if the rootTask is a "child" of a tile, then don't consider it a root task.
-        // TODO: remove this along with removing tile.
-        if (((ActivityStack) rootTask).getTile() != null) {
+        if (rootTask.mTaskOrganizer == null) {
+            // You are obviously not organized...
             return false;
         }
-        return rootTask == this && rootTask.mTaskOrganizer != null;
+        if (rootTask == this) {
+            // Root tasks can be organized.
+            return true;
+        }
+        if (rootTask.mCreatedByOrganizer && getParent() == rootTask) {
+            // Direct children of tasks added by the organizer can the organized.
+            return true;
+        }
+
+        return false;
     }
 
     @Override
     protected void reparentSurfaceControl(SurfaceControl.Transaction t, SurfaceControl newParent) {
         /**
-         * Avoid yanking back control from the TaskOrganizer, which has presumably reparented the
-         * Surface in to its own hierarchy.
+         * Avoid reparenting SurfaceControl of the organized tasks that are always on top, since
+         * the surfaces should be controlled by the organizer itself, like bubbles.
          */
-        if (isOrganized()) {
+        if (isOrganized() && isAlwaysOnTop()) {
             return;
         }
         super.reparentSurfaceControl(t, newParent);
@@ -4071,6 +4116,9 @@
         mTaskOrganizer = null;
         mLastTaskOrganizerWindowingMode = -1;
         onTaskOrganizerChanged();
+        if (mCreatedByOrganizer) {
+            removeImmediately();
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 5523678..05b721b 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -17,6 +17,7 @@
 package com.android.server.wm;
 
 import static android.Manifest.permission.MANAGE_ACTIVITY_STACKS;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 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;
@@ -27,13 +28,14 @@
 
 import android.annotation.Nullable;
 import android.app.ActivityManager.RunningTaskInfo;
+import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
-import android.window.ITaskOrganizerController;
 import android.window.ITaskOrganizer;
+import android.window.ITaskOrganizerController;
 import android.window.IWindowContainer;
 
 import com.android.internal.util.ArrayUtils;
@@ -255,11 +257,12 @@
                 if (display == null) {
                     return null;
                 }
-                final int nextId = display.getNextStackId();
-                TaskTile tile = new TaskTile(mService, nextId, windowingMode);
-                display.addTile(tile);
-                RunningTaskInfo out = tile.getTaskInfo();
-                mLastSentTaskInfos.put(tile, out);
+
+                final Task task = display.getOrCreateStack(windowingMode, ACTIVITY_TYPE_UNDEFINED,
+                        false /* onTop */, new Intent(), null /* candidateTask */,
+                        true /* createdByOrganizer */);
+                RunningTaskInfo out = task.getTaskInfo();
+                mLastSentTaskInfos.put(task, out);
                 return out;
             }
         } finally {
@@ -273,11 +276,13 @@
         final long origId = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
-                TaskTile tile = TaskTile.forToken(token.asBinder());
-                if (tile == null) {
-                    return false;
+                final Task task = WindowContainer.fromBinder(token.asBinder()).asTask();
+                if (task == null) return false;
+                if (!task.mCreatedByOrganizer) {
+                    throw new IllegalArgumentException(
+                            "Attempt to delete task not created by organizer task=" + task);
                 }
-                tile.removeImmediately();
+                task.removeImmediately();
                 return true;
             }
         } finally {
@@ -358,12 +363,7 @@
                 if (task == null) {
                     return null;
                 }
-                ActivityStack rootTask = (ActivityStack) task.getRootTask();
-                final TaskTile tile = rootTask.getTile();
-                if (tile != null) {
-                    rootTask = tile;
-                }
-                return rootTask.mRemoteToken;
+                return task.getRootTask().mRemoteToken;
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -371,7 +371,7 @@
     }
 
     @Override
-    public void setLaunchRoot(int displayId, @Nullable IWindowContainer tile) {
+    public void setLaunchRoot(int displayId, @Nullable IWindowContainer token) {
         enforceStackPermission("setLaunchRoot()");
         final long origId = Binder.clearCallingIdentity();
         try {
@@ -380,16 +380,21 @@
                 if (display == null) {
                     return;
                 }
-                TaskTile taskTile = tile == null ? null : TaskTile.forToken(tile.asBinder());
-                if (taskTile == null) {
-                    display.mLaunchTile = null;
+                Task task = token == null
+                        ? null : WindowContainer.fromBinder(token.asBinder()).asTask();
+                if (task == null) {
+                    display.mLaunchRootTask = null;
                     return;
                 }
-                if (taskTile.getDisplay() != display) {
-                    throw new RuntimeException("Can't set launch root for display " + displayId
-                            + " to task on display " + taskTile.getDisplay().getDisplayId());
+                if (!task.mCreatedByOrganizer) {
+                    throw new IllegalArgumentException("Attempt to set task not created by "
+                            + "organizer as launch root task=" + task);
                 }
-                display.mLaunchTile = taskTile;
+                if (task.getDisplayContent() != display) {
+                    throw new RuntimeException("Can't set launch root for display " + displayId
+                            + " to task on display " + task.getDisplayContent().getDisplayId());
+                }
+                display.mLaunchRootTask = task;
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -411,25 +416,25 @@
                     Slog.e(TAG, "Can't get children of " + parent + " because it is not valid.");
                     return null;
                 }
-                // For now, only support returning children of persistent root tasks (of which the
-                // only current implementation is TaskTile).
-                if (!(container instanceof TaskTile)) {
+                final Task task = container.asTask();
+                if (task == null) {
+                    Slog.e(TAG, container + " is not a task...");
+                    return null;
+                }
+                // For now, only support returning children of tasks created by the organizer.
+                if (!task.mCreatedByOrganizer) {
                     Slog.w(TAG, "Can only get children of root tasks created via createRootTask");
                     return null;
                 }
                 ArrayList<RunningTaskInfo> out = new ArrayList<>();
-                // Tiles aren't real parents, so we need to go through stacks on the display to
-                // ensure correct ordering.
-                final DisplayContent dc = container.getDisplayContent();
-                for (int i = dc.getStackCount() - 1; i >= 0; --i) {
-                    final ActivityStack as = dc.getStackAt(i);
-                    if (as.getTile() == container) {
-                        if (activityTypes != null
-                                && !ArrayUtils.contains(activityTypes, as.getActivityType())) {
-                            continue;
-                        }
-                        out.add(as.getTaskInfo());
+                for (int i = task.getChildCount() - 1; i >= 0; --i) {
+                    final Task child = task.getChildAt(i).asTask();
+                    if (child == null) continue;
+                    if (activityTypes != null
+                            && !ArrayUtils.contains(activityTypes, child.getActivityType())) {
+                        continue;
                     }
+                    out.add(child.getTaskInfo());
                 }
                 return out;
             }
@@ -451,12 +456,7 @@
                 }
                 ArrayList<RunningTaskInfo> out = new ArrayList<>();
                 for (int i = dc.getStackCount() - 1; i >= 0; --i) {
-                    final ActivityStack task = dc.getStackAt(i);
-                    if (task.getTile() != null) {
-                        // a tile is supposed to look like a parent, so don't include their
-                        // "children" here. They can be accessed via getChildTasks()
-                        continue;
-                    }
+                    final Task task = dc.getStackAt(i);
                     if (activityTypes != null
                             && !ArrayUtils.contains(activityTypes, task.getActivityType())) {
                         continue;
diff --git a/services/core/java/com/android/server/wm/TaskTile.java b/services/core/java/com/android/server/wm/TaskTile.java
deleted file mode 100644
index 51142b1..0000000
--- a/services/core/java/com/android/server/wm/TaskTile.java
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * Copyright (C) 2020 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_UNDEFINED;
-import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
-import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
-
-import android.app.ActivityManager;
-import android.app.TaskInfo;
-import android.app.WindowConfiguration;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.res.Configuration;
-import android.graphics.Rect;
-import android.os.IBinder;
-import android.util.Slog;
-
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.function.Consumer;
-
-/**
- * A Tile. Right now this acts as a proxy for manipulating non-child stacks. Eventually, this
- * can become an actual parent.
- */
-// TODO(task-hierarchy): Remove when tasks can nest >2 or when single tasks can handle their
-//                       own lifecycles.
-public class TaskTile extends ActivityStack {
-    private static final String TAG = "TaskTile";
-    final ArrayList<WindowContainer> mChildren = new ArrayList<>();
-
-    private static ActivityInfo createEmptyActivityInfo() {
-        ActivityInfo info = new ActivityInfo();
-        info.applicationInfo = new ApplicationInfo();
-        return info;
-    }
-
-    TaskTile(ActivityTaskManagerService atmService, int id, int windowingMode) {
-        super(atmService, id, new Intent() /*intent*/,  null /*affinityIntent*/, null /*affinity*/,
-                null /*rootAffinity*/, null /*realActivity*/, null /*origActivity*/,
-                false /*rootWasReset*/, false /*autoRemoveRecents*/, false /*askedCompatMode*/,
-                0 /*userId*/, 0 /*effectiveUid*/, null /*lastDescription*/,
-                System.currentTimeMillis(), true /*neverRelinquishIdentity*/,
-                new ActivityManager.TaskDescription(), id, INVALID_TASK_ID, INVALID_TASK_ID,
-                0 /*taskAffiliationColor*/, 0 /*callingUid*/, "" /*callingPackage*/,
-                null /*callingFeatureId*/, RESIZE_MODE_RESIZEABLE,
-                false /*supportsPictureInPicture*/, false /*_realActivitySuspended*/,
-                false /*userSetupComplete*/, INVALID_MIN_SIZE, INVALID_MIN_SIZE,
-                createEmptyActivityInfo(), null /*voiceSession*/, null /*voiceInteractor*/,
-                null /*stack*/);
-        getRequestedOverrideConfiguration().windowConfiguration.setWindowingMode(windowingMode);
-    }
-
-    @Override
-    void onDisplayChanged(DisplayContent dc) {
-        mDisplayContent = null;
-        if (dc != null) {
-            dc.getPendingTransaction().merge(getPendingTransaction());
-        }
-        mDisplayContent = dc;
-        // Virtual parent, so don't notify children.
-    }
-
-    @Override
-    TaskTile asTile() {
-        return this;
-    }
-
-    @Override
-    protected void addChild(WindowContainer child, Comparator<WindowContainer> comparator) {
-        throw new RuntimeException("Improper use of addChild() on Tile");
-    }
-
-    @Override
-    void addChild(WindowContainer child, int index) {
-        mChildren.add(child);
-        if (child instanceof ActivityStack) {
-            ((ActivityStack) child).setTile(this);
-        }
-        mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged(
-                this, false /* force */);
-    }
-
-    @Override
-    void removeChild(WindowContainer child) {
-        if (child instanceof ActivityStack) {
-            ((ActivityStack) child).setTile(null);
-        }
-        mChildren.remove(child);
-        mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged(
-                this, false /* force */);
-    }
-
-    void removeAllChildren() {
-        for (int i = mChildren.size() - 1; i >= 0; --i) {
-            final WindowContainer child = mChildren.get(i);
-            if (child instanceof ActivityStack) {
-                ((ActivityStack) child).setTile(null);
-            }
-        }
-        mChildren.clear();
-        mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged(
-                this, false /* force */);
-    }
-
-    @Override
-    protected int getChildCount() {
-        // Currently 0 as this isn't a proper hierarchy member yet.
-        return 0;
-    }
-
-    @Override
-    public void setWindowingMode(/*@WindowConfiguration.WindowingMode*/ int windowingMode) {
-        Configuration c = new Configuration(getRequestedOverrideConfiguration());
-        c.windowConfiguration.setWindowingMode(windowingMode);
-        onRequestedOverrideConfigurationChanged(c);
-    }
-
-    @Override
-    public void onConfigurationChanged(Configuration newParentConfig) {
-        super.onConfigurationChanged(newParentConfig);
-        for (int i = mChildren.size() - 1; i >= 0; --i) {
-            final WindowContainer child = mChildren.get(i);
-            child.onConfigurationChanged(child.getParent().getConfiguration());
-        }
-    }
-
-    void forAllTileActivities(Consumer<ActivityRecord> callback) {
-        for (int i = mChildren.size() - 1; i >= 0; --i) {
-            mChildren.get(i).forAllActivities(callback, true /* traverseTopToBottom */);
-        }
-    }
-
-    /**
-     * Until this can be part of the hierarchy, the Stack level can use this utility during
-     * resolveOverrideConfig to simulate inheritance.
-     */
-    void updateResolvedConfig(Configuration inOutResolvedConfig) {
-        Rect resolveBounds = inOutResolvedConfig.windowConfiguration.getBounds();
-        if (resolveBounds.isEmpty()) {
-            resolveBounds.set(getRequestedOverrideBounds());
-        }
-        int stackMode = inOutResolvedConfig.windowConfiguration.getWindowingMode();
-        if (stackMode == WindowConfiguration.WINDOWING_MODE_UNDEFINED
-                || stackMode == WindowConfiguration.WINDOWING_MODE_FULLSCREEN) {
-            // Also replace FULLSCREEN because we interpret FULLSCREEN as "fill parent"
-            inOutResolvedConfig.windowConfiguration.setWindowingMode(
-                    getRequestedOverrideWindowingMode());
-        }
-        if (inOutResolvedConfig.smallestScreenWidthDp
-                == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) {
-            inOutResolvedConfig.smallestScreenWidthDp =
-                    getRequestedOverrideConfiguration().smallestScreenWidthDp;
-        }
-        if (inOutResolvedConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
-            inOutResolvedConfig.screenWidthDp = getRequestedOverrideConfiguration().screenWidthDp;
-        }
-        if (inOutResolvedConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
-            inOutResolvedConfig.screenHeightDp = getRequestedOverrideConfiguration().screenHeightDp;
-        }
-        Rect resolveAppBounds = inOutResolvedConfig.windowConfiguration.getAppBounds();
-        if (resolveAppBounds == null || resolveAppBounds.isEmpty()) {
-            inOutResolvedConfig.windowConfiguration.setAppBounds(
-                    getRequestedOverrideConfiguration().windowConfiguration.getAppBounds());
-        }
-    }
-
-    @Override
-    void fillTaskInfo(TaskInfo info) {
-        super.fillTaskInfo(info);
-        WindowContainer top = null;
-        // Check mChildren.isEmpty directly because hasChild() -> getChildCount() always returns 0
-        if (!mChildren.isEmpty()) {
-            // Find the top-most root task which is a virtual child of this Tile. Because this is a
-            // virtual parent, the mChildren order here isn't changed during hierarchy operations.
-            WindowContainer parent = mChildren.get(0).getParent();
-            for (int i = parent.getChildCount() - 1; i >= 0; --i) {
-                if (mChildren.contains(parent.getChildAt(i))) {
-                    top = parent.getChildAt(i);
-                    break;
-                }
-            }
-        }
-        final Task topTask = top == null ? null : top.getTopMostTask();
-        boolean isResizable = topTask == null || topTask.isResizeable();
-        info.resizeMode = isResizable ? RESIZE_MODE_RESIZEABLE : RESIZE_MODE_UNRESIZEABLE;
-        info.topActivityType = top == null ? ACTIVITY_TYPE_UNDEFINED : top.getActivityType();
-    }
-
-    @Override
-    void removeImmediately() {
-        removeAllChildren();
-        super.removeImmediately();
-    }
-
-    @Override
-    void taskOrganizerDied() {
-        super.taskOrganizerDied();
-        removeImmediately();
-    }
-
-    static TaskTile forToken(IBinder token) {
-        try {
-            return (TaskTile) ((RemoteToken) token).getContainer();
-        } catch (ClassCastException e) {
-            Slog.w(TAG, "Bad tile token: " + token, e);
-            return null;
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index e416e80..5f21e17 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -159,17 +159,7 @@
                                 false /* preserveWindow */);
                         try {
                             for (int i = haveConfigChanges.size() - 1; i >= 0; --i) {
-                                final WindowContainer wc = haveConfigChanges.valueAt(i);
-                                final Task task = wc.asTask();
-                                final TaskTile tile = task != null ? task.asTile() : null;
-                                if (tile != null) {
-                                    // Special case for tile. Can't override normal forAllActivities
-                                    // because it generates duplicate calls and messes up existing
-                                    // code-paths.
-                                    tile.forAllTileActivities(f);
-                                } else {
-                                    wc.forAllActivities(f);
-                                }
+                                haveConfigChanges.valueAt(i).forAllActivities(f);
                             }
                         } finally {
                             f.recycle();
@@ -223,51 +213,65 @@
 
     private int sanitizeAndApplyHierarchyOp(WindowContainer container,
             WindowContainerTransaction.HierarchyOp hop) {
-        if (!(container instanceof Task)) {
+        final Task task = container.asTask();
+        if (task == null) {
             throw new IllegalArgumentException("Invalid container in hierarchy op");
         }
-        if (container.getDisplayContent() == null) {
-            Slog.w(TAG, "Container is no longer attached: " + container);
+        final DisplayContent dc = task.getDisplayContent();
+        if (dc == null) {
+            Slog.w(TAG, "Container is no longer attached: " + task);
             return 0;
         }
+        final ActivityStack as = (ActivityStack) task;
+
         if (hop.isReparent()) {
-            // special case for tiles since they are "virtual" parents
-            if (container instanceof ActivityStack && ((ActivityStack) container).isRootTask()) {
-                ActivityStack as = (ActivityStack) container;
-                TaskTile newParent = hop.getNewParent() == null ? null
-                        : (TaskTile) WindowContainer.fromBinder(hop.getNewParent());
-                if (as.getTile() != newParent) {
-                    if (as.getTile() != null) {
-                        as.getTile().removeChild(as);
+            final boolean isNonOrganizedRootableTask =
+                    (task.isRootTask() && !task.mCreatedByOrganizer)
+                            || task.getParent().asTask().mCreatedByOrganizer;
+            if (isNonOrganizedRootableTask) {
+                Task newParent = hop.getNewParent() == null ? null
+                        : WindowContainer.fromBinder(hop.getNewParent()).asTask();
+                if (task.getParent() != newParent) {
+                    if (newParent == null) {
+                        // Re-parent task to display as a root task.
+                        dc.moveStackToDisplay(as, hop.getToTop());
+                    } else if (newParent.inMultiWindowMode() && !task.isResizeable()
+                            && task.isLeafTask()) {
+                        Slog.w(TAG, "Can't support task that doesn't support multi-window mode in"
+                                + " multi-window mode... newParent=" + newParent + " task=" + task);
+                        return 0;
+                    } else {
+                        // Clear the window crop on root task since it may not be updated after
+                        // reparent (no longer be a root task)
+                        task.getSurfaceControl().setWindowCrop(null);
+                        task.reparent((ActivityStack) newParent,
+                                hop.getToTop() ? POSITION_TOP : POSITION_BOTTOM,
+                                false /*moveParents*/, "sanitizeAndApplyHierarchyOp");
                     }
-                    if (newParent != null) {
-                        if (!as.affectedBySplitScreenResize()) {
-                            return 0;
-                        }
-                        newParent.addChild(as, POSITION_TOP);
-                    }
-                }
-                if (hop.getToTop()) {
-                    as.getDisplay().positionStackAtTop(as, false /* includingParents */);
                 } else {
-                    as.getDisplay().positionStackAtBottom(as);
+                    final ActivityStack rootTask =
+                            (ActivityStack) (newParent != null ? newParent : task.getRootTask());
+                    if (hop.getToTop()) {
+                        as.getDisplay().positionStackAtTop(rootTask, false /* includingParents */);
+                    } else {
+                        as.getDisplay().positionStackAtBottom(rootTask);
+                    }
                 }
-            } else if (container instanceof Task) {
+            } else {
                 throw new RuntimeException("Reparenting leaf Tasks is not supported now.");
             }
         } else {
             // Ugh, of course ActivityStack has its own special reorder logic...
-            if (container instanceof ActivityStack && ((ActivityStack) container).isRootTask()) {
-                ActivityStack as = (ActivityStack) container;
+            if (task.isRootTask()) {
                 if (hop.getToTop()) {
-                    as.getDisplay().positionStackAtTop(as, false /* includingParents */);
+                    dc.positionStackAtTop(as, false /* includingParents */);
                 } else {
-                    as.getDisplay().positionStackAtBottom(as);
+                    dc.positionStackAtBottom(as);
                 }
             } else {
-                container.getParent().positionChildAt(
+                task.getParent().positionChildAt(
                         hop.getToTop() ? POSITION_TOP : POSITION_BOTTOM,
-                        container, false /* includingParents */);
+                        task, false /* includingParents */);
             }
         }
         return TRANSACT_EFFECTS_LIFECYCLE;
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 161152b..0338cc3 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -131,6 +131,7 @@
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowManagerService.H.WINDOW_STATE_BLAST_SYNC_TIMEOUT;
 import static com.android.server.wm.WindowManagerService.MAX_ANIMATION_DURATION;
 import static com.android.server.wm.WindowManagerService.TYPE_LAYER_MULTIPLIER;
 import static com.android.server.wm.WindowManagerService.TYPE_LAYER_OFFSET;
@@ -138,7 +139,6 @@
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_REMOVING_FOCUS;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
 import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_TIMEOUT;
-import static com.android.server.wm.WindowManagerService.H.WINDOW_STATE_BLAST_SYNC_TIMEOUT;
 import static com.android.server.wm.WindowStateAnimator.COMMIT_DRAW_PENDING;
 import static com.android.server.wm.WindowStateAnimator.DRAW_PENDING;
 import static com.android.server.wm.WindowStateAnimator.HAS_DRAWN;
@@ -3338,7 +3338,7 @@
         }
 
         final ActivityStack stack = task.getStack();
-        if (stack == null) {
+        if (stack == null || stack.mCreatedByOrganizer) {
             return;
         }
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 023a1e8..09fab3e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -4434,6 +4434,11 @@
                     clearDeviceOwnerLocked(getDeviceOwnerAdminLocked(), userHandle);
                 }
                 if (isProfileOwner(adminReceiver, userHandle)) {
+                    if (isProfileOwnerOfOrganizationOwnedDevice(userHandle)) {
+                        mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE,
+                                false,
+                                UserHandle.of(getProfileParentId(userHandle)));
+                    }
                     final ActiveAdmin admin = getActiveAdminUncheckedLocked(adminReceiver,
                             userHandle, /* parent */ false);
                     clearProfileOwnerLocked(admin, userHandle);
@@ -13070,26 +13075,25 @@
             final boolean addingProfileRestricted = mUserManager.hasUserRestriction(
                     UserManager.DISALLOW_ADD_MANAGED_PROFILE, callingUserHandle);
 
-            UserInfo parentUser = mUserManager.getProfileParent(callingUserId);
-            final boolean addingProfileRestrictedOnParent = (parentUser != null)
-                    && mUserManager.hasUserRestriction(
-                            UserManager.DISALLOW_ADD_MANAGED_PROFILE,
-                            UserHandle.of(parentUser.id));
+            if (mUserManager.getUserInfo(callingUserId).isProfile()) {
+                Slog.i(LOG_TAG,
+                        String.format("Calling user %d is a profile, cannot add another.",
+                                callingUserId));
+                // The check is called from inside a managed profile. A managed profile cannot
+                // be provisioned from within another managed profile.
+                return CODE_CANNOT_ADD_MANAGED_PROFILE;
+            }
 
-            Slog.i(LOG_TAG, String.format(
-                    "When checking for managed profile provisioning: Has device owner? %b, adding"
-                            + " profile restricted? %b, adding profile restricted on parent? %b",
-                    hasDeviceOwner, addingProfileRestricted, addingProfileRestrictedOnParent));
-
-            // If there's a device owner, the restriction on adding a managed profile must be set
-            // somewhere.
-            if (hasDeviceOwner && !addingProfileRestricted && !addingProfileRestrictedOnParent) {
+            // If there's a device owner, the restriction on adding a managed profile must be set.
+            if (hasDeviceOwner && !addingProfileRestricted) {
                 Slog.wtf(LOG_TAG, "Has a device owner but no restriction on adding a profile.");
             }
 
-            // Do not allow adding a managed profile if there's a restriction, either on the current
-            // user or its parent user.
-            if (addingProfileRestricted || addingProfileRestrictedOnParent) {
+            // Do not allow adding a managed profile if there's a restriction.
+            if (addingProfileRestricted) {
+                Slog.i(LOG_TAG, String.format(
+                        "Adding a profile is restricted: User %s Has device owner? %b",
+                        callingUserHandle, hasDeviceOwner));
                 return CODE_CANNOT_ADD_MANAGED_PROFILE;
             }
             // If there's a restriction on removing the managed profile then we have to take it
@@ -13098,6 +13102,8 @@
                     !mUserManager.hasUserRestriction(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE,
                     callingUserHandle);
             if (!mUserManager.canAddMoreManagedProfiles(callingUserId, canRemoveProfile)) {
+                Slog.i(LOG_TAG, String.format(
+                        "Cannot add more profiles: Can remove current? %b", canRemoveProfile));
                 return CODE_CANNOT_ADD_MANAGED_PROFILE;
             }
         } finally {
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 7c6ac17..baf551e 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -3205,6 +3205,7 @@
         when(getServices().userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, true))
                 .thenReturn(true);
         setUserSetupCompleteForUser(false, UserHandle.USER_SYSTEM);
+        when(getServices().userManager.getProfileParent(UserHandle.USER_SYSTEM)).thenReturn(null);
 
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
     }
@@ -3246,6 +3247,7 @@
         when(getServices().userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, true))
                 .thenReturn(true);
         setUserSetupCompleteForUser(true, UserHandle.USER_SYSTEM);
+        when(getServices().userManager.getProfileParent(UserHandle.USER_SYSTEM)).thenReturn(null);
 
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
     }
@@ -3617,14 +3619,14 @@
 
         when(getServices().ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0))
                 .thenReturn(true);
-        when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(true);
+        when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(false);
         when(getServices().userManager.getProfileParent(DpmMockContext.CALLER_USER_HANDLE))
             .thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0));
         when(getServices().userManager.canAddMoreManagedProfiles(DpmMockContext.CALLER_USER_HANDLE,
                 true)).thenReturn(true);
         setUserSetupCompleteForUser(false, DpmMockContext.CALLER_USER_HANDLE);
 
-        mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+        mContext.binder.callingUid = DpmMockContext.ANOTHER_UID;
     }
 
     public void testIsProvisioningAllowed_provisionManagedProfileWithDeviceOwner_primaryUser()
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 6ec0f91..ed400ea 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -82,9 +82,8 @@
 import android.platform.test.annotations.Presubmit;
 import android.service.voice.IVoiceInteractionSession;
 import android.view.Gravity;
-import android.window.IWindowContainer;
-import android.view.SurfaceControl;
 import android.window.ITaskOrganizer;
+import android.window.IWindowContainer;
 
 import androidx.test.filters.SmallTest;
 
@@ -975,7 +974,7 @@
 
         // Move activity to split-screen-primary stack and make sure it has the focus.
         TestSplitOrganizer splitOrg = new TestSplitOrganizer(mService, top.getDisplayId());
-        splitOrg.mPrimary.addChild(top.getRootTask(), 0 /* index */);
+        top.getRootTask().reparent(splitOrg.mPrimary, POSITION_BOTTOM);
         top.getRootTask().moveToFront("testWindowingModeOptionsLaunchAdjacent");
 
         // Activity must landed on split-screen-secondary when launch adjacent.
@@ -1001,8 +1000,8 @@
 
     static class TestSplitOrganizer extends ITaskOrganizer.Stub {
         final ActivityTaskManagerService mService;
-        TaskTile mPrimary;
-        TaskTile mSecondary;
+        Task mPrimary;
+        Task mSecondary;
         boolean mInSplit = false;
         int mDisplayId;
         TestSplitOrganizer(ActivityTaskManagerService service, int displayId) {
@@ -1014,10 +1013,10 @@
                     WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
             IWindowContainer primary = mService.mTaskOrganizerController.createRootTask(
                     displayId, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY).token;
-            mPrimary = TaskTile.forToken(primary.asBinder());
+            mPrimary = WindowContainer.fromBinder(primary.asBinder()).asTask();
             IWindowContainer secondary = mService.mTaskOrganizerController.createRootTask(
                     displayId, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY).token;
-            mSecondary = TaskTile.forToken(secondary.asBinder());
+            mSecondary = WindowContainer.fromBinder(secondary.asBinder()).asTask();
         }
         @Override
         public void onTaskAppeared(ActivityManager.RunningTaskInfo info) {
@@ -1042,7 +1041,7 @@
                     for (int i = dc.getStackCount() - 1; i >= 0; --i) {
                         if (!WindowConfiguration.isSplitScreenWindowingMode(
                                 dc.getStackAt(i).getWindowingMode())) {
-                            mSecondary.addChild(dc.getStackAt(i), 0);
+                            dc.getStackAt(i).reparent(mSecondary, POSITION_BOTTOM);
                         }
                     }
                 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
index 4634e2d..716369d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
@@ -464,7 +464,7 @@
         ActivityStack build() {
             final int stackId = mStackId >= 0 ? mStackId : mDisplay.getNextStackId();
             final ActivityStack stack = mDisplay.createStackUnchecked(mWindowingMode,
-                    mActivityType, stackId, mOnTop, mInfo, mIntent);
+                    mActivityType, stackId, mOnTop, mInfo, mIntent, false /* createdByOrganizer */);
             final ActivityStackSupervisor supervisor = mRootWindowContainer.mStackSupervisor;
 
             if (mCreateActivity) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
index a96f401..ed635ce 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
@@ -37,6 +37,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.WindowContainer.POSITION_TOP;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -347,45 +348,62 @@
         assertEquals(ACTIVITY_TYPE_UNDEFINED, info2.topActivityType);
 
         DisplayContent dc = mWm.mRoot.getDisplayContent(Display.DEFAULT_DISPLAY);
-        List<TaskTile> infos = getTaskTiles(dc);
+        List<Task> infos = getTasksCreatedByOrganizer(dc);
         assertEquals(2, infos.size());
 
         assertTrue(mWm.mAtmService.mTaskOrganizerController.deleteRootTask(info1.token));
-        infos = getTaskTiles(dc);
+        infos = getTasksCreatedByOrganizer(dc);
         assertEquals(1, infos.size());
         assertEquals(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, infos.get(0).getWindowingMode());
     }
 
     @Test
     public void testTileAddRemoveChild() {
+        ITaskOrganizer listener = new ITaskOrganizer.Stub() {
+            @Override
+            public void onTaskAppeared(RunningTaskInfo taskInfo) { }
+
+            @Override
+            public void onTaskVanished(RunningTaskInfo container) { }
+
+            @Override
+            public void onTaskInfoChanged(RunningTaskInfo info) throws RemoteException {
+            }
+        };
+        mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(listener,
+                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
         RunningTaskInfo info1 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
                 mDisplayContent.mDisplayId, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
 
         final ActivityStack stack = createTaskStackOnDisplay(
                 WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, mDisplayContent);
         assertEquals(mDisplayContent.getWindowingMode(), stack.getWindowingMode());
-        TaskTile tile1 = TaskTile.forToken(info1.token.asBinder());
-        tile1.addChild(stack, 0 /* index */);
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        wct.reparent(stack.mRemoteToken, info1.token, true /* onTop */);
+        mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
         assertEquals(info1.configuration.windowConfiguration.getWindowingMode(),
                 stack.getWindowingMode());
 
         // Info should reflect new membership
-        List<TaskTile> tiles = getTaskTiles(mDisplayContent);
-        info1 = tiles.get(0).getTaskInfo();
+        List<Task> infos = getTasksCreatedByOrganizer(mDisplayContent);
+        info1 = infos.get(0).getTaskInfo();
         assertEquals(ACTIVITY_TYPE_STANDARD, info1.topActivityType);
 
         // Children inherit configuration
         Rect newSize = new Rect(10, 10, 300, 300);
-        Configuration c = new Configuration(tile1.getRequestedOverrideConfiguration());
+        Task task1 = WindowContainer.fromBinder(info1.token.asBinder()).asTask();
+        Configuration c = new Configuration(task1.getRequestedOverrideConfiguration());
         c.windowConfiguration.setBounds(newSize);
         doNothing().when(stack).adjustForMinimalTaskDimensions(any(), any());
-        tile1.onRequestedOverrideConfigurationChanged(c);
+        task1.onRequestedOverrideConfigurationChanged(c);
         assertEquals(newSize, stack.getBounds());
 
-        tile1.removeChild(stack);
+        wct = new WindowContainerTransaction();
+        wct.reparent(stack.mRemoteToken, null, true /* onTop */);
+        mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
         assertEquals(mDisplayContent.getWindowingMode(), stack.getWindowingMode());
-        tiles = getTaskTiles(mDisplayContent);
-        info1 = tiles.get(0).getTaskInfo();
+        infos = getTasksCreatedByOrganizer(mDisplayContent);
+        info1 = infos.get(0).getTaskInfo();
         assertEquals(ACTIVITY_TYPE_UNDEFINED, info1.topActivityType);
     }
 
@@ -415,8 +433,10 @@
 
         final ActivityStack stack = createTaskStackOnDisplay(
                 WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, mDisplayContent);
-        TaskTile tile1 = TaskTile.forToken(info1.token.asBinder());
-        tile1.addChild(stack, 0 /* index */);
+        Task task1 = WindowContainer.fromBinder(info1.token.asBinder()).asTask();
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        wct.reparent(stack.mRemoteToken, info1.token, true /* onTop */);
+        mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
         assertTrue(called[0]);
         assertEquals(ACTIVITY_TYPE_STANDARD, lastReportedTiles.get(0).topActivityType);
 
@@ -424,19 +444,24 @@
         called[0] = false;
         final ActivityStack stack2 = createTaskStackOnDisplay(
                 WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, mDisplayContent);
-        tile1.addChild(stack2, 0 /* index */);
+        wct = new WindowContainerTransaction();
+        wct.reparent(stack2.mRemoteToken, info1.token, true /* onTop */);
+        mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
         assertTrue(called[0]);
         assertEquals(ACTIVITY_TYPE_HOME, lastReportedTiles.get(0).topActivityType);
 
         lastReportedTiles.clear();
         called[0] = false;
-        mDisplayContent.positionStackAtTop(stack, false /* includingParents */);
+        task1.positionChildAt(POSITION_TOP, stack, false /* includingParents */);
         assertTrue(called[0]);
         assertEquals(ACTIVITY_TYPE_STANDARD, lastReportedTiles.get(0).topActivityType);
 
         lastReportedTiles.clear();
         called[0] = false;
-        tile1.removeAllChildren();
+        wct = new WindowContainerTransaction();
+        wct.reparent(stack.mRemoteToken, null, true /* onTop */);
+        wct.reparent(stack2.mRemoteToken, null, true /* onTop */);
+        mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
         assertTrue(called[0]);
         assertEquals(ACTIVITY_TYPE_UNDEFINED, lastReportedTiles.get(0).topActivityType);
     }
@@ -457,9 +482,11 @@
             }
         };
         mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(
+                listener, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+        mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(
                 listener, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
         RunningTaskInfo info1 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
-                mDisplayContent.mDisplayId, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+                mDisplayContent.mDisplayId, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
         RunningTaskInfo info2 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
                 mDisplayContent.mDisplayId, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
 
@@ -522,13 +549,11 @@
                 lastReportedTiles.get(info1.token.asBinder()).topActivityType);
     }
 
-    private List<TaskTile> getTaskTiles(DisplayContent dc) {
-        ArrayList<TaskTile> out = new ArrayList<>();
+    private List<Task> getTasksCreatedByOrganizer(DisplayContent dc) {
+        ArrayList<Task> out = new ArrayList<>();
         for (int i = dc.getStackCount() - 1; i >= 0; --i) {
-            final TaskTile t = dc.getStackAt(i).asTile();
-            if (t != null) {
-                out.add(t);
-            }
+            final Task t = dc.getStackAt(i);
+            if (t.mCreatedByOrganizer) out.add(t);
         }
         return out;
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
index 5848295..56c19a4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
@@ -152,7 +152,7 @@
             assertFalse(factory.mCreated);
 
             Task.create(mService, 0 /*taskId*/, 0 /*activityType*/,
-                    new ActivityInfo(), new Intent());
+                    new ActivityInfo(), new Intent(), false /* createdByOrganizer */);
 
             assertTrue(factory.mCreated);
         } finally {
@@ -1015,7 +1015,7 @@
 
         @Override
         Task create(ActivityTaskManagerService service, int taskId, int activityType,
-                ActivityInfo info, Intent intent) {
+                ActivityInfo info, Intent intent, boolean createdByOrganizer) {
             mCreated = true;
             return null;
         }
diff --git a/tests/RollbackTest/RollbackTest.xml b/tests/RollbackTest/RollbackTest.xml
index 269cec1..7b85cc8 100644
--- a/tests/RollbackTest/RollbackTest.xml
+++ b/tests/RollbackTest/RollbackTest.xml
@@ -23,6 +23,10 @@
         <option name="run-command" value="am broadcast -a 'com.google.android.gms.phenotype.FLAG_OVERRIDE' --es package &quot;com.google.android.gms.platformconfigurator&quot; --es user '\\*' --esa flags &quot;ModuleConfig__versioned_immediate_commit_packages&quot; --esa types &quot;bytes&quot; --esa values &quot;Cm5vdGFwYWNrYWdlOgA=&quot; com.google.android.gms" />
         <option name="teardown-command" value="am broadcast -a 'com.google.android.gms.phenotype.FLAG_OVERRIDE' --es action delete --es package &quot;com.google.android.gms.platformconfigurator&quot; --es user '\*' --esa flag &quot;ModuleConfig__immediate_commit_packages&quot; com.google.android.gms" />
         <option name="teardown-command" value="am broadcast -a 'com.google.android.gms.phenotype.FLAG_OVERRIDE' --es action delete --es package &quot;com.google.android.gms.platformconfigurator&quot; --es user '\*' --esa flag &quot;ModuleConfig__versioned_immediate_commit_packages&quot; com.google.android.gms" />
+        <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.A" />
+        <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.B" />
+        <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.A" />
+        <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.B" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="com.android.tests.rollback" />
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
index 5a92d68..cab8b42 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
@@ -75,6 +75,12 @@
     private static final String PROPERTY_ENABLE_ROLLBACK_TIMEOUT_MILLIS =
             "enable_rollback_timeout";
 
+    private static boolean hasRollbackInclude(List<RollbackInfo> rollbacks, String packageName) {
+        return rollbacks.stream().anyMatch(
+                ri -> ri.getPackages().stream().anyMatch(
+                        pri -> packageName.equals(pri.getPackageName())));
+    }
+
     /**
      * Test basic rollbacks.
      */
@@ -113,18 +119,14 @@
             // Uninstall TestApp.A
             Uninstall.packages(TestApp.A);
             assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
-            // TODO: There is currently a race condition between when the app is
-            // uninstalled and when rollback manager deletes the rollback. Fix it
-            // so that's not the case!
             for (int i = 0; i < 5; ++i) {
-                RollbackInfo rollback = getUniqueRollbackInfoForPackage(
-                        rm.getRecentlyCommittedRollbacks(), TestApp.A);
-                if (rollback != null) {
+                if (hasRollbackInclude(rm.getRecentlyCommittedRollbacks(), TestApp.A)) {
                     Log.i(TAG, "Sleeping 1 second to wait for uninstall to take effect.");
                     Thread.sleep(1000);
                 }
             }
 
+            assertThat(hasRollbackInclude(rm.getRecentlyCommittedRollbacks(), TestApp.A)).isFalse();
             // The app should not be available for rollback.
             waitForUnavailableRollback(TestApp.A);
 
diff --git a/wifi/Android.bp b/wifi/Android.bp
index 9d9a495..d0f1a26 100644
--- a/wifi/Android.bp
+++ b/wifi/Android.bp
@@ -170,24 +170,23 @@
 java_library {
     name: "framework-wifi-stubs-publicapi",
     srcs: [":framework-wifi-stubs-srcs-publicapi"],
+    defaults: ["framework-module-stubs-lib-defaults-publicapi"],
+    // TODO(b/151134996): remove this
     sdk_version: "current",
-    installable: false,
 }
 
 java_library {
     name: "framework-wifi-stubs-systemapi",
     srcs: [":framework-wifi-stubs-srcs-systemapi"],
-    sdk_version: "system_current",
     libs: ["framework-annotations-lib"],
-    installable: false,
+    defaults: ["framework-module-stubs-lib-defaults-systemapi"],
 }
 
 java_library {
     name: "framework-wifi-stubs-module_libs_api",
     srcs: [":framework-wifi-stubs-srcs-module_libs_api"],
-    sdk_version: "module_current",
     libs: ["framework-annotations-lib"],
-    installable: false,
+    defaults: ["framework-module-stubs-lib-defaults-module_libs_api"],
 }
 
 // defaults for tests that need to build against framework-wifi's @hide APIs