Merge "Deprecates the animatingBounds from WM to SysUI" into rvc-dev
diff --git a/Android.bp b/Android.bp
index d4ca706..03a6af5 100644
--- a/Android.bp
+++ b/Android.bp
@@ -339,9 +339,7 @@
             "sax/java",
             "telecomm/java",
 
-            // TODO(b/148660295): remove this
-            "apex/media/framework/java",
-
+            "apex/media/aidl/stable",
             // TODO(b/147699819): remove this
             "telephony/java",
         ],
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 60f6174..f06f279 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -256,18 +256,16 @@
 /////////////////////////////////////////////////////////////////////
 
 java_defaults {
-    name: "framework-stubs-default",
+    name: "android_defaults_stubs_current",
     libs: [ "stub-annotations" ],
     static_libs: [ "private-stub-annotations-jar" ],
-    sdk_version: "core_current",
     errorprone: {
         javacflags: [
             "-XepDisableAllChecks",
         ],
     },
-    java_resources: [
-        ":notices-for-framework-stubs",
-    ],
+    java_resources: [":notices-for-framework-stubs"],
+    sdk_version: "none",
     system_modules: "none",
     java_version: "1.8",
     compile_dex: true,
@@ -276,25 +274,25 @@
 java_library_static {
     name: "android_stubs_current",
     srcs: [ ":api-stubs-docs" ],
-    defaults: ["framework-stubs-default"],
+    defaults: ["android_defaults_stubs_current"],
 }
 
 java_library_static {
     name: "android_system_stubs_current",
     srcs: [ ":system-api-stubs-docs" ],
-    defaults: ["framework-stubs-default"],
+    defaults: ["android_defaults_stubs_current"],
 }
 
 java_library_static {
     name: "android_test_stubs_current",
     srcs: [ ":test-api-stubs-docs" ],
-    defaults: ["framework-stubs-default"],
+    defaults: ["android_defaults_stubs_current"],
 }
 
 java_library_static {
     name: "android_module_lib_stubs_current",
     srcs: [ ":module-lib-api-stubs-docs" ],
-    defaults: ["framework-stubs-default"],
+    defaults: ["android_defaults_stubs_current"],
     libs: ["android_system_stubs_current"],
 }
 
diff --git a/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java b/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java
index 73b4a19..836e6b6 100644
--- a/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java
+++ b/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java
@@ -192,6 +192,11 @@
                 Assume.assumeNoException(
                         new AssertionError("onAnimationCanceled should not be called"));
             }
+
+            @Override
+            public void onTaskAppeared(RemoteAnimationTarget app) throws RemoteException {
+                /* no-op */
+            }
         };
 
         recentsSemaphore.tryAcquire();
diff --git a/apex/Android.bp b/apex/Android.bp
index 5f418d4..67cd0d7 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -67,7 +67,7 @@
     name: "framework-module-stubs-defaults-publicapi",
     args: mainline_framework_stubs_args,
     installable: false,
-    sdk_version: "current",
+    sdk_version: "module_current",
     filter_packages: framework_packages_to_document,
     check_api: {
         current: {
@@ -86,7 +86,7 @@
     args: mainline_framework_stubs_args + priv_apps,
     libs: ["framework-annotations-lib"],
     installable: false,
-    sdk_version: "system_current",
+    sdk_version: "module_current",
     filter_packages: framework_packages_to_document,
     check_api: {
         current: {
diff --git a/apex/blobstore/framework/java/android/app/blob/XmlTags.java b/apex/blobstore/framework/java/android/app/blob/XmlTags.java
index e64edc3..dbfdcba 100644
--- a/apex/blobstore/framework/java/android/app/blob/XmlTags.java
+++ b/apex/blobstore/framework/java/android/app/blob/XmlTags.java
@@ -48,6 +48,7 @@
 
     // For committer
     public static final String TAG_COMMITTER = "c";
+    public static final String ATTR_COMMIT_TIME_MS = "cmt";
 
     // For leasee
     public static final String TAG_LEASEE = "l";
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
index c8ca44b..49b3ec1 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.blob;
 
+import static android.app.blob.XmlTags.ATTR_COMMIT_TIME_MS;
 import static android.app.blob.XmlTags.ATTR_DESCRIPTION;
 import static android.app.blob.XmlTags.ATTR_DESCRIPTION_RES_NAME;
 import static android.app.blob.XmlTags.ATTR_EXPIRY_TIME;
@@ -30,6 +31,7 @@
 import static android.system.OsConstants.O_RDONLY;
 
 import static com.android.server.blob.BlobStoreConfig.TAG;
+import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_COMMIT_TIME;
 import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_DESC_RES_NAME;
 import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_STRING_DESC;
 import static com.android.server.blob.BlobStoreConfig.hasLeaseWaitTimeElapsed;
@@ -54,6 +56,7 @@
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.XmlUtils;
 import com.android.server.blob.BlobStoreManagerService.DumpArgs;
@@ -125,7 +128,7 @@
         }
     }
 
-    void addCommitters(ArraySet<Committer> committers) {
+    void setCommitters(ArraySet<Committer> committers) {
         synchronized (mMetadataLock) {
             mCommitters.clear();
             mCommitters.addAll(committers);
@@ -153,11 +156,16 @@
     }
 
     @Nullable
-    Committer getExistingCommitter(@NonNull Committer newCommitter) {
+    Committer getExistingCommitter(@NonNull String packageName, int uid) {
         synchronized (mCommitters) {
-            final int index = mCommitters.indexOf(newCommitter);
-            return index >= 0 ? mCommitters.valueAt(index) : null;
+            for (int i = 0, size = mCommitters.size(); i < size; ++i) {
+                final Committer committer = mCommitters.valueAt(i);
+                if (committer.uid == uid && committer.packageName.equals(packageName)) {
+                    return committer;
+                }
+            }
         }
+        return null;
     }
 
     void addOrReplaceLeasee(String callingPackage, int callingUid, int descriptionResId,
@@ -172,7 +180,7 @@
         }
     }
 
-    void addLeasees(ArraySet<Leasee> leasees) {
+    void setLeasees(ArraySet<Leasee> leasees) {
         synchronized (mMetadataLock) {
             mLeasees.clear();
             mLeasees.addAll(leasees);
@@ -380,8 +388,7 @@
         }
 
         // Blobs with no active leases
-        // TODO: Track commit time instead of using last modified time.
-        if ((!respectLeaseWaitTime || hasLeaseWaitTimeElapsed(getBlobFile().lastModified()))
+        if ((!respectLeaseWaitTime || hasLeaseWaitTimeElapsedForAll())
                 && !hasLeases()) {
             return true;
         }
@@ -389,6 +396,17 @@
         return false;
     }
 
+    @VisibleForTesting
+    boolean hasLeaseWaitTimeElapsedForAll() {
+        for (int i = 0, size = mCommitters.size(); i < size; ++i) {
+            final Committer committer = mCommitters.valueAt(i);
+            if (!hasLeaseWaitTimeElapsed(committer.getCommitTimeMs())) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     void dump(IndentingPrintWriter fout, DumpArgs dumpArgs) {
         fout.println("blobHandle:");
         fout.increaseIndent();
@@ -492,20 +510,28 @@
         }
 
         final BlobMetadata blobMetadata = new BlobMetadata(context, blobId, blobHandle, userId);
-        blobMetadata.addCommitters(committers);
-        blobMetadata.addLeasees(leasees);
+        blobMetadata.setCommitters(committers);
+        blobMetadata.setLeasees(leasees);
         return blobMetadata;
     }
 
     static final class Committer extends Accessor {
         public final BlobAccessMode blobAccessMode;
+        public final long commitTimeMs;
 
-        Committer(String packageName, int uid, BlobAccessMode blobAccessMode) {
+        Committer(String packageName, int uid, BlobAccessMode blobAccessMode, long commitTimeMs) {
             super(packageName, uid);
             this.blobAccessMode = blobAccessMode;
+            this.commitTimeMs = commitTimeMs;
+        }
+
+        long getCommitTimeMs() {
+            return commitTimeMs;
         }
 
         void dump(IndentingPrintWriter fout) {
+            fout.println("commit time: "
+                    + (commitTimeMs == 0 ? "<null>" : BlobStoreUtils.formatTime(commitTimeMs)));
             fout.println("accessMode:");
             fout.increaseIndent();
             blobAccessMode.dump(fout);
@@ -515,6 +541,7 @@
         void writeToXml(@NonNull XmlSerializer out) throws IOException {
             XmlUtils.writeStringAttribute(out, ATTR_PACKAGE, packageName);
             XmlUtils.writeIntAttribute(out, ATTR_UID, uid);
+            XmlUtils.writeLongAttribute(out, ATTR_COMMIT_TIME_MS, commitTimeMs);
 
             out.startTag(null, TAG_ACCESS_MODE);
             blobAccessMode.writeToXml(out);
@@ -526,6 +553,9 @@
                 throws XmlPullParserException, IOException {
             final String packageName = XmlUtils.readStringAttribute(in, ATTR_PACKAGE);
             final int uid = XmlUtils.readIntAttribute(in, ATTR_UID);
+            final long commitTimeMs = version >= XML_VERSION_ADD_COMMIT_TIME
+                    ? XmlUtils.readLongAttribute(in, ATTR_COMMIT_TIME_MS)
+                    : 0;
 
             final int depth = in.getDepth();
             BlobAccessMode blobAccessMode = null;
@@ -538,7 +568,7 @@
                 Slog.wtf(TAG, "blobAccessMode should be available");
                 return null;
             }
-            return new Committer(packageName, uid, blobAccessMode);
+            return new Committer(packageName, uid, blobAccessMode, commitTimeMs);
         }
     }
 
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java
index f2c1586..6af1178 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java
@@ -45,8 +45,9 @@
     // Added a string variant of lease description.
     public static final int XML_VERSION_ADD_STRING_DESC = 2;
     public static final int XML_VERSION_ADD_DESC_RES_NAME = 3;
+    public static final int XML_VERSION_ADD_COMMIT_TIME = 4;
 
-    public static final int XML_VERSION_CURRENT = XML_VERSION_ADD_DESC_RES_NAME;
+    public static final int XML_VERSION_CURRENT = XML_VERSION_ADD_COMMIT_TIME;
 
     private static final String ROOT_DIR_NAME = "blobstore";
     private static final String BLOBS_DIR_NAME = "blobs";
@@ -100,6 +101,18 @@
         public static long LEASE_ACQUISITION_WAIT_DURATION_MS =
                 DEFAULT_LEASE_ACQUISITION_WAIT_DURATION_MS;
 
+        /**
+         * Denotes the duration from the time a blob is committed that any new commits of the same
+         * data blob from the same committer will be treated as if they occurred at the earlier
+         * commit time.
+         */
+        public static final String KEY_COMMIT_COOL_OFF_DURATION_MS =
+                "commit_cool_off_duration_ms";
+        public static final long DEFAULT_COMMIT_COOL_OFF_DURATION_MS =
+                TimeUnit.HOURS.toMillis(48);
+        public static long COMMIT_COOL_OFF_DURATION_MS =
+                DEFAULT_COMMIT_COOL_OFF_DURATION_MS;
+
         static void refresh(Properties properties) {
             if (!NAMESPACE_BLOBSTORE.equals(properties.getNamespace())) {
                 return;
@@ -163,6 +176,27 @@
                 < System.currentTimeMillis();
     }
 
+    /**
+     * Returns an adjusted commit time depending on whether commit cool-off period has elapsed.
+     *
+     * If this is the initial commit or the earlier commit cool-off period has elapsed, then
+     * the new commit time is used. Otherwise, the earlier commit time is used.
+     */
+    public static long getAdjustedCommitTimeMs(long oldCommitTimeMs, long newCommitTimeMs) {
+        if (oldCommitTimeMs == 0 || hasCommitCoolOffPeriodElapsed(oldCommitTimeMs)) {
+            return newCommitTimeMs;
+        }
+        return oldCommitTimeMs;
+    }
+
+    /**
+     * Returns whether the commit cool-off period has elapsed.
+     */
+    private static boolean hasCommitCoolOffPeriodElapsed(long commitTimeMs) {
+        return commitTimeMs + DeviceConfigProperties.COMMIT_COOL_OFF_DURATION_MS
+                < System.currentTimeMillis();
+    }
+
     @Nullable
     public static File prepareBlobFile(long sessionId) {
         final File blobsDir = prepareBlobsDir();
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
index e472d05..35a2436 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
@@ -31,6 +31,7 @@
 import static com.android.server.blob.BlobStoreConfig.SESSION_EXPIRY_TIMEOUT_MILLIS;
 import static com.android.server.blob.BlobStoreConfig.TAG;
 import static com.android.server.blob.BlobStoreConfig.XML_VERSION_CURRENT;
+import static com.android.server.blob.BlobStoreConfig.getAdjustedCommitTimeMs;
 import static com.android.server.blob.BlobStoreSession.STATE_ABANDONED;
 import static com.android.server.blob.BlobStoreSession.STATE_COMMITTED;
 import static com.android.server.blob.BlobStoreSession.STATE_VERIFIED_INVALID;
@@ -566,13 +567,18 @@
                             userId);
                     BlobMetadata blob = userBlobs.get(session.getBlobHandle());
                     if (blob == null) {
-                        blob = new BlobMetadata(mContext,
-                                session.getSessionId(), session.getBlobHandle(), userId);
+                        blob = new BlobMetadata(mContext, session.getSessionId(),
+                                session.getBlobHandle(), userId);
                         addBlobForUserLocked(blob, userBlobs);
                     }
+                    final Committer existingCommitter = blob.getExistingCommitter(
+                            session.getOwnerPackageName(), session.getOwnerUid());
+                    final long existingCommitTimeMs =
+                            (existingCommitter == null) ? 0 : existingCommitter.getCommitTimeMs();
                     final Committer newCommitter = new Committer(session.getOwnerPackageName(),
-                            session.getOwnerUid(), session.getBlobAccessMode());
-                    final Committer existingCommitter = blob.getExistingCommitter(newCommitter);
+                            session.getOwnerUid(), session.getBlobAccessMode(),
+                            getAdjustedCommitTimeMs(existingCommitTimeMs,
+                                    System.currentTimeMillis()));
                     blob.addOrReplaceCommitter(newCommitter);
                     try {
                         writeBlobsInfoLocked();
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreUtils.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreUtils.java
index fabce76..1d07e88 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreUtils.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreUtils.java
@@ -24,6 +24,7 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
+import android.text.format.TimeMigrationUtils;
 import android.util.Slog;
 
 class BlobStoreUtils {
@@ -56,4 +57,9 @@
                 ? Resources.ID_NULL
                 : getDescriptionResourceId(resources, resourceEntryName, packageName);
     }
+
+    @NonNull
+    static String formatTime(long timeMs) {
+        return TimeMigrationUtils.formatMillisWithFixedFormat(timeMs);
+    }
 }
diff --git a/apex/media/aidl/Android.bp b/apex/media/aidl/Android.bp
new file mode 100644
index 0000000..409a048
--- /dev/null
+++ b/apex/media/aidl/Android.bp
@@ -0,0 +1,35 @@
+//
+// Copyright 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.
+//
+
+filegroup {
+    name: "stable-mediasession2-aidl-srcs",
+    srcs: ["stable/**/*.aidl"],
+    path: "stable",
+}
+
+filegroup {
+    name: "private-mediasession2-aidl-srcs",
+    srcs: ["private/**/I*.aidl"],
+    path: "private",
+}
+
+filegroup {
+    name: "mediasession2-aidl-srcs",
+    srcs: [
+        ":private-mediasession2-aidl-srcs",
+        ":stable-mediasession2-aidl-srcs",
+    ],
+}
diff --git a/apex/media/framework/java/android/media/Controller2Link.aidl b/apex/media/aidl/private/android/media/Controller2Link.aidl
similarity index 100%
rename from apex/media/framework/java/android/media/Controller2Link.aidl
rename to apex/media/aidl/private/android/media/Controller2Link.aidl
diff --git a/apex/media/framework/java/android/media/IMediaController2.aidl b/apex/media/aidl/private/android/media/IMediaController2.aidl
similarity index 100%
rename from apex/media/framework/java/android/media/IMediaController2.aidl
rename to apex/media/aidl/private/android/media/IMediaController2.aidl
diff --git a/apex/media/framework/java/android/media/IMediaSession2.aidl b/apex/media/aidl/private/android/media/IMediaSession2.aidl
similarity index 100%
rename from apex/media/framework/java/android/media/IMediaSession2.aidl
rename to apex/media/aidl/private/android/media/IMediaSession2.aidl
diff --git a/apex/media/framework/java/android/media/IMediaSession2Service.aidl b/apex/media/aidl/private/android/media/IMediaSession2Service.aidl
similarity index 100%
rename from apex/media/framework/java/android/media/IMediaSession2Service.aidl
rename to apex/media/aidl/private/android/media/IMediaSession2Service.aidl
diff --git a/apex/media/framework/java/android/media/Session2Command.aidl b/apex/media/aidl/private/android/media/Session2Command.aidl
similarity index 100%
rename from apex/media/framework/java/android/media/Session2Command.aidl
rename to apex/media/aidl/private/android/media/Session2Command.aidl
diff --git a/apex/media/framework/java/android/media/Session2Token.aidl b/apex/media/aidl/stable/android/media/Session2Token.aidl
similarity index 100%
rename from apex/media/framework/java/android/media/Session2Token.aidl
rename to apex/media/aidl/stable/android/media/Session2Token.aidl
diff --git a/apex/media/framework/Android.bp b/apex/media/framework/Android.bp
index 579963b..34fe228 100644
--- a/apex/media/framework/Android.bp
+++ b/apex/media/framework/Android.bp
@@ -55,17 +55,15 @@
     name: "updatable-media-srcs",
     srcs: [
         ":mediaparser-srcs",
-        ":mediasession2-srcs",
+        ":mediasession2-java-srcs",
+        ":mediasession2-aidl-srcs",
     ],
 }
 
 filegroup {
-    name: "mediasession2-srcs",
+    name: "mediasession2-java-srcs",
     srcs: [
         "java/android/media/Controller2Link.java",
-        "java/android/media/IMediaController2.aidl",
-        "java/android/media/IMediaSession2.aidl",
-        "java/android/media/IMediaSession2Service.aidl",
         "java/android/media/MediaConstants.java",
         "java/android/media/MediaController2.java",
         "java/android/media/MediaSession2.java",
@@ -83,7 +81,7 @@
     srcs: [
         "java/android/media/MediaParser.java"
     ],
-    path: "java"
+    path: "java",
 }
 
 stubs_defaults {
diff --git a/apex/statsd/aidl/android/os/IStatsd.aidl b/apex/statsd/aidl/android/os/IStatsd.aidl
index d5b5949..80308d2 100644
--- a/apex/statsd/aidl/android/os/IStatsd.aidl
+++ b/apex/statsd/aidl/android/os/IStatsd.aidl
@@ -31,6 +31,11 @@
     oneway void systemRunning();
 
     /**
+     * Tell the stats daemon that the android system has finished booting.
+     */
+    oneway void bootCompleted();
+
+    /**
      * Tell the stats daemon that the StatsCompanionService is up and running.
      * Two-way binder call so that caller knows message received.
      */
@@ -182,10 +187,15 @@
      */
     void sendAppBreadcrumbAtom(int label, int state);
 
-   /**
-    * Registers a puller callback function that, when invoked, pulls the data
-    * for the specified atom tag.
-    */
+    /**
+     * Tell the stats daemon that all the pullers registered during boot have been sent.
+     */
+    oneway void allPullersFromBootRegistered();
+
+    /**
+     * Registers a puller callback function that, when invoked, pulls the data
+     * for the specified atom tag.
+     */
     oneway void registerPullAtomCallback(int uid, int atomTag, long coolDownMillis,
                                          long timeoutMillis,in int[] additiveFields,
                                          IPullAtomCallback pullerCallback);
diff --git a/apex/statsd/service/java/com/android/server/stats/StatsCompanion.java b/apex/statsd/service/java/com/android/server/stats/StatsCompanion.java
index c1ba73f..dc477a5 100644
--- a/apex/statsd/service/java/com/android/server/stats/StatsCompanion.java
+++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanion.java
@@ -87,6 +87,9 @@
             if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
                 mStatsCompanionService.systemReady();
             }
+            if (phase == PHASE_BOOT_COMPLETED) {
+                mStatsCompanionService.bootCompleted();
+            }
         }
     }
 
diff --git a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
index 66e41cc..ce5309e 100644
--- a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
+++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
@@ -112,6 +112,18 @@
     private final HashMap<Long, String> mDeletedFiles = new HashMap<>();
     private final CompanionHandler mHandler;
 
+    // Flag that is set when PHASE_BOOT_COMPLETED is triggered in the StatsCompanion lifecycle. This
+    // and the flag mSentBootComplete below is used for synchronization to ensure that the boot
+    // complete signal is only ever sent once to statsd. Two signals are needed because
+    // #sayHiToStatsd can be called from both statsd and #onBootPhase
+    // PHASE_THIRD_PARTY_APPS_CAN_START.
+    @GuardedBy("sStatsdLock")
+    private boolean mBootCompleted = false;
+    // Flag that is set when IStatsd#bootCompleted is called. This flag ensures that boot complete
+    // signal is only ever sent once.
+    @GuardedBy("sStatsdLock")
+    private boolean mSentBootComplete = false;
+
     public StatsCompanionService(Context context) {
         super();
         mContext = context;
@@ -688,6 +700,19 @@
                     List.of(appUpdateReceiver, userUpdateReceiver, shutdownEventReceiver));
 
             final long token = Binder.clearCallingIdentity();
+
+            // Used so we can call statsd.bootComplete() outside of the lock.
+            boolean shouldSendBootComplete = false;
+            synchronized (sStatsdLock) {
+                if (mBootCompleted && !mSentBootComplete) {
+                    mSentBootComplete = true;
+                    shouldSendBootComplete = true;
+                }
+            }
+            if (shouldSendBootComplete) {
+                statsd.bootCompleted();
+            }
+
             try {
                 // Pull the latest state of UID->app name, version mapping when
                 // statsd starts.
@@ -749,6 +774,7 @@
                     mContext.unregisterReceiver(receiver);
                 }
                 statsdNotReadyLocked();
+                mSentBootComplete = false;
             }
         }
     }
@@ -758,6 +784,28 @@
         mStatsManagerService.statsdNotReady();
     }
 
+    void bootCompleted() {
+        IStatsd statsd = getStatsdNonblocking();
+        synchronized (sStatsdLock) {
+            mBootCompleted = true;
+            if (mSentBootComplete) {
+                // do not send a boot complete a second time.
+                return;
+            }
+            if (statsd == null) {
+                // Statsd is not yet ready.
+                // Delay the boot completed ping to {@link #sayHiToStatsd()}
+                return;
+            }
+            mSentBootComplete = true;
+        }
+        try {
+            statsd.bootCompleted();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to notify statsd that boot completed");
+        }
+    }
+
     @Override
     protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
diff --git a/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java b/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java
index 58c78da..90764b0 100644
--- a/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java
+++ b/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java
@@ -600,6 +600,7 @@
             statsd.registerPullAtomCallback(key.getUid(), key.getAtom(), value.getCoolDownMillis(),
                     value.getTimeoutMillis(), value.getAdditiveFields(), value.getCallback());
         }
+        statsd.allPullersFromBootRegistered();
     }
 
     // Pre-condition: the Binder calling identity has already been cleared
diff --git a/cmds/idmap2/TEST_MAPPING b/cmds/idmap2/TEST_MAPPING
index 26ccf03..9e0fb84 100644
--- a/cmds/idmap2/TEST_MAPPING
+++ b/cmds/idmap2/TEST_MAPPING
@@ -3,5 +3,10 @@
     {
       "name" : "idmap2_tests"
     }
+  ],
+  "imports": [
+    {
+      "path": "frameworks/base/services/core/java/com/android/server/om"
+    }
   ]
 }
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp
index e55ea6c..75fc7f7 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.cpp
+++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp
@@ -149,15 +149,21 @@
     return error(idmap.GetErrorMessage());
   }
 
+  // idmap files are mapped with mmap in libandroidfw. Deleting and recreating the idmap guarantees
+  // that existing memory maps will continue to be valid and unaffected.
+  unlink(idmap_path.c_str());
+
   umask(kIdmapFilePermissionMask);
   std::ofstream fout(idmap_path);
   if (fout.fail()) {
     return error("failed to open idmap path " + idmap_path);
   }
+
   BinaryStreamVisitor visitor(fout);
   (*idmap)->accept(&visitor);
   fout.close();
   if (fout.fail()) {
+    unlink(idmap_path.c_str());
     return error("failed to write to idmap path " + idmap_path);
   }
 
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 9169eb17..dd1d400 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -1054,6 +1054,14 @@
     return Status::ok();
 }
 
+Status StatsService::bootCompleted() {
+    ENFORCE_UID(AID_SYSTEM);
+
+    VLOG("StatsService::bootCompleted was called");
+
+    return Status::ok();
+}
+
 void StatsService::Startup() {
     mConfigManager->Startup();
     mProcessor->LoadActiveConfigsFromDisk();
@@ -1215,6 +1223,14 @@
     return Status::ok();
 }
 
+Status StatsService::allPullersFromBootRegistered() {
+    ENFORCE_UID(AID_SYSTEM);
+
+    VLOG("StatsService::allPullersFromBootRegistered was called");
+
+    return Status::ok();
+}
+
 Status StatsService::registerPullAtomCallback(int32_t uid, int32_t atomTag, int64_t coolDownMillis,
                                               int64_t timeoutMillis,
                                               const std::vector<int32_t>& additiveFields,
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index 114c84f..23d4c1b 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -64,6 +64,7 @@
 
     virtual Status systemRunning();
     virtual Status statsCompanionReady();
+    virtual Status bootCompleted();
     virtual Status informAnomalyAlarmFired();
     virtual Status informPollAlarmFired();
     virtual Status informAlarmForSubscriberTriggeringFired();
@@ -165,6 +166,11 @@
     virtual Status sendAppBreadcrumbAtom(int32_t label, int32_t state) override;
 
     /**
+     * Binder call to notify statsd that all pullers from boot have been registered.
+     */
+    virtual Status allPullersFromBootRegistered();
+
+    /**
      * Binder call to register a callback function for a pulled atom.
      */
     virtual Status registerPullAtomCallback(
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 38b421f..453ddeb 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -384,12 +384,12 @@
         PerfettoUploaded perfetto_uploaded = 229 [(module) = "perfetto"];
         VmsClientConnectionStateChanged vms_client_connection_state_changed =
                 230 [(module) = "car"];
-        MediaProviderScanEvent media_provider_scan_event = 233 [(module) = "mediaprovider"];
-        MediaProviderDeletionEvent media_provider_deletion_event = 234 [(module) = "mediaprovider"];
-        MediaProviderPermissionEvent media_provider_permission_event =
+        MediaProviderScanOccurred media_provider_scan_occurred = 233 [(module) = "mediaprovider"];
+        MediaContentDeleted media_content_deleted = 234 [(module) = "mediaprovider"];
+        MediaProviderPermissionRequested media_provider_permission_requested =
             235 [(module) = "mediaprovider"];
-        MediaProviderSchemaChange media_provider_schema_change = 236 [(module) = "mediaprovider"];
-        MediaProviderIdleMaintenance media_provider_idle_maintenance =
+        MediaProviderSchemaChanged media_provider_schema_changed = 236 [(module) = "mediaprovider"];
+        MediaProviderIdleMaintenanceFinished media_provider_idle_maintenance_finished =
             237 [(module) = "mediaprovider"];
         RebootEscrowRecoveryReported reboot_escrow_recovery_reported = 238 [(module) = "framework"];
         BootTimeEventDuration boot_time_event_duration_reported = 239 [(module) = "framework"];
@@ -4456,7 +4456,7 @@
  * Logged from:
  *   packages/providers/MediaProvider/src/com/android/providers/media/scan/ModernMediaScanner.java
  */
-message MediaProviderScanEvent {
+message MediaProviderScanOccurred {
     enum Reason {
         // Scan triggered due to unknown reason
         UNKNOWN = 0;
@@ -4490,15 +4490,13 @@
  * Logged from:
  *   packages/providers/MediaProvider/src/com/android/providers/media/MediaProvider.java
  */
-message MediaProviderDeletionEvent {
+message MediaContentDeleted {
     // Volume type that this event pertains to
     optional android.stats.mediaprovider.VolumeType volume_type = 1;
-    // Device timestamp when this deletion event occurred
-    optional int64 timestamp_millis = 2;
-    // App that requested deletion
-    optional string package_name = 3;
+    // UID of app that requested deletion
+    optional int32 uid = 2 [(is_uid) = true];
     // Number of items that were deleted
-    optional int32 item_count = 4;
+    optional int32 item_count = 3;
 }
 
 /**
@@ -4507,7 +4505,7 @@
  * Logged from:
  *   packages/providers/MediaProvider/src/com/android/providers/media/PermissionActivity.java
  */
-message MediaProviderPermissionEvent {
+message MediaProviderPermissionRequested {
     enum Result {
         UNKNOWN = 0;
         USER_GRANTED = 1;
@@ -4519,14 +4517,12 @@
 
     // Volume type that this event pertains to
     optional android.stats.mediaprovider.VolumeType volume_type = 1;
-    // Device timestamp when this permission event occurred
-    optional int64 timestamp_millis = 2;
-    // App that requested permission
-    optional string package_name = 3;
+    // UID of app that requested permission
+    optional int32 uid = 2 [(is_uid) = true];
     // Number of items that were requested
-    optional int32 item_count = 4;
+    optional int32 item_count = 3;
     // Result of this request
-    optional Result result = 5;
+    optional Result result = 4;
 }
 
 /**
@@ -4535,7 +4531,7 @@
  * Logged from:
  *   packages/providers/MediaProvider/src/com/android/providers/media/DatabaseHelper.java
  */
-message MediaProviderSchemaChange {
+message MediaProviderSchemaChanged {
     // Volume type that this event pertains to
     optional android.stats.mediaprovider.VolumeType volume_type = 1;
     // Old database version code
@@ -4554,7 +4550,7 @@
  * Logged from:
  *   packages/providers/MediaProvider/src/com/android/providers/media/MediaProvider.java
  */
-message MediaProviderIdleMaintenance {
+message MediaProviderIdleMaintenanceFinished {
     // Volume type that this event pertains to
     optional android.stats.mediaprovider.VolumeType volume_type = 1;
 
diff --git a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
index d5da0b4..54a744b 100644
--- a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
+++ b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
@@ -64,7 +64,8 @@
             "AID_LMKD",
             "com.android.managedprovisioning",
             "AID_MEDIA",
-            "AID_NETWORK_STACK"
+            "AID_NETWORK_STACK",
+            "com.google.android.providers.media.module",
     };
     private static final String[] DEFAULT_PULL_SOURCES = {
             "AID_SYSTEM",
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index d00366b..47ccc2f 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -344,7 +344,7 @@
         ApkAssets apkAssets = null;
         if (mLoadedApkAssets != null) {
             apkAssets = mLoadedApkAssets.get(newKey);
-            if (apkAssets != null) {
+            if (apkAssets != null && apkAssets.isUpToDate()) {
                 return apkAssets;
             }
         }
@@ -353,7 +353,7 @@
         final WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.get(newKey);
         if (apkAssetsRef != null) {
             apkAssets = apkAssetsRef.get();
-            if (apkAssets != null) {
+            if (apkAssets != null && apkAssets.isUpToDate()) {
                 if (mLoadedApkAssets != null) {
                     mLoadedApkAssets.put(newKey, apkAssets);
                 }
@@ -1121,7 +1121,9 @@
             daj = new DisplayAdjustments(daj);
             daj.setCompatibilityInfo(compat);
         }
-        daj.setConfiguration(config);
+        if (displayId == Display.DEFAULT_DISPLAY) {
+            daj.setConfiguration(config);
+        }
         DisplayMetrics dm = getDisplayMetrics(displayId, daj);
         if (displayId != Display.DEFAULT_DISPLAY) {
             applyNonDefaultDisplayMetricsToConfiguration(dm, tmpConfig);
diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
index c94d428..6bd8b1d 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
@@ -21,7 +21,6 @@
 import static android.content.pm.PackageManager.FEATURE_WATCH;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
-import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_ONLY_COREAPP_ALLOWED;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION;
 import static android.os.Build.VERSION_CODES.DONUT;
@@ -253,10 +252,8 @@
             final File baseApk = new File(lite.baseCodePath);
             ParseResult<ParsingPackage> result = parseBaseApk(input, baseApk,
                     lite.codePath, assets, flags);
-            // TODO(b/135203078): Pass original error up?
             if (result.isError()) {
-                return input.error(INSTALL_PARSE_FAILED_NOT_APK,
-                        "Failed to parse base APK: " + baseApk);
+                return input.error(result);
             }
 
             ParsingPackage pkg = result.getResult();
diff --git a/core/java/android/net/VpnManager.java b/core/java/android/net/VpnManager.java
index 2041cfb..c87b827 100644
--- a/core/java/android/net/VpnManager.java
+++ b/core/java/android/net/VpnManager.java
@@ -75,7 +75,7 @@
     }
 
     /**
-     * Create an instance of the VpnManger with the given context.
+     * Create an instance of the VpnManager with the given context.
      *
      * <p>Internal only. Applications are expected to obtain an instance of the VpnManager via the
      * {@link Context.getSystemService()} method call.
diff --git a/core/java/android/service/dataloader/DataLoaderService.java b/core/java/android/service/dataloader/DataLoaderService.java
index c047dc0..60373ac 100644
--- a/core/java/android/service/dataloader/DataLoaderService.java
+++ b/core/java/android/service/dataloader/DataLoaderService.java
@@ -34,6 +34,8 @@
 import android.util.ExceptionUtils;
 import android.util.Slog;
 
+import libcore.io.IoUtils;
+
 import java.io.IOException;
 import java.util.Collection;
 
@@ -115,22 +117,10 @@
                 destroy(id);
                 throw new RuntimeException(ex);
             } finally {
-                // Closing FDs.
                 if (control.incremental != null) {
-                    if (control.incremental.cmd != null) {
-                        try {
-                            control.incremental.cmd.close();
-                        } catch (IOException e) {
-                            Slog.e(TAG, "Failed to close IncFs CMD file descriptor " + e);
-                        }
-                    }
-                    if (control.incremental.log != null) {
-                        try {
-                            control.incremental.log.close();
-                        } catch (IOException e) {
-                            Slog.e(TAG, "Failed to close IncFs LOG file descriptor " + e);
-                        }
-                    }
+                    IoUtils.closeQuietly(control.incremental.cmd);
+                    IoUtils.closeQuietly(control.incremental.pendingReads);
+                    IoUtils.closeQuietly(control.incremental.log);
                 }
             }
         }
diff --git a/core/java/android/view/IRecentsAnimationController.aidl b/core/java/android/view/IRecentsAnimationController.aidl
index a60a5cc..983ab2e 100644
--- a/core/java/android/view/IRecentsAnimationController.aidl
+++ b/core/java/android/view/IRecentsAnimationController.aidl
@@ -114,4 +114,16 @@
      * animation is cancelled through fail safe mechanism.
      */
     void setWillFinishToHome(boolean willFinishToHome);
+
+    /**
+     * Stops controlling a task that is currently controlled by this recents animation.
+     *
+     * This method should be called when a task that has been received via {@link #onAnimationStart}
+     * or {@link #onTaskAppeared} is no longer needed.  After calling this method, the task will
+     * either disappear from the screen, or jump to its final position in case it was the top task.
+     *
+     * @param taskId Id of the Task target to remove
+     * @return {@code true} when target removed successfully, {@code false} otherwise.
+     */
+    boolean removeTask(int taskId);
 }
diff --git a/core/java/android/view/IRecentsAnimationRunner.aidl b/core/java/android/view/IRecentsAnimationRunner.aidl
index 6eb90fc..925786f 100644
--- a/core/java/android/view/IRecentsAnimationRunner.aidl
+++ b/core/java/android/view/IRecentsAnimationRunner.aidl
@@ -56,4 +56,10 @@
     void onAnimationStart(in IRecentsAnimationController controller,
             in RemoteAnimationTarget[] apps, in RemoteAnimationTarget[] wallpapers,
             in Rect homeContentInsets, in Rect minimizedHomeBounds) = 2;
+
+    /**
+     * Called when the task of an activity that has been started while the recents animation
+     * was running becomes ready for control.
+     */
+    void onTaskAppeared(in RemoteAnimationTarget app) = 3;
 }
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 81bfcb0..a0527b7 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -48,6 +48,11 @@
             out Rect outContentInsets, out Rect outStableInsets,
             out DisplayCutout.ParcelableWrapper displayCutout, out InputChannel outInputChannel,
             out InsetsState insetsState, out InsetsSourceControl[] activeControls);
+    int addToDisplayAsUser(IWindow window, int seq, in WindowManager.LayoutParams attrs,
+                in int viewVisibility, in int layerStackId, in int userId,
+                out Rect outFrame, out Rect outContentInsets, out Rect outStableInsets,
+                out DisplayCutout.ParcelableWrapper displayCutout, out InputChannel outInputChannel,
+                out InsetsState insetsState, out InsetsSourceControl[] activeControls);
     int addToDisplayWithoutInputChannel(IWindow window, int seq, in WindowManager.LayoutParams attrs,
             in int viewVisibility, in int layerStackId, out Rect outContentInsets,
             out Rect outStableInsets, out InsetsState insetsState);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index da18608..8abe72f 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -14684,17 +14684,19 @@
                 }
             }
         }
-        if (isAccessibilityPane()) {
-            if (isVisible != oldVisible) {
+
+        if (isVisible != oldVisible) {
+            if (isAccessibilityPane()) {
                 notifyViewAccessibilityStateChangedIfNeeded(isVisible
                         ? AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_APPEARED
                         : AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_DISAPPEARED);
             }
-        }
 
-        notifyAppearedOrDisappearedForContentCaptureIfNeeded(isVisible);
-        if (!getSystemGestureExclusionRects().isEmpty() && isVisible != oldVisible) {
-            postUpdateSystemGestureExclusionRects();
+            notifyAppearedOrDisappearedForContentCaptureIfNeeded(isVisible);
+
+            if (!getSystemGestureExclusionRects().isEmpty()) {
+                postUpdateSystemGestureExclusionRects();
+            }
         }
     }
 
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index dab1108..ed1edc3 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -111,6 +111,7 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.Trace;
+import android.os.UserHandle;
 import android.sysprop.DisplayProperties;
 import android.util.AndroidRuntimeException;
 import android.util.DisplayMetrics;
@@ -893,6 +894,14 @@
      * We have one child
      */
     public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
+        setView(view, attrs, panelParentView, UserHandle.myUserId());
+    }
+
+    /**
+     * We have one child
+     */
+    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
+            int userId) {
         synchronized (this) {
             if (mView == null) {
                 mView = view;
@@ -1001,8 +1010,8 @@
                     mAttachInfo.mRecomputeGlobalAttributes = true;
                     collectViewAttributes();
                     adjustLayoutParamsForCompatibility(mWindowAttributes);
-                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
-                            getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
+                    res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,
+                            getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame,
                             mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                             mAttachInfo.mDisplayCutout, inputChannel,
                             mTempInsets, mTempControls);
@@ -1075,6 +1084,9 @@
                             throw new WindowManager.InvalidDisplayException("Unable to add window "
                                     + mWindow + " -- the specified window type "
                                     + mWindowAttributes.type + " is not valid");
+                        case WindowManagerGlobal.ADD_INVALID_USER:
+                            throw new WindowManager.BadTokenException("Unable to add Window "
+                                    + mWindow + " -- requested userId is not valid");
                     }
                     throw new RuntimeException(
                             "Unable to add window -- unknown error code " + res);
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index ab968d7..fba6a55 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -144,6 +144,7 @@
     public static final int ADD_PERMISSION_DENIED = -8;
     public static final int ADD_INVALID_DISPLAY = -9;
     public static final int ADD_INVALID_TYPE = -10;
+    public static final int ADD_INVALID_USER = -11;
 
     @UnsupportedAppUsage
     private static WindowManagerGlobal sDefaultWindowManager;
@@ -325,7 +326,7 @@
     }
 
     public void addView(View view, ViewGroup.LayoutParams params,
-            Display display, Window parentWindow) {
+            Display display, Window parentWindow, int userId) {
         if (view == null) {
             throw new IllegalArgumentException("view must not be null");
         }
@@ -402,7 +403,7 @@
 
             // do this last because it fires off messages to start doing things
             try {
-                root.setView(view, wparams, panelParentView);
+                root.setView(view, wparams, panelParentView, userId);
             } catch (RuntimeException e) {
                 // BadTokenException or InvalidDisplayException, clean up.
                 if (index >= 0) {
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index 316a5f2..2975d5e 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -104,7 +104,8 @@
     @Override
     public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
         applyDefaultToken(params);
-        mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow);
+        mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
+                mContext.getUserId());
     }
 
     @Override
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 39ed401..ec51301 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -130,6 +130,20 @@
         return WindowManagerGlobal.ADD_OKAY | WindowManagerGlobal.ADD_FLAG_APP_VISIBLE;
     }
 
+    /**
+     * IWindowSession implementation. Currently this class doesn't need to support for multi-user.
+     */
+    @Override
+    public int addToDisplayAsUser(IWindow window, int seq, WindowManager.LayoutParams attrs,
+            int viewVisibility, int displayId, int userId, Rect outFrame,
+            Rect outContentInsets, Rect outStableInsets,
+            DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
+            InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) {
+        return addToDisplay(window, seq, attrs, viewVisibility, displayId,
+                outFrame, outContentInsets, outStableInsets, outDisplayCutout, outInputChannel,
+                outInsetsState, outActiveControls);
+    }
+
     @Override
     public int addToDisplayWithoutInputChannel(android.view.IWindow window, int seq,
             android.view.WindowManager.LayoutParams attrs, int viewVisibility, int layerStackId,
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 482d5b25..71dd665 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1968,6 +1968,38 @@
         return true;
     }
 
+    /**
+     * An empty method only to avoid crashes of apps that call this method via reflection and do not
+     * handle {@link NoSuchMethodException} in a graceful manner.
+     *
+     * @deprecated This is an empty method.  No framework method must call this method.
+     * @hide
+     */
+    @Deprecated
+    @UnsupportedAppUsage(trackingBug = 37122102, maxTargetSdk = Build.VERSION_CODES.Q,
+            publicAlternatives = "{@code androidx.activity.ComponentActivity}")
+    public void windowDismissed(IBinder appWindowToken) {
+        // Intentionally empty.
+        //
+        // It seems that some applications call this method via reflection to null clear the
+        // following fields that used to exist in InputMethodManager:
+        //  * InputMethodManager#mCurRootView
+        //  * InputMethodManager#mServedView
+        //  * InputMethodManager#mNextServedView
+        // so that these objects can be garbage-collected when an Activity gets dismissed.
+        //
+        // It is indeed true that older versions of InputMethodManager had issues that prevented
+        // these fields from being null-cleared when it should have been, but the understanding of
+        // the engineering team is that all known issues have already been fixed as of Android 10.
+        //
+        // For older devices, developers can work around the object leaks by using
+        // androidx.activity.ComponentActivity.
+        // See https://issuetracker.google.com/u/1/issues/37122102 for details.
+        //
+        // If you believe InputMethodManager is leaking objects in API 24 or any later version,
+        // please file a bug at https://issuetracker.google.com/issues/new?component=192705.
+    }
+
     private int getStartInputFlags(View focusedView, int startInputFlags) {
         startInputFlags |= StartInputFlags.VIEW_HAS_FOCUS;
         if (focusedView.onCheckIsTextEditor()) {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 1c978bf..32a79f3 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1099,9 +1099,9 @@
         android:description="@string/permgroupdesc_phone"
         android:priority="500" />
 
-    <!-- Allows read only access to phone state, including the phone number of the device,
-         current cellular network information, the status of any ongoing calls, and a list of any
-         {@link android.telecom.PhoneAccount}s registered on the device.
+    <!-- Allows read only access to phone state, including the current cellular network information,
+         the status of any ongoing calls, and a list of any {@link android.telecom.PhoneAccount}s
+         registered on the device.
          <p class="note"><strong>Note:</strong> If <em>both</em> your <a
          href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#min">{@code
          minSdkVersion}</a> and <a
diff --git a/core/tests/overlaytests/host/Android.bp b/core/tests/overlaytests/host/Android.bp
index a2fcef5..2b38cca 100644
--- a/core/tests/overlaytests/host/Android.bp
+++ b/core/tests/overlaytests/host/Android.bp
@@ -16,7 +16,7 @@
     name: "OverlayHostTests",
     srcs: ["src/**/*.java"],
     libs: ["tradefed"],
-    test_suites: ["general-tests"],
+    test_suites: ["device-tests"],
     target_required: [
         "OverlayHostTests_NonPlatformSignatureOverlay",
         "OverlayHostTests_PlatformSignatureStaticOverlay",
diff --git a/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java b/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java
index eec7be2..d898d22 100644
--- a/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java
+++ b/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java
@@ -78,14 +78,9 @@
     }
 
     @Test
-    public void failToInstallPlatformSignedStaticOverlay() throws Exception {
-        try {
-            installPackage("OverlayHostTests_PlatformSignatureStaticOverlay.apk");
-            fail("installed a static overlay");
-        } catch (Exception e) {
-            // Expected.
-        }
-        assertFalse(overlayManagerContainsPackage(SIG_OVERLAY_PACKAGE_NAME));
+    public void installedIsStaticOverlayIsMutable() throws Exception {
+        installPackage("OverlayHostTests_PlatformSignatureStaticOverlay.apk");
+        assertTrue(isOverlayMutable(SIG_OVERLAY_PACKAGE_NAME));
     }
 
     @Test
@@ -229,6 +224,10 @@
         return shell("cmd overlay list").contains(pkg);
     }
 
+    private boolean isOverlayMutable(String pkg) throws Exception {
+        return shell("cmd overlay dump ismutable " + pkg).contains("true");
+    }
+
     private String shell(final String cmd) throws Exception {
         return getDevice().executeShellCommand(cmd);
     }
diff --git a/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk b/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk
index cc7704b..f3c0abd 100644
--- a/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk
+++ b/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk
@@ -20,7 +20,7 @@
 LOCAL_MODULE_TAGS := tests
 LOCAL_PACKAGE_NAME := OverlayHostTests_NonPlatformSignatureOverlay
 LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := general-tests
+LOCAL_COMPATIBILITY_SUITE := device-tests
 LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_bad
 include $(BUILD_PACKAGE)
 
@@ -28,7 +28,8 @@
 LOCAL_MODULE_TAGS := tests
 LOCAL_PACKAGE_NAME := OverlayHostTests_PlatformSignatureStaticOverlay
 LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := general-tests
+LOCAL_COMPATIBILITY_SUITE := device-tests
+LOCAL_CERTIFICATE := platform
 LOCAL_MANIFEST_FILE := static/AndroidManifest.xml
 LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_static
 include $(BUILD_PACKAGE)
@@ -37,7 +38,7 @@
 LOCAL_MODULE_TAGS := tests
 LOCAL_PACKAGE_NAME := OverlayHostTests_PlatformSignatureOverlay
 LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := general-tests
+LOCAL_COMPATIBILITY_SUITE := device-tests
 LOCAL_CERTIFICATE := platform
 LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v1
 LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1
diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk b/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk
index f8607f4..878f05d 100644
--- a/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk
+++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk
@@ -19,7 +19,7 @@
 LOCAL_SRC_FILES := $(call all-java-files-under,src)
 LOCAL_PACKAGE_NAME := OverlayHostTests_UpdateOverlay
 LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := general-tests
+LOCAL_COMPATIBILITY_SUITE := device-tests
 LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules
 LOCAL_USE_AAPT2 := true
 LOCAL_AAPT_FLAGS := --no-resource-removal
@@ -31,7 +31,7 @@
 LOCAL_MODULE_TAGS := tests
 LOCAL_PACKAGE_NAME := OverlayHostTests_FrameworkOverlayV1
 LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := general-tests
+LOCAL_COMPATIBILITY_SUITE := device-tests
 LOCAL_CERTIFICATE := platform
 LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v1
 LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1
@@ -43,7 +43,7 @@
 LOCAL_MODULE_TAGS := tests
 LOCAL_PACKAGE_NAME := OverlayHostTests_FrameworkOverlayV2
 LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := general-tests
+LOCAL_COMPATIBILITY_SUITE := device-tests
 LOCAL_CERTIFICATE := platform
 LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v2
 LOCAL_AAPT_FLAGS += --version-code 2 --version-name v2
@@ -57,7 +57,7 @@
 LOCAL_MODULE_TAGS := tests
 LOCAL_PACKAGE_NAME := OverlayHostTests_AppOverlayV1
 LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := general-tests
+LOCAL_COMPATIBILITY_SUITE := device-tests
 LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v1
 LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/app/v1/res
@@ -68,7 +68,7 @@
 LOCAL_MODULE_TAGS := tests
 LOCAL_PACKAGE_NAME := OverlayHostTests_AppOverlayV2
 LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := general-tests
+LOCAL_COMPATIBILITY_SUITE := device-tests
 LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v2
 LOCAL_AAPT_FLAGS += --version-code 2 --version-name v2
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/app/v2/res
diff --git a/core/tests/overlaytests/remount/host/Android.bp b/core/tests/overlaytests/remount/Android.bp
similarity index 84%
rename from core/tests/overlaytests/remount/host/Android.bp
rename to core/tests/overlaytests/remount/Android.bp
index 3825c55..5757cfe 100644
--- a/core/tests/overlaytests/remount/host/Android.bp
+++ b/core/tests/overlaytests/remount/Android.bp
@@ -21,8 +21,12 @@
     ],
     test_suites: ["general-tests"],
     java_resources: [
+        ":com.android.overlaytest.overlaid",
+        ":com.android.overlaytest.overlay",
         ":OverlayRemountedTest_SharedLibrary",
         ":OverlayRemountedTest_SharedLibraryOverlay",
         ":OverlayRemountedTest_Target",
+        ":OverlayRemountedTest_TargetUpgrade",
+        ":OverlayRemountedTest_Overlay",
     ],
 }
diff --git a/core/tests/overlaytests/remount/host/AndroidTest.xml b/core/tests/overlaytests/remount/AndroidTest.xml
similarity index 100%
rename from core/tests/overlaytests/remount/host/AndroidTest.xml
rename to core/tests/overlaytests/remount/AndroidTest.xml
diff --git a/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/OverlayApexTest.java b/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/OverlayApexTest.java
new file mode 100644
index 0000000..3fa8bcd
--- /dev/null
+++ b/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/OverlayApexTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.overlaytest.remounted;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class OverlayApexTest extends OverlayRemountedTestBase {
+    private static final String OVERLAID_APEX = "com.android.overlaytest.overlaid.apex";
+    private static final String OVERLAY_APEX = "com.android.overlaytest.overlay.apex";
+
+    @Test
+    public void testApkInApexCanBeOverlaid() throws Exception {
+        final String targetResource = resourceName(TARGET_PACKAGE, "bool", "target_overlaid");
+
+        // The target APK will be installed inside the overlaid APEX.
+        mPreparer.pushResourceFile(OVERLAID_APEX,
+                "/system/apex/com.android.overlaytest.overlaid.apex")
+                .installResourceApk(OVERLAY_APK, OVERLAY_PACKAGE)
+                .reboot()
+                .setOverlayEnabled(OVERLAY_PACKAGE, false);
+
+        // The resource is not currently overlaid.
+        assertResource(targetResource, "false");
+
+        // Overlay the resource.
+        mPreparer.setOverlayEnabled(OVERLAY_PACKAGE, true);
+        assertResource(targetResource, "true");
+    }
+
+    @Test
+    public void testApkInApexCanOverlay() throws Exception {
+        final String targetResource = resourceName(TARGET_PACKAGE, "bool", "target_overlaid");
+
+        // The overlay APK will be installed inside the overlay APEX.
+        mPreparer.pushResourceFile(OVERLAY_APEX,
+                "/system/apex/com.android.overlaytest.overlay.apex")
+                .installResourceApk(TARGET_APK, TARGET_PACKAGE)
+                .reboot()
+                .setOverlayEnabled(OVERLAY_PACKAGE, false);
+
+        // The resource is not currently overlaid.
+        assertResource(targetResource, "false");
+
+        // Overlay the resource.
+        mPreparer.setOverlayEnabled(OVERLAY_PACKAGE, true);
+        assertResource(targetResource, "true");
+    }
+}
diff --git a/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/OverlayRemountedTestBase.java b/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/OverlayRemountedTestBase.java
new file mode 100644
index 0000000..14b5bf6
--- /dev/null
+++ b/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/OverlayRemountedTestBase.java
@@ -0,0 +1,74 @@
+/*
+ * 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.overlaytest.remounted;
+
+import static org.junit.Assert.fail;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TemporaryFolder;
+
+public class OverlayRemountedTestBase extends BaseHostJUnit4Test {
+    private static final long ASSERT_RESOURCE_TIMEOUT_MS = 30000;
+    static final String TARGET_APK = "OverlayRemountedTest_Target.apk";
+    static final String TARGET_PACKAGE = "com.android.overlaytest.remounted.target";
+    static final String OVERLAY_APK = "OverlayRemountedTest_Overlay.apk";
+    static final String OVERLAY_PACKAGE = "com.android.overlaytest.remounted.target.overlay";
+
+    private final TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+    protected final SystemPreparer mPreparer = new SystemPreparer(mTemporaryFolder,
+            this::getDevice);
+
+    @Rule
+    public final RuleChain ruleChain = RuleChain.outerRule(mTemporaryFolder).around(mPreparer);
+
+    @Before
+    public void startBefore() throws DeviceNotAvailableException {
+        getDevice().waitForDeviceAvailable();
+    }
+
+    /** Builds the full name of a resource in the form package:type/entry. */
+    String resourceName(String pkg, String type, String entry) {
+        return String.format("%s:%s/%s", pkg, type, entry);
+    }
+
+    void assertResource(String resourceName, String expectedValue)
+            throws DeviceNotAvailableException {
+        String result = null;
+
+        final long endMillis = System.currentTimeMillis() + ASSERT_RESOURCE_TIMEOUT_MS;
+        while (System.currentTimeMillis() <= endMillis) {
+            result = getDevice().executeShellCommand(
+                    String.format("cmd overlay lookup %s %s", TARGET_PACKAGE, resourceName));
+            if (result.equals(expectedValue + "\n") ||
+                    result.endsWith("-> " + expectedValue + "\n")) {
+                return;
+            }
+
+            try {
+                Thread.sleep(200);
+            } catch (InterruptedException ignore) {
+            }
+        }
+
+        fail(String.format("expected: <[%s]> in: <[%s]>", expectedValue, result));
+    }
+}
diff --git a/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java b/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java
similarity index 60%
rename from core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java
rename to core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java
index 06b2ac8..7f2c060 100644
--- a/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java
+++ b/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java
@@ -16,23 +16,13 @@
 
 package com.android.overlaytest.remounted;
 
-import static org.junit.Assert.assertTrue;
-
-import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
-import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
-import org.junit.rules.RuleChain;
-import org.junit.rules.TemporaryFolder;
 import org.junit.runner.RunWith;
 
 @RunWith(DeviceJUnit4ClassRunner.class)
-public class OverlaySharedLibraryTest extends BaseHostJUnit4Test {
-    private static final String TARGET_APK = "OverlayRemountedTest_Target.apk";
-    private static final String TARGET_PACKAGE = "com.android.overlaytest.remounted.target";
+public class OverlaySharedLibraryTest extends OverlayRemountedTestBase {
     private static final String SHARED_LIBRARY_APK =
             "OverlayRemountedTest_SharedLibrary.apk";
     private static final String SHARED_LIBRARY_PACKAGE =
@@ -42,17 +32,6 @@
     private static final String SHARED_LIBRARY_OVERLAY_PACKAGE =
             "com.android.overlaytest.remounted.shared_library.overlay";
 
-    public final TemporaryFolder temporaryFolder = new TemporaryFolder();
-    public final SystemPreparer preparer = new SystemPreparer(temporaryFolder, this::getDevice);
-
-    @Rule
-    public final RuleChain ruleChain = RuleChain.outerRule(temporaryFolder).around(preparer);
-
-    @Before
-    public void startBefore() throws DeviceNotAvailableException {
-        getDevice().waitForDeviceAvailable();
-    }
-
     @Test
     public void testSharedLibrary() throws Exception {
         final String targetResource = resourceName(TARGET_PACKAGE, "bool",
@@ -60,7 +39,7 @@
         final String libraryResource = resourceName(SHARED_LIBRARY_PACKAGE, "bool",
                 "shared_library_overlaid");
 
-        preparer.pushResourceFile(SHARED_LIBRARY_APK, "/product/app/SharedLibrary.apk")
+        mPreparer.pushResourceFile(SHARED_LIBRARY_APK, "/product/app/SharedLibrary.apk")
                 .installResourceApk(SHARED_LIBRARY_OVERLAY_APK, SHARED_LIBRARY_OVERLAY_PACKAGE)
                 .reboot()
                 .setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, false)
@@ -71,7 +50,7 @@
         assertResource(libraryResource, "false");
 
         // Overlay the shared library resource.
-        preparer.setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, true);
+        mPreparer.setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, true);
         assertResource(targetResource, "true");
         assertResource(libraryResource, "true");
     }
@@ -83,7 +62,7 @@
         final String libraryResource = resourceName(SHARED_LIBRARY_PACKAGE, "bool",
                 "shared_library_overlaid");
 
-        preparer.pushResourceFile(SHARED_LIBRARY_APK, "/product/app/SharedLibrary.apk")
+        mPreparer.pushResourceFile(SHARED_LIBRARY_APK, "/product/app/SharedLibrary.apk")
                 .installResourceApk(SHARED_LIBRARY_OVERLAY_APK, SHARED_LIBRARY_OVERLAY_PACKAGE)
                 .setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, true)
                 .reboot()
@@ -92,18 +71,4 @@
         assertResource(targetResource, "true");
         assertResource(libraryResource, "true");
     }
-
-    /** Builds the full name of a resource in the form package:type/entry. */
-    String resourceName(String pkg, String type, String entry) {
-        return String.format("%s:%s/%s", pkg, type, entry);
-    }
-
-    void assertResource(String resourceName, String expectedValue)
-            throws DeviceNotAvailableException {
-        final String result = getDevice().executeShellCommand(
-                String.format("cmd overlay lookup %s %s", TARGET_PACKAGE, resourceName));
-        assertTrue(String.format("expected: <[%s]> in: <[%s]>", expectedValue, result),
-                result.equals(expectedValue + "\n") ||
-                result.endsWith("-> " + expectedValue + "\n"));
-    }
 }
diff --git a/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/PackagedUpgradedTest.java b/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/PackagedUpgradedTest.java
new file mode 100644
index 0000000..70e3423
--- /dev/null
+++ b/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/PackagedUpgradedTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.overlaytest.remounted;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class PackagedUpgradedTest extends OverlayRemountedTestBase {
+    private static final String TARGET_UPGRADE_APK = "OverlayRemountedTest_TargetUpgrade.apk";
+
+    @Test
+    public void testTargetUpgrade() throws Exception {
+        final String targetOverlaid = resourceName(TARGET_PACKAGE, "bool", "target_overlaid");
+        final String targetReference = resourceName(TARGET_PACKAGE, "bool", "target_reference");
+
+        mPreparer.pushResourceFile(TARGET_APK, "/product/app/OverlayTarget.apk")
+                .reboot()
+                .installResourceApk(OVERLAY_APK, OVERLAY_PACKAGE)
+                .setOverlayEnabled(OVERLAY_PACKAGE, true);
+
+        assertResource(targetReference, "@" + 0x7f010000 + " -> true");
+        assertResource(targetOverlaid, "true");
+
+        mPreparer.installResourceApk(TARGET_UPGRADE_APK, TARGET_PACKAGE);
+
+        assertResource(targetReference, "@" + 0x7f0100ff + " -> true");
+        assertResource(targetOverlaid, "true");
+    }
+
+    @Test
+    public void testTargetRelocated() throws Exception {
+        final String targetOverlaid = resourceName(TARGET_PACKAGE, "bool", "target_overlaid");
+        final String originalPath = "/product/app/OverlayTarget.apk";
+
+        mPreparer.pushResourceFile(TARGET_APK, originalPath)
+                .reboot()
+                .installResourceApk(OVERLAY_APK, OVERLAY_PACKAGE)
+                .setOverlayEnabled(OVERLAY_PACKAGE, true);
+
+        assertResource(targetOverlaid, "true");
+
+        mPreparer.remount();
+        getDevice().deleteFile(originalPath);
+        mPreparer.pushResourceFile(TARGET_UPGRADE_APK, "/product/app/OverlayTarget2.apk")
+                .reboot();
+
+        assertResource(targetOverlaid, "true");
+    }
+}
diff --git a/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/SystemPreparer.java b/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/SystemPreparer.java
similarity index 75%
rename from core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/SystemPreparer.java
rename to core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/SystemPreparer.java
index 8696091..bb72d0e 100644
--- a/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/SystemPreparer.java
+++ b/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/SystemPreparer.java
@@ -18,8 +18,6 @@
 
 import static org.junit.Assert.assertTrue;
 
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
-
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
 
@@ -32,10 +30,6 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
-import java.util.concurrent.FutureTask;
-import java.util.concurrent.TimeoutException;
 
 class SystemPreparer extends ExternalResource {
     private static final long OVERLAY_ENABLE_TIMEOUT_MS = 30000;
@@ -58,7 +52,7 @@
     SystemPreparer pushResourceFile(String resourcePath,
             String outputPath) throws DeviceNotAvailableException, IOException {
         final ITestDevice device = mDeviceProvider.getDevice();
-        device.executeAdbCommand("remount");
+        remount();
         assertTrue(device.pushFile(copyResourceToTemp(resourcePath), outputPath));
         mPushedFiles.add(outputPath);
         return this;
@@ -69,42 +63,37 @@
             throws DeviceNotAvailableException, IOException {
         final ITestDevice device = mDeviceProvider.getDevice();
         final File tmpFile = copyResourceToTemp(resourcePath);
-        final String result = device.installPackage(tmpFile, true);
+        final String result = device.installPackage(tmpFile, true /* reinstall */);
         Assert.assertNull(result);
         mInstalledPackages.add(packageName);
         return this;
     }
 
-    /** Sets the enable state of an overlay pacakage. */
+    /** Sets the enable state of an overlay package. */
     SystemPreparer setOverlayEnabled(String packageName, boolean enabled)
-            throws ExecutionException, DeviceNotAvailableException {
+            throws DeviceNotAvailableException {
         final ITestDevice device = mDeviceProvider.getDevice();
+        final String enable = enabled ? "enable" : "disable";
 
         // Wait for the overlay to change its enabled state.
-        final FutureTask<Boolean> enabledListener = new FutureTask<>(() -> {
-            while (true) {
-                device.executeShellCommand(String.format("cmd overlay %s %s",
-                        enabled ? "enable" : "disable", packageName));
-
-                final String result = device.executeShellCommand("cmd overlay dump " + packageName);
-                final int startIndex = result.indexOf("mIsEnabled");
-                final int endIndex = result.indexOf('\n', startIndex);
-                if (result.substring(startIndex, endIndex).contains((enabled) ? "true" : "false")) {
-                    return true;
-                }
+        final long endMillis = System.currentTimeMillis() + OVERLAY_ENABLE_TIMEOUT_MS;
+        String result;
+        while (System.currentTimeMillis() <= endMillis) {
+            device.executeShellCommand(String.format("cmd overlay %s %s", enable, packageName));
+            result = device.executeShellCommand("cmd overlay dump isenabled "
+                    + packageName);
+            if (((enabled) ? "true\n" : "false\n").equals(result)) {
+                return this;
             }
-        });
 
-        final Executor executor = (cmd) -> new Thread(cmd).start();
-        executor.execute(enabledListener);
-        try {
-            enabledListener.get(OVERLAY_ENABLE_TIMEOUT_MS, MILLISECONDS);
-        } catch (InterruptedException ignored) {
-        } catch (TimeoutException e) {
-            throw new IllegalStateException(device.executeShellCommand("cmd overlay list"));
+            try {
+                Thread.sleep(200);
+            } catch (InterruptedException ignore) {
+            }
         }
 
-        return this;
+        throw new IllegalStateException(String.format("Failed to %s overlay %s:\n%s", enable,
+                packageName, device.executeShellCommand("cmd overlay list")));
     }
 
     /** Restarts the device and waits until after boot is completed. */
@@ -114,6 +103,11 @@
         return this;
     }
 
+    SystemPreparer remount() throws DeviceNotAvailableException {
+        mDeviceProvider.getDevice().executeAdbCommand("remount");
+        return this;
+    }
+
     /** Copies a file within the host test jar to a temporary file on the host machine. */
     private File copyResourceToTemp(String resourcePath) throws IOException {
         final File tempFile = mHostTempFolder.newFile(resourcePath);
@@ -138,7 +132,7 @@
     protected void after() {
         final ITestDevice device = mDeviceProvider.getDevice();
         try {
-            device.executeAdbCommand("remount");
+            remount();
             for (final String file : mPushedFiles) {
                 device.deleteFile(file);
             }
diff --git a/core/tests/overlaytests/remount/target/Android.bp b/core/tests/overlaytests/remount/test-apps/Overlay/Android.bp
similarity index 73%
rename from core/tests/overlaytests/remount/target/Android.bp
rename to core/tests/overlaytests/remount/test-apps/Overlay/Android.bp
index 83f9f28..a1fdbfd 100644
--- a/core/tests/overlaytests/remount/target/Android.bp
+++ b/core/tests/overlaytests/remount/test-apps/Overlay/Android.bp
@@ -1,4 +1,4 @@
-// Copyright (C) 2018 The Android Open Source Project
+// Copyright (C) 2019 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.
@@ -13,8 +13,9 @@
 // limitations under the License.
 
 android_test_helper_app {
-    name: "OverlayRemountedTest_Target",
-    srcs: ["src/**/*.java"],
-    sdk_version: "test_current",
-    libs: ["OverlayRemountedTest_SharedLibrary"],
+    name: "OverlayRemountedTest_Overlay",
+    sdk_version: "current",
+    apex_available: [
+        "com.android.overlaytest.overlay",
+    ],
 }
diff --git a/core/tests/overlaytests/remount/target/AndroidManifest.xml b/core/tests/overlaytests/remount/test-apps/Overlay/AndroidManifest.xml
similarity index 66%
copy from core/tests/overlaytests/remount/target/AndroidManifest.xml
copy to core/tests/overlaytests/remount/test-apps/Overlay/AndroidManifest.xml
index dc07dca..d6d706c 100644
--- a/core/tests/overlaytests/remount/target/AndroidManifest.xml
+++ b/core/tests/overlaytests/remount/test-apps/Overlay/AndroidManifest.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
+  ~ 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.
@@ -16,11 +16,8 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.overlaytest.remounted.target">
-
-    <application>
-        <uses-library android:name="android.test.runner" />
-        <uses-library android:name="com.android.overlaytest.remounted.shared_library"
-                      android:required="true" />
-    </application>
-</manifest>
+    package="com.android.overlaytest.remounted.target.overlay">
+    <application android:hasCode="false" />
+    <overlay android:targetPackage="com.android.overlaytest.remounted.target"
+             android:targetName="TestResources" />
+</manifest>
\ No newline at end of file
diff --git a/core/tests/overlaytests/remount/target/res/values/values.xml b/core/tests/overlaytests/remount/test-apps/Overlay/res/values/values.xml
similarity index 70%
copy from core/tests/overlaytests/remount/target/res/values/values.xml
copy to core/tests/overlaytests/remount/test-apps/Overlay/res/values/values.xml
index b5f444a..675e44f 100644
--- a/core/tests/overlaytests/remount/target/res/values/values.xml
+++ b/core/tests/overlaytests/remount/test-apps/Overlay/res/values/values.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
+  ~ 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.
@@ -15,6 +15,6 @@
   ~ limitations under the License.
   -->
 
-<resources xmlns:sharedlib="http://schemas.android.com/apk/res/com.android.overlaytest.remounted.shared_library">
-    <bool name="uses_shared_library_overlaid">@sharedlib:bool/shared_library_overlaid</bool>
+<resources>
+    <bool name="target_overlaid">true</bool>
 </resources>
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/Android.bp b/core/tests/overlaytests/remount/test-apps/SharedLibrary/Android.bp
similarity index 100%
rename from core/tests/overlaytests/remount/host/test-apps/SharedLibrary/Android.bp
rename to core/tests/overlaytests/remount/test-apps/SharedLibrary/Android.bp
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/AndroidManifest.xml b/core/tests/overlaytests/remount/test-apps/SharedLibrary/AndroidManifest.xml
similarity index 100%
rename from core/tests/overlaytests/remount/host/test-apps/SharedLibrary/AndroidManifest.xml
rename to core/tests/overlaytests/remount/test-apps/SharedLibrary/AndroidManifest.xml
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/overlayable.xml b/core/tests/overlaytests/remount/test-apps/SharedLibrary/res/values/overlayable.xml
similarity index 100%
rename from core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/overlayable.xml
rename to core/tests/overlaytests/remount/test-apps/SharedLibrary/res/values/overlayable.xml
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/public.xml b/core/tests/overlaytests/remount/test-apps/SharedLibrary/res/values/public.xml
similarity index 100%
rename from core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/public.xml
rename to core/tests/overlaytests/remount/test-apps/SharedLibrary/res/values/public.xml
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/values.xml b/core/tests/overlaytests/remount/test-apps/SharedLibrary/res/values/values.xml
similarity index 100%
rename from core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/values.xml
rename to core/tests/overlaytests/remount/test-apps/SharedLibrary/res/values/values.xml
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/Android.bp b/core/tests/overlaytests/remount/test-apps/SharedLibraryOverlay/Android.bp
similarity index 100%
rename from core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/Android.bp
rename to core/tests/overlaytests/remount/test-apps/SharedLibraryOverlay/Android.bp
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/AndroidManifest.xml b/core/tests/overlaytests/remount/test-apps/SharedLibraryOverlay/AndroidManifest.xml
similarity index 100%
rename from core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/AndroidManifest.xml
rename to core/tests/overlaytests/remount/test-apps/SharedLibraryOverlay/AndroidManifest.xml
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/res/values/values.xml b/core/tests/overlaytests/remount/test-apps/SharedLibraryOverlay/res/values/values.xml
similarity index 100%
rename from core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/res/values/values.xml
rename to core/tests/overlaytests/remount/test-apps/SharedLibraryOverlay/res/values/values.xml
diff --git a/core/tests/overlaytests/remount/target/Android.bp b/core/tests/overlaytests/remount/test-apps/Target/Android.bp
similarity index 72%
copy from core/tests/overlaytests/remount/target/Android.bp
copy to core/tests/overlaytests/remount/test-apps/Target/Android.bp
index 83f9f28..19947b1 100644
--- a/core/tests/overlaytests/remount/target/Android.bp
+++ b/core/tests/overlaytests/remount/test-apps/Target/Android.bp
@@ -1,4 +1,4 @@
-// Copyright (C) 2018 The Android Open Source Project
+// Copyright (C) 2019 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.
@@ -14,7 +14,15 @@
 
 android_test_helper_app {
     name: "OverlayRemountedTest_Target",
-    srcs: ["src/**/*.java"],
     sdk_version: "test_current",
+    apex_available: [
+        "com.android.overlaytest.overlaid",
+    ],
     libs: ["OverlayRemountedTest_SharedLibrary"],
 }
+
+android_test_helper_app {
+    name: "OverlayRemountedTest_TargetUpgrade",
+    resource_dirs: ["res_upgrade"],
+    sdk_version: "test_current",
+}
diff --git a/core/tests/overlaytests/remount/target/AndroidManifest.xml b/core/tests/overlaytests/remount/test-apps/Target/AndroidManifest.xml
similarity index 89%
rename from core/tests/overlaytests/remount/target/AndroidManifest.xml
rename to core/tests/overlaytests/remount/test-apps/Target/AndroidManifest.xml
index dc07dca..d1c7b7e 100644
--- a/core/tests/overlaytests/remount/target/AndroidManifest.xml
+++ b/core/tests/overlaytests/remount/test-apps/Target/AndroidManifest.xml
@@ -19,8 +19,7 @@
           package="com.android.overlaytest.remounted.target">
 
     <application>
-        <uses-library android:name="android.test.runner" />
         <uses-library android:name="com.android.overlaytest.remounted.shared_library"
-                      android:required="true" />
+                      android:required="false" />
     </application>
 </manifest>
diff --git a/core/tests/overlaytests/remount/target/res/values/values.xml b/core/tests/overlaytests/remount/test-apps/Target/res/values/overlayable.xml
similarity index 70%
copy from core/tests/overlaytests/remount/target/res/values/values.xml
copy to core/tests/overlaytests/remount/test-apps/Target/res/values/overlayable.xml
index b5f444a..4aa5bce 100644
--- a/core/tests/overlaytests/remount/target/res/values/values.xml
+++ b/core/tests/overlaytests/remount/test-apps/Target/res/values/overlayable.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
+  ~ 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.
@@ -15,6 +15,10 @@
   ~ limitations under the License.
   -->
 
-<resources xmlns:sharedlib="http://schemas.android.com/apk/res/com.android.overlaytest.remounted.shared_library">
-    <bool name="uses_shared_library_overlaid">@sharedlib:bool/shared_library_overlaid</bool>
+<resources>
+    <overlayable name="TestResources">
+        <policy type="public">
+            <item type="bool" name="target_overlaid" />
+        </policy>
+    </overlayable>
 </resources>
diff --git a/core/tests/overlaytests/remount/target/res/values/values.xml b/core/tests/overlaytests/remount/test-apps/Target/res/values/values.xml
similarity index 72%
rename from core/tests/overlaytests/remount/target/res/values/values.xml
rename to core/tests/overlaytests/remount/test-apps/Target/res/values/values.xml
index b5f444a..76253a9 100644
--- a/core/tests/overlaytests/remount/target/res/values/values.xml
+++ b/core/tests/overlaytests/remount/test-apps/Target/res/values/values.xml
@@ -17,4 +17,10 @@
 
 <resources xmlns:sharedlib="http://schemas.android.com/apk/res/com.android.overlaytest.remounted.shared_library">
     <bool name="uses_shared_library_overlaid">@sharedlib:bool/shared_library_overlaid</bool>
+
+    <!-- This resource has a different id in the updated version of this target app to test that the
+         idmap is regenerated when the target is updated. -->
+    <bool name="target_overlaid">false</bool>
+    <public type="bool" name="target_overlaid" id="0x7f010000" />
+    <bool name="target_reference">@bool/target_overlaid</bool>
 </resources>
diff --git a/core/tests/overlaytests/remount/target/res/values/values.xml b/core/tests/overlaytests/remount/test-apps/Target/res_upgrade/values/overlayable.xml
similarity index 70%
copy from core/tests/overlaytests/remount/target/res/values/values.xml
copy to core/tests/overlaytests/remount/test-apps/Target/res_upgrade/values/overlayable.xml
index b5f444a..4aa5bce 100644
--- a/core/tests/overlaytests/remount/target/res/values/values.xml
+++ b/core/tests/overlaytests/remount/test-apps/Target/res_upgrade/values/overlayable.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
+  ~ 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.
@@ -15,6 +15,10 @@
   ~ limitations under the License.
   -->
 
-<resources xmlns:sharedlib="http://schemas.android.com/apk/res/com.android.overlaytest.remounted.shared_library">
-    <bool name="uses_shared_library_overlaid">@sharedlib:bool/shared_library_overlaid</bool>
+<resources>
+    <overlayable name="TestResources">
+        <policy type="public">
+            <item type="bool" name="target_overlaid" />
+        </policy>
+    </overlayable>
 </resources>
diff --git a/core/tests/overlaytests/remount/target/res/values/values.xml b/core/tests/overlaytests/remount/test-apps/Target/res_upgrade/values/values.xml
similarity index 60%
copy from core/tests/overlaytests/remount/target/res/values/values.xml
copy to core/tests/overlaytests/remount/test-apps/Target/res_upgrade/values/values.xml
index b5f444a..f552cb0 100644
--- a/core/tests/overlaytests/remount/target/res/values/values.xml
+++ b/core/tests/overlaytests/remount/test-apps/Target/res_upgrade/values/values.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
+  ~ 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.
@@ -14,7 +14,10 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-
-<resources xmlns:sharedlib="http://schemas.android.com/apk/res/com.android.overlaytest.remounted.shared_library">
-    <bool name="uses_shared_library_overlaid">@sharedlib:bool/shared_library_overlaid</bool>
-</resources>
+<resources>
+    <!-- This resource has a different id in the updated target app than the base target app to test
+         that the idmap is regenerated when the target is updated. -->
+    <bool name="target_overlaid">false</bool>
+    <public type="bool" name="target_overlaid" id="0x7f0100ff" />
+    <bool name="target_reference">@bool/target_overlaid</bool>
+</resources>
\ No newline at end of file
diff --git a/core/tests/overlaytests/remount/test-apps/overlaid_apex/Android.bp b/core/tests/overlaytests/remount/test-apps/overlaid_apex/Android.bp
new file mode 100644
index 0000000..e6ebd5e
--- /dev/null
+++ b/core/tests/overlaytests/remount/test-apps/overlaid_apex/Android.bp
@@ -0,0 +1,42 @@
+// 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.
+
+genrule {
+  name: "com.android.overlaytest.overlaid.pem",
+  out: ["com.android.overlaytest.overlaid.pem"],
+  cmd: "openssl genrsa -out $(out) 4096",
+}
+
+genrule {
+  name: "com.android.overlaytest.overlaid.pubkey",
+  srcs: [":com.android.overlaytest.overlaid.pem"],
+  out: ["com.android.overlaytest.overlaid.pubkey"],
+  tools: ["avbtool"],
+  cmd: "$(location avbtool) extract_public_key --key $(in) --output $(out)",
+}
+
+apex_key {
+    name: "com.android.overlaytest.overlaid.key",
+    public_key: ":com.android.overlaytest.overlaid.pubkey",
+    private_key: ":com.android.overlaytest.overlaid.pem",
+}
+
+apex {
+    name: "com.android.overlaytest.overlaid",
+    manifest: "manifest.json",
+    file_contexts: ":apex.test-file_contexts",
+    key: "com.android.overlaytest.overlaid.key",
+    apps: ["OverlayRemountedTest_Target"],
+    installable: false,
+}
diff --git a/core/tests/overlaytests/remount/test-apps/overlaid_apex/manifest.json b/core/tests/overlaytests/remount/test-apps/overlaid_apex/manifest.json
new file mode 100644
index 0000000..9a5102f
--- /dev/null
+++ b/core/tests/overlaytests/remount/test-apps/overlaid_apex/manifest.json
@@ -0,0 +1,4 @@
+{
+  "name": "com.android.overlaytest.overlaid",
+  "version": "1"
+}
diff --git a/core/tests/overlaytests/remount/test-apps/overlay_apex/Android.bp b/core/tests/overlaytests/remount/test-apps/overlay_apex/Android.bp
new file mode 100644
index 0000000..07f27ee
--- /dev/null
+++ b/core/tests/overlaytests/remount/test-apps/overlay_apex/Android.bp
@@ -0,0 +1,42 @@
+// 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.
+
+genrule {
+  name: "com.android.overlaytest.overlay.pem",
+  out: ["com.android.overlaytest.overlay.pem"],
+  cmd: "openssl genrsa -out $(out) 4096",
+}
+
+genrule {
+  name: "com.android.overlaytest.overlay.pubkey",
+  srcs: [":com.android.overlaytest.overlay.pem"],
+  out: ["com.android.overlaytest.overlay.pubkey"],
+  tools: ["avbtool"],
+  cmd: "$(location avbtool) extract_public_key --key $(in) --output $(out)",
+}
+
+apex_key {
+    name: "com.android.overlaytest.overlay.key",
+    public_key: ":com.android.overlaytest.overlay.pubkey",
+    private_key: ":com.android.overlaytest.overlay.pem",
+}
+
+apex {
+    name: "com.android.overlaytest.overlay",
+    manifest: "manifest.json",
+    file_contexts: ":apex.test-file_contexts",
+    key: "com.android.overlaytest.overlay.key",
+    apps: ["OverlayRemountedTest_Overlay"],
+    installable: false,
+}
diff --git a/core/tests/overlaytests/remount/test-apps/overlay_apex/manifest.json b/core/tests/overlaytests/remount/test-apps/overlay_apex/manifest.json
new file mode 100644
index 0000000..ac5f846
--- /dev/null
+++ b/core/tests/overlaytests/remount/test-apps/overlay_apex/manifest.json
@@ -0,0 +1,4 @@
+{
+  "name": "com.android.overlaytest.overlay",
+  "version": "1"
+}
\ No newline at end of file
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 19ad6f7..18086ec 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -883,6 +883,12 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "-242787066": {
+      "message": "addTaskToRecentsAnimationIfNeeded, control: %s, task: %s, transit: %s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_RECENTS_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/WindowContainer.java"
+    },
     "-198463978": {
       "message": "updateRotationUnchecked: alwaysSendConfiguration=%b forceRelayout=%b",
       "level": "VERBOSE",
@@ -901,6 +907,12 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/ScreenRotationAnimation.java"
     },
+    "-172900257": {
+      "message": "addTaskToTargets, target: %s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_RECENTS_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RecentsAnimationController.java"
+    },
     "-167822951": {
       "message": "Attempted to add starting window to token with already existing starting window",
       "level": "WARN",
@@ -1201,6 +1213,12 @@
       "group": "WM_SHOW_TRANSACTIONS",
       "at": "com\/android\/server\/wm\/WindowSurfaceController.java"
     },
+    "315395835": {
+      "message": "Trying to add window with invalid user=%d",
+      "level": "WARN",
+      "group": "WM_ERROR",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
     "342460966": {
       "message": "DRAG %s: pos=(%d,%d)",
       "level": "INFO",
@@ -1525,6 +1543,12 @@
       "group": "WM_DEBUG_RECENTS_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RecentsAnimation.java"
     },
+    "854237232": {
+      "message": "addTaskToRecentsAnimationIfNeeded, control: %s, task: %s, transit: %s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_RECENTS_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/Task.java"
+    },
     "873914452": {
       "message": "goodToGo()",
       "level": "DEBUG",
diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp
index 918e7af..05f4d6b 100644
--- a/libs/androidfw/ApkAssets.cpp
+++ b/libs/androidfw/ApkAssets.cpp
@@ -385,7 +385,7 @@
   const StringPiece idmap_data(
       reinterpret_cast<const char*>(idmap_asset->getBuffer(true /*wordAligned*/)),
       static_cast<size_t>(idmap_asset->getLength()));
-  std::unique_ptr<const LoadedIdmap> loaded_idmap = LoadedIdmap::Load(idmap_data);
+  std::unique_ptr<const LoadedIdmap> loaded_idmap = LoadedIdmap::Load(idmap_path, idmap_data);
   if (loaded_idmap == nullptr) {
     LOG(ERROR) << "failed to load IDMAP " << idmap_path;
     return {};
@@ -538,8 +538,9 @@
     // Loaders are invalidated by the app, not the system, so assume they are up to date.
     return true;
   }
+  return (!loaded_idmap_ || loaded_idmap_->IsUpToDate()) &&
+      last_mod_time_ == getFileModDate(path_.c_str());
 
-  return last_mod_time_ == getFileModDate(path_.c_str());
 }
 
 }  // namespace android
diff --git a/libs/androidfw/Idmap.cpp b/libs/androidfw/Idmap.cpp
index 0b2fd9e..eb6ee95 100644
--- a/libs/androidfw/Idmap.cpp
+++ b/libs/androidfw/Idmap.cpp
@@ -20,6 +20,7 @@
 
 #include "android-base/logging.h"
 #include "android-base/stringprintf.h"
+#include "androidfw/misc.h"
 #include "androidfw/ResourceTypes.h"
 #include "androidfw/Util.h"
 #include "utils/ByteOrder.h"
@@ -192,7 +193,9 @@
   return true;
 }
 
-LoadedIdmap::LoadedIdmap(const Idmap_header* header,
+LoadedIdmap::LoadedIdmap(std::string&& idmap_path,
+                         const time_t last_mod_time,
+                         const Idmap_header* header,
                          const Idmap_data_header* data_header,
                          const Idmap_target_entry* target_entries,
                          const Idmap_overlay_entry* overlay_entries,
@@ -201,7 +204,9 @@
        data_header_(data_header),
        target_entries_(target_entries),
        overlay_entries_(overlay_entries),
-       string_pool_(string_pool) {
+       string_pool_(string_pool),
+       idmap_path_(std::move(idmap_path)),
+       idmap_last_mod_time_(last_mod_time) {
 
   size_t length = strnlen(reinterpret_cast<const char*>(header_->overlay_path),
                           arraysize(header_->overlay_path));
@@ -212,7 +217,8 @@
   target_apk_path_.assign(reinterpret_cast<const char*>(header_->target_path), length);
 }
 
-std::unique_ptr<const LoadedIdmap> LoadedIdmap::Load(const StringPiece& idmap_data) {
+std::unique_ptr<const LoadedIdmap> LoadedIdmap::Load(const StringPiece& idmap_path,
+                                                     const StringPiece& idmap_data) {
   ATRACE_CALL();
   if (!IsValidIdmapHeader(idmap_data)) {
     return {};
@@ -275,10 +281,14 @@
 
   // Can't use make_unique because LoadedIdmap constructor is private.
   std::unique_ptr<LoadedIdmap> loaded_idmap = std::unique_ptr<LoadedIdmap>(
-      new LoadedIdmap(header, data_header, target_entries, overlay_entries,
-                      idmap_string_pool.release()));
+      new LoadedIdmap(idmap_path.to_string(), getFileModDate(idmap_path.data()), header,
+                      data_header, target_entries, overlay_entries, idmap_string_pool.release()));
 
   return std::move(loaded_idmap);
 }
 
+bool LoadedIdmap::IsUpToDate() const {
+  return idmap_last_mod_time_ == getFileModDate(idmap_path_.c_str());
+}
+
 }  // namespace android
diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h
index ccb57f3..ecc1ce6 100644
--- a/libs/androidfw/include/androidfw/Idmap.h
+++ b/libs/androidfw/include/androidfw/Idmap.h
@@ -142,7 +142,13 @@
 class LoadedIdmap {
  public:
   // Loads an IDMAP from a chunk of memory. Returns nullptr if the IDMAP data was malformed.
-  static std::unique_ptr<const LoadedIdmap> Load(const StringPiece& idmap_data);
+  static std::unique_ptr<const LoadedIdmap> Load(const StringPiece& idmap_path,
+                                                 const StringPiece& idmap_data);
+
+  // Returns the path to the IDMAP.
+  inline const std::string& IdmapPath() const {
+    return idmap_path_;
+  }
 
   // Returns the path to the RRO (Runtime Resource Overlay) APK for which this IDMAP was generated.
   inline const std::string& OverlayApkPath() const {
@@ -167,6 +173,10 @@
     return OverlayDynamicRefTable(data_header_, overlay_entries_, target_assigned_package_id);
   }
 
+  // Returns whether the idmap file on disk has not been modified since the construction of this
+  // LoadedIdmap.
+  bool IsUpToDate() const;
+
  protected:
   // Exposed as protected so that tests can subclass and mock this class out.
   LoadedIdmap() = default;
@@ -177,13 +187,17 @@
   const Idmap_overlay_entry* overlay_entries_;
   const std::unique_ptr<ResStringPool> string_pool_;
 
+  const std::string idmap_path_;
   std::string overlay_apk_path_;
   std::string target_apk_path_;
+  const time_t idmap_last_mod_time_;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(LoadedIdmap);
 
-  explicit LoadedIdmap(const Idmap_header* header,
+  explicit LoadedIdmap(std::string&& idmap_path,
+                       time_t last_mod_time,
+                       const Idmap_header* header,
                        const Idmap_data_header* data_header,
                        const Idmap_target_entry* target_entries,
                        const Idmap_overlay_entry* overlay_entries,
diff --git a/libs/androidfw/tests/Idmap_test.cpp b/libs/androidfw/tests/Idmap_test.cpp
index 41ba637..7aa0dbb 100644
--- a/libs/androidfw/tests/Idmap_test.cpp
+++ b/libs/androidfw/tests/Idmap_test.cpp
@@ -38,7 +38,7 @@
  protected:
   void SetUp() override {
     // Move to the test data directory so the idmap can locate the overlay APK.
-    std::string original_path = base::GetExecutableDirectory();
+    original_path = base::GetExecutableDirectory();
     chdir(GetTestDataPath().c_str());
 
     system_assets_ = ApkAssets::Load("system/system.apk");
@@ -49,10 +49,14 @@
 
     overlayable_assets_ = ApkAssets::Load("overlayable/overlayable.apk");
     ASSERT_NE(nullptr, overlayable_assets_);
+  }
+
+  void TearDown() override {
     chdir(original_path.c_str());
   }
 
  protected:
+  std::string original_path;
   std::unique_ptr<const ApkAssets> system_assets_;
   std::unique_ptr<const ApkAssets> overlay_assets_;
   std::unique_ptr<const ApkAssets> overlayable_assets_;
@@ -221,8 +225,7 @@
 
 TEST_F(IdmapTest, OverlayLoaderInterop) {
   std::string contents;
-  auto loader_assets = ApkAssets::LoadTable(GetTestDataPath() + "/loader/resources.arsc",
-                                            PROPERTY_LOADER);
+  auto loader_assets = ApkAssets::LoadTable("loader/resources.arsc", PROPERTY_LOADER);
 
   AssetManager2 asset_manager;
   asset_manager.SetApkAssets({overlayable_assets_.get(), loader_assets.get(),
@@ -241,4 +244,25 @@
   ASSERT_EQ(GetStringFromApkAssets(asset_manager, val, cookie), "loader");
 }
 
+TEST_F(IdmapTest, OverlayAssetsIsUpToDate) {
+  std::string idmap_contents;
+  ASSERT_TRUE(base::ReadFileToString("overlay/overlay.idmap", &idmap_contents));
+
+  TemporaryFile temp_file;
+  ASSERT_TRUE(base::WriteStringToFile(idmap_contents, temp_file.path));
+
+  auto apk_assets = ApkAssets::LoadOverlay(temp_file.path);
+  ASSERT_NE(nullptr, apk_assets);
+  ASSERT_TRUE(apk_assets->IsUpToDate());
+
+  unlink(temp_file.path);
+  ASSERT_FALSE(apk_assets->IsUpToDate());
+  sleep(2);
+
+  base::WriteStringToFile("hello", temp_file.path);
+  sleep(2);
+
+  ASSERT_FALSE(apk_assets->IsUpToDate());
+}
+
 }  // namespace
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 48aed34..861eeea 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -394,7 +394,7 @@
     private native Lnb nativeOpenLnbByName(String name);
 
     private native Descrambler nativeOpenDescramblerByHandle(int handle);
-    private native Descrambler nativeOpenDemuxByhandle(int handle);
+    private native int nativeOpenDemuxByhandle(int handle);
 
     private native DvrRecorder nativeOpenDvrRecorder(long bufferSize);
     private native DvrPlayback nativeOpenDvrPlayback(long bufferSize);
@@ -985,7 +985,7 @@
         boolean granted = mTunerResourceManager.requestDescrambler(request, descramblerHandle);
         if (granted) {
             mDescramblerHandle = descramblerHandle[0];
-            nativeOpenDescramblerByHandle(mDescramblerHandle);
+            mDescrambler = nativeOpenDescramblerByHandle(mDescramblerHandle);
         }
         return granted;
     }
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index fc9b91c..a31f177 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -676,8 +676,6 @@
     if (buffer->size() > 0) {
         std::shared_ptr<C2Buffer> c2Buffer = buffer->asC2Buffer();
         if (c2Buffer) {
-            // asC2Buffer clears internal reference, so set the reference again.
-            buffer->copy(c2Buffer);
             switch (c2Buffer->data().type()) {
                 case C2BufferData::LINEAR: {
                     std::unique_ptr<JMediaCodecLinearBlock> context{new JMediaCodecLinearBlock};
@@ -2526,7 +2524,7 @@
     codec->selectAudioPresentation((int32_t)presentationId, (int32_t)programId);
 }
 
-static void android_media_MediaCodec_native_init(JNIEnv *env) {
+static void android_media_MediaCodec_native_init(JNIEnv *env, jclass) {
     ScopedLocalRef<jclass> clazz(
             env, env->FindClass("android/media/MediaCodec"));
     CHECK(clazz.get() != NULL);
@@ -2983,7 +2981,7 @@
 }
 
 static jboolean android_media_MediaCodec_LinearBlock_checkCompatible(
-        JNIEnv *env, jobjectArray codecNames) {
+        JNIEnv *env, jclass, jobjectArray codecNames) {
     std::vector<std::string> names;
     PopulateNamesVector(env, codecNames, &names);
     bool isCompatible = false;
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index ac7fe5d..7579ca5 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -880,10 +880,12 @@
 
 jobject JTuner::openFrontendById(int id) {
     sp<IFrontend> fe;
-    mTuner->openFrontendById(id, [&](Result, const sp<IFrontend>& frontend) {
+    Result res;
+    mTuner->openFrontendById(id, [&](Result r, const sp<IFrontend>& frontend) {
         fe = frontend;
+        res = r;
     });
-    if (fe == nullptr) {
+    if (res != Result::SUCCESS || fe == nullptr) {
         ALOGE("Failed to open frontend");
         return NULL;
     }
@@ -906,7 +908,7 @@
             (jint) jId);
 }
 
-jobject JTuner::getAnalogFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps) {
+jobject JTuner::getAnalogFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities& caps) {
     jclass clazz = env->FindClass("android/media/tv/tuner/frontend/AnalogFrontendCapabilities");
     jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(II)V");
 
@@ -915,7 +917,7 @@
     return env->NewObject(clazz, capsInit, typeCap, sifStandardCap);
 }
 
-jobject JTuner::getAtsc3FrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps) {
+jobject JTuner::getAtsc3FrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities& caps) {
     jclass clazz = env->FindClass("android/media/tv/tuner/frontend/Atsc3FrontendCapabilities");
     jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(IIIIII)V");
 
@@ -930,7 +932,7 @@
             codeRateCap, fecCap, demodOutputFormatCap);
 }
 
-jobject JTuner::getAtscFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps) {
+jobject JTuner::getAtscFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities& caps) {
     jclass clazz = env->FindClass("android/media/tv/tuner/frontend/AtscFrontendCapabilities");
     jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(I)V");
 
@@ -939,7 +941,7 @@
     return env->NewObject(clazz, capsInit, modulationCap);
 }
 
-jobject JTuner::getDvbcFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps) {
+jobject JTuner::getDvbcFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities& caps) {
     jclass clazz = env->FindClass("android/media/tv/tuner/frontend/DvbcFrontendCapabilities");
     jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(III)V");
 
@@ -950,7 +952,7 @@
     return env->NewObject(clazz, capsInit, modulationCap, fecCap, annexCap);
 }
 
-jobject JTuner::getDvbsFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps) {
+jobject JTuner::getDvbsFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities& caps) {
     jclass clazz = env->FindClass("android/media/tv/tuner/frontend/DvbsFrontendCapabilities");
     jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(IJI)V");
 
@@ -961,7 +963,7 @@
     return env->NewObject(clazz, capsInit, modulationCap, innerfecCap, standard);
 }
 
-jobject JTuner::getDvbtFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps) {
+jobject JTuner::getDvbtFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities& caps) {
     jclass clazz = env->FindClass("android/media/tv/tuner/frontend/DvbtFrontendCapabilities");
     jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(IIIIIIZZ)V");
 
@@ -978,7 +980,7 @@
             coderateCap, hierarchyCap, guardIntervalCap, isT2Supported, isMisoSupported);
 }
 
-jobject JTuner::getIsdbs3FrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps) {
+jobject JTuner::getIsdbs3FrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities& caps) {
     jclass clazz = env->FindClass("android/media/tv/tuner/frontend/Isdbs3FrontendCapabilities");
     jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(II)V");
 
@@ -988,7 +990,7 @@
     return env->NewObject(clazz, capsInit, modulationCap, coderateCap);
 }
 
-jobject JTuner::getIsdbsFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps) {
+jobject JTuner::getIsdbsFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities& caps) {
     jclass clazz = env->FindClass("android/media/tv/tuner/frontend/IsdbsFrontendCapabilities");
     jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(II)V");
 
@@ -998,7 +1000,7 @@
     return env->NewObject(clazz, capsInit, modulationCap, coderateCap);
 }
 
-jobject JTuner::getIsdbtFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps) {
+jobject JTuner::getIsdbtFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities& caps) {
     jclass clazz = env->FindClass("android/media/tv/tuner/frontend/IsdbtFrontendCapabilities");
     jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(IIIII)V");
 
@@ -1044,31 +1046,58 @@
     jobject jcaps = NULL;
     switch(feInfo.type) {
         case FrontendType::ANALOG:
-            jcaps = getAnalogFrontendCaps(env, caps);
+            if (FrontendInfo::FrontendCapabilities::hidl_discriminator::analogCaps
+                    == caps.getDiscriminator()) {
+                jcaps = getAnalogFrontendCaps(env, caps);
+            }
             break;
         case FrontendType::ATSC3:
-            jcaps = getAtsc3FrontendCaps(env, caps);
+            if (FrontendInfo::FrontendCapabilities::hidl_discriminator::atsc3Caps
+                    == caps.getDiscriminator()) {
+                jcaps = getAtsc3FrontendCaps(env, caps);
+            }
             break;
         case FrontendType::ATSC:
-            jcaps = getAtscFrontendCaps(env, caps);
+            if (FrontendInfo::FrontendCapabilities::hidl_discriminator::atscCaps
+                    == caps.getDiscriminator()) {
+                jcaps = getAtscFrontendCaps(env, caps);
+            }
             break;
         case FrontendType::DVBC:
-            jcaps = getDvbcFrontendCaps(env, caps);
+            if (FrontendInfo::FrontendCapabilities::hidl_discriminator::dvbcCaps
+                    == caps.getDiscriminator()) {
+                jcaps = getDvbcFrontendCaps(env, caps);
+            }
             break;
         case FrontendType::DVBS:
-            jcaps = getDvbsFrontendCaps(env, caps);
+            if (FrontendInfo::FrontendCapabilities::hidl_discriminator::dvbsCaps
+                    == caps.getDiscriminator()) {
+                jcaps = getDvbsFrontendCaps(env, caps);
+            }
             break;
         case FrontendType::DVBT:
-            jcaps = getDvbtFrontendCaps(env, caps);
+            if (FrontendInfo::FrontendCapabilities::hidl_discriminator::dvbtCaps
+                    == caps.getDiscriminator()) {
+                jcaps = getDvbtFrontendCaps(env, caps);
+            }
             break;
         case FrontendType::ISDBS:
-            jcaps = getIsdbsFrontendCaps(env, caps);
+            if (FrontendInfo::FrontendCapabilities::hidl_discriminator::isdbsCaps
+                    == caps.getDiscriminator()) {
+                jcaps = getIsdbsFrontendCaps(env, caps);
+            }
             break;
         case FrontendType::ISDBS3:
-            jcaps = getIsdbs3FrontendCaps(env, caps);
+            if (FrontendInfo::FrontendCapabilities::hidl_discriminator::isdbs3Caps
+                    == caps.getDiscriminator()) {
+                jcaps = getIsdbs3FrontendCaps(env, caps);
+            }
             break;
         case FrontendType::ISDBT:
-            jcaps = getIsdbtFrontendCaps(env, caps);
+            if (FrontendInfo::FrontendCapabilities::hidl_discriminator::isdbtCaps
+                    == caps.getDiscriminator()) {
+                jcaps = getIsdbtFrontendCaps(env, caps);
+            }
             break;
         default:
             break;
@@ -2308,7 +2337,7 @@
     gFields.dvrPlaybackContext = env->GetFieldID(dvrPlaybackClazz, "mNativeContext", "J");
     gFields.dvrPlaybackInitID = env->GetMethodID(dvrPlaybackClazz, "<init>", "()V");
     gFields.onDvrPlaybackStatusID =
-            env->GetMethodID(dvrRecorderClazz, "onPlaybackStatusChanged", "(I)V");
+            env->GetMethodID(dvrPlaybackClazz, "onPlaybackStatusChanged", "(I)V");
 
     jclass linearBlockClazz = env->FindClass("android/media/MediaCodec$LinearBlock");
     gFields.linearBlockInitID = env->GetMethodID(linearBlockClazz, "<init>", "()V");
@@ -3101,6 +3130,11 @@
     return tuner->getDemuxCaps();
 }
 
+static jint android_media_tv_Tuner_open_demux(JNIEnv* env, jobject thiz, jint /* handle */) {
+    sp<JTuner> tuner = getTuner(env, thiz);
+    return (jint) tuner->openDemux();
+}
+
 static jint android_media_tv_Tuner_attach_filter(JNIEnv *env, jobject dvr, jobject filter) {
     sp<Dvr> dvrSp = getDvr(env, dvr);
     if (dvrSp == NULL) {
@@ -3425,6 +3459,7 @@
             (void *)android_media_tv_Tuner_open_dvr_playback },
     { "nativeGetDemuxCapabilities", "()Landroid/media/tv/tuner/DemuxCapabilities;",
             (void *)android_media_tv_Tuner_get_demux_caps },
+    { "nativeOpenDemuxByhandle", "(I)I", (void *)android_media_tv_Tuner_open_demux },
 };
 
 static const JNINativeMethod gFilterMethods[] = {
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index 73fc38d..6749ba0 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -188,9 +188,9 @@
     jobject openDvr(DvrType type, jlong bufferSize);
     jobject getDemuxCaps();
     jobject getFrontendStatus(jintArray types);
+    Result openDemux();
 
 protected:
-    Result openDemux();
     virtual ~JTuner();
 
 private:
@@ -204,15 +204,15 @@
     sp<ILnb> mLnb;
     sp<IDemux> mDemux;
     uint32_t mDemuxId;
-    static jobject getAnalogFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps);
-    static jobject getAtsc3FrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps);
-    static jobject getAtscFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps);
-    static jobject getDvbcFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps);
-    static jobject getDvbsFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps);
-    static jobject getDvbtFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps);
-    static jobject getIsdbs3FrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps);
-    static jobject getIsdbsFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps);
-    static jobject getIsdbtFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps);
+    static jobject getAnalogFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities& caps);
+    static jobject getAtsc3FrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities& caps);
+    static jobject getAtscFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities& caps);
+    static jobject getDvbcFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities& caps);
+    static jobject getDvbsFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities& caps);
+    static jobject getDvbtFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities& caps);
+    static jobject getIsdbs3FrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities& caps);
+    static jobject getIsdbsFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities& caps);
+    static jobject getIsdbtFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities& caps);
 };
 
 }  // namespace android
diff --git a/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java b/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java
index ff40d8e..450bdb1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java
+++ b/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java
@@ -202,6 +202,12 @@
     }
 
     /**
+     * Gives descendants a chance to log Preference click event
+     */
+    protected void logPreferenceClick(Intent intent) {
+    }
+
+    /**
      * Returns the settings parsed from the attributes of the
      * {@link SettingInjectorService#META_DATA_NAME} tag, or null.
      *
@@ -315,6 +321,7 @@
             // Settings > Location.
             Intent settingIntent = new Intent();
             settingIntent.setClassName(mInfo.packageName, mInfo.settingsActivity);
+            logPreferenceClick(settingIntent);
             // Sometimes the user may navigate back to "Settings" and launch another different
             // injected setting after one injected setting has been launched.
             //
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
index 9d7e2c8..b1234f2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
@@ -18,11 +18,15 @@
 
 import android.content.Context;
 import android.net.NetworkTemplate;
+import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.util.Log;
 
 import com.android.internal.util.ArrayUtils;
+
+import java.util.List;
+
 /**
  * Utils class for data usage
  */
@@ -33,26 +37,42 @@
      * Return mobile NetworkTemplate based on {@code subId}
      */
     public static NetworkTemplate getMobileTemplate(Context context, int subId) {
-        final TelephonyManager telephonyManager = context.getSystemService(
-                TelephonyManager.class);
-        final SubscriptionManager subscriptionManager = context.getSystemService(
-                SubscriptionManager.class);
-        final NetworkTemplate mobileAll = NetworkTemplate.buildTemplateMobileAll(
-                telephonyManager.getSubscriberId());
+        final TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class);
+        final int mobileDefaultSubId = telephonyManager.getSubscriptionId();
 
-        if (!subscriptionManager.isActiveSubscriptionId(subId)) {
-            Log.i(TAG, "Subscription is not active: " + subId);
-            return mobileAll;
+        final SubscriptionManager subscriptionManager =
+                context.getSystemService(SubscriptionManager.class);
+        final List<SubscriptionInfo> subInfoList =
+                subscriptionManager.getAvailableSubscriptionInfoList();
+        if (subInfoList == null) {
+            Log.i(TAG, "Subscription is not inited: " + subId);
+            return getMobileTemplateForSubId(telephonyManager, mobileDefaultSubId);
         }
 
-        final String[] mergedSubscriberIds = telephonyManager.createForSubscriptionId(subId)
-                .getMergedImsisFromGroup();
+        for (SubscriptionInfo subInfo : subInfoList) {
+            if ((subInfo != null) && (subInfo.getSubscriptionId() == subId)) {
+                return getNormalizedMobileTemplate(telephonyManager, subId);
+            }
+        }
+        Log.i(TAG, "Subscription is not active: " + subId);
+        return getMobileTemplateForSubId(telephonyManager, mobileDefaultSubId);
+    }
 
+    private static NetworkTemplate getNormalizedMobileTemplate(
+            TelephonyManager telephonyManager, int subId) {
+        final NetworkTemplate mobileTemplate = getMobileTemplateForSubId(telephonyManager, subId);
+        final String[] mergedSubscriberIds = telephonyManager
+                .createForSubscriptionId(subId).getMergedImsisFromGroup();
         if (ArrayUtils.isEmpty(mergedSubscriberIds)) {
             Log.i(TAG, "mergedSubscriberIds is null.");
-            return mobileAll;
+            return mobileTemplate;
         }
 
-        return NetworkTemplate.normalize(mobileAll, mergedSubscriberIds);
+        return NetworkTemplate.normalize(mobileTemplate, mergedSubscriberIds);
+    }
+
+    private static NetworkTemplate getMobileTemplateForSubId(
+            TelephonyManager telephonyManager, int subId) {
+        return NetworkTemplate.buildTemplateMobileAll(telephonyManager.getSubscriberId(subId));
     }
 }
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index c6f0327..133d375b 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -670,7 +670,7 @@
         </activity>
 
         <activity android:name=".controls.management.ControlsProviderSelectorActivity"
-                  android:label="Controls Providers"
+                  android:label="@string/controls_providers_title"
                   android:theme="@style/Theme.ControlsManagement"
                   android:showForAllUsers="true"
                   android:clearTaskOnLaunch="true"
@@ -679,6 +679,15 @@
                   android:visibleToInstantApps="true">
         </activity>
 
+        <activity android:name=".controls.management.ControlsEditingActivity"
+                  android:theme="@style/Theme.ControlsManagement"
+                  android:excludeFromRecents="true"
+                  android:showForAllUsers="true"
+                  android:finishOnTaskLaunch="true"
+                  android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
+                  android:visibleToInstantApps="true">
+        </activity>
+
         <activity android:name=".controls.management.ControlsFavoritingActivity"
                   android:theme="@style/Theme.ControlsManagement"
                   android:excludeFromRecents="true"
diff --git a/packages/SystemUI/res/anim/bottomsheet_in.xml b/packages/SystemUI/res/anim/bottomsheet_in.xml
new file mode 100644
index 0000000..0d5efeb
--- /dev/null
+++ b/packages/SystemUI/res/anim/bottomsheet_in.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+     android:interpolator="@*android:anim/accelerate_decelerate_interpolator"
+     android:zAdjustment="top">
+
+    <translate android:fromYDelta="100%"
+               android:toYDelta="0"
+               android:startOffset="@android:integer/config_shortAnimTime"
+               android:duration="@*android:integer/config_mediumAnimTime"/>
+</set>
diff --git a/packages/SystemUI/res/anim/bottomsheet_out.xml b/packages/SystemUI/res/anim/bottomsheet_out.xml
new file mode 100644
index 0000000..01f8d2d
--- /dev/null
+++ b/packages/SystemUI/res/anim/bottomsheet_out.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+     android:interpolator="@*android:anim/accelerate_interpolator"
+     android:zAdjustment="top">
+
+    <translate xmlns:android="http://schemas.android.com/apk/res/android"
+               android:fromYDelta="0"
+               android:toYDelta="100%"
+               android:duration="@*android:integer/config_shortAnimTime" />
+</set>
diff --git a/packages/SystemUI/res/drawable/rounded_bg_top.xml b/packages/SystemUI/res/drawable/rounded_bg_top.xml
new file mode 100644
index 0000000..988ab58
--- /dev/null
+++ b/packages/SystemUI/res/drawable/rounded_bg_top.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle">
+    <solid android:color="?android:attr/colorPrimaryDark" />
+    <corners
+        android:topLeftRadius="?android:attr/dialogCornerRadius"
+        android:topRightRadius="?android:attr/dialogCornerRadius" />
+</shape>
diff --git a/packages/SystemUI/res/layout/app_ops_info.xml b/packages/SystemUI/res/layout/app_ops_info.xml
index bfa252c..8342a2a 100644
--- a/packages/SystemUI/res/layout/app_ops_info.xml
+++ b/packages/SystemUI/res/layout/app_ops_info.xml
@@ -19,8 +19,8 @@
         xmlns:android="http://schemas.android.com/apk/res/android"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
+        android:focusable="true"
         android:id="@+id/app_ops_info"
-        android:clickable="true"
         android:clipChildren="false"
         android:clipToPadding="false"
         android:orientation="vertical"
diff --git a/packages/SystemUI/res/layout/controls_base_item.xml b/packages/SystemUI/res/layout/controls_base_item.xml
index db81e23..55c9083 100644
--- a/packages/SystemUI/res/layout/controls_base_item.xml
+++ b/packages/SystemUI/res/layout/controls_base_item.xml
@@ -45,7 +45,8 @@
         android:textAppearance="@style/TextAppearance.Control.Status"
         android:paddingTop="@dimen/control_padding_adjustment"
         android:paddingStart="@dimen/control_status_padding"
-        android:clickable="true"
+        android:screenReaderFocusable="false"
+        android:clickable="false"
         android:focusable="false"
         android:singleLine="true"
         android:ellipsize="marquee"
diff --git a/packages/SystemUI/res/layout/controls_detail_dialog.xml b/packages/SystemUI/res/layout/controls_detail_dialog.xml
index f2de45a..34b603f 100644
--- a/packages/SystemUI/res/layout/controls_detail_dialog.xml
+++ b/packages/SystemUI/res/layout/controls_detail_dialog.xml
@@ -15,9 +15,76 @@
      limitations under the License.
 -->
 
-<FrameLayout
+<LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/controls_activity_view"
+    android:id="@+id/control_detail_root"
     android:layout_width="match_parent"
-    android:layout_height="match_parent" />
+    android:layout_height="match_parent"
+    android:layout_marginTop="@dimen/controls_activity_view_top_offset"
+    android:orientation="vertical">
+  <LinearLayout
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal"
+    android:layout_marginBottom="10dp">
+    <ImageView
+        android:id="@+id/control_detail_close"
+        android:contentDescription="@string/accessibility_desc_close"
+        android:src="@drawable/ic_close"
+        android:background="?android:attr/selectableItemBackgroundBorderless"
+        android:tint="@color/control_primary_text"
+        android:layout_width="48dp"
+        android:layout_height="48dp"
+        android:padding="12dp" />
+    <Space
+        android:layout_width="0dp"
+        android:layout_weight="1"
+        android:layout_height="1dp" />
+    <ImageView
+        android:id="@+id/control_detail_open_in_app"
+        android:src="@drawable/ic_open_in_new"
+        android:background="?android:attr/selectableItemBackgroundBorderless"
+        android:tint="@color/control_primary_text"
+        android:layout_width="48dp"
+        android:layout_height="48dp"
+        android:padding="12dp" />
+  </LinearLayout>
+
+  <LinearLayout
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:paddingTop="@dimen/controls_activity_view_top_padding"
+      android:paddingLeft="@dimen/controls_activity_view_side_padding"
+      android:paddingRight="@dimen/controls_activity_view_side_padding"
+      android:background="@drawable/rounded_bg_top"
+      android:orientation="vertical">
+    <TextView
+        android:id="@+id/title"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textAppearance="@style/TextAppearance.ControlDialog"
+        android:clickable="false"
+        android:focusable="false"
+        android:maxLines="1"
+        android:ellipsize="end" />
+    <TextView
+        android:id="@+id/subtitle"
+        android:layout_marginTop="6dp"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textAppearance="@style/TextAppearance.ControlDialog"
+        android:clickable="false"
+        android:focusable="false"
+        android:maxLines="1"
+        android:ellipsize="end" />
+
+    <FrameLayout
+        android:id="@+id/controls_activity_view"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_marginTop="10dp"
+        android:layout_weight="1" />
+
+  </LinearLayout>
+</LinearLayout>
 
diff --git a/packages/SystemUI/res/layout/controls_horizontal_divider_withEmpty.xml b/packages/SystemUI/res/layout/controls_horizontal_divider_withEmpty.xml
new file mode 100644
index 0000000..90b3398
--- /dev/null
+++ b/packages/SystemUI/res/layout/controls_horizontal_divider_withEmpty.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+>
+
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/controls_management_list_margin"
+        />
+
+    <FrameLayout
+        android:id="@+id/frame"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/control_height"
+        android:visibility="gone"
+    >
+    </FrameLayout>
+    <View
+        android:id="@+id/divider"
+        android:layout_width="match_parent"
+        android:layout_height="1dp"
+        android:layout_marginBottom="10dp"
+        android:layout_marginStart="40dp"
+        android:layout_marginEnd="40dp"
+        android:background="#4dffffff" />
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/controls_management.xml b/packages/SystemUI/res/layout/controls_management.xml
index 9d5eb63..ae57563 100644
--- a/packages/SystemUI/res/layout/controls_management.xml
+++ b/packages/SystemUI/res/layout/controls_management.xml
@@ -29,6 +29,8 @@
         android:orientation="horizontal"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
+        android:focusable="false"
+        android:clickable="false"
         android:gravity="center_vertical">
 
         <FrameLayout
diff --git a/packages/SystemUI/res/layout/controls_management_apps.xml b/packages/SystemUI/res/layout/controls_management_apps.xml
index 42d73f3..94df9d8 100644
--- a/packages/SystemUI/res/layout/controls_management_apps.xml
+++ b/packages/SystemUI/res/layout/controls_management_apps.xml
@@ -14,18 +14,11 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<androidx.core.widget.NestedScrollView
+<androidx.recyclerview.widget.RecyclerView
     xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/list"
     android:layout_width="match_parent"
-    android:layout_height="0dp"
-    android:layout_weight="1"
-    android:orientation="vertical"
-    android:layout_marginTop="@dimen/controls_management_list_margin">
+    android:layout_height="match_parent"
+    android:layout_marginTop="@dimen/controls_management_list_margin"
+/>
 
-    <androidx.recyclerview.widget.RecyclerView
-        android:id="@+id/list"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-    />
-
-</androidx.core.widget.NestedScrollView>
diff --git a/core/tests/overlaytests/remount/target/res/values/values.xml b/packages/SystemUI/res/layout/controls_management_editing.xml
similarity index 60%
copy from core/tests/overlaytests/remount/target/res/values/values.xml
copy to packages/SystemUI/res/layout/controls_management_editing.xml
index b5f444a..8a14ec3 100644
--- a/core/tests/overlaytests/remount/target/res/values/values.xml
+++ b/packages/SystemUI/res/layout/controls_management_editing.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
+  ~ 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.
@@ -15,6 +15,13 @@
   ~ limitations under the License.
   -->
 
-<resources xmlns:sharedlib="http://schemas.android.com/apk/res/com.android.overlaytest.remounted.shared_library">
-    <bool name="uses_shared_library_overlaid">@sharedlib:bool/shared_library_overlaid</bool>
-</resources>
+<androidx.recyclerview.widget.RecyclerView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/list"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:clipChildren="false"
+    android:clipToPadding="false"
+    android:paddingTop="@dimen/controls_management_list_margin"
+/>
+
diff --git a/packages/SystemUI/res/layout/controls_spinner_item.xml b/packages/SystemUI/res/layout/controls_spinner_item.xml
index 00654c8..45540f1 100644
--- a/packages/SystemUI/res/layout/controls_spinner_item.xml
+++ b/packages/SystemUI/res/layout/controls_spinner_item.xml
@@ -30,6 +30,7 @@
         android:layout_gravity="center"
         android:layout_width="@dimen/controls_header_app_icon_size"
         android:layout_height="@dimen/controls_header_app_icon_size"
+        android:contentDescription="@null"
         android:layout_marginEnd="10dp" />
 
     <TextView
diff --git a/packages/SystemUI/res/layout/controls_with_favorites.xml b/packages/SystemUI/res/layout/controls_with_favorites.xml
index 623f2a0..b323209 100644
--- a/packages/SystemUI/res/layout/controls_with_favorites.xml
+++ b/packages/SystemUI/res/layout/controls_with_favorites.xml
@@ -34,6 +34,7 @@
         android:orientation="horizontal"
         android:layout_width="0dp"
         android:layout_weight="1"
+        android:minHeight="48dp"
         android:layout_height="wrap_content"
         android:layout_gravity="center"
         android:gravity="center">
@@ -43,6 +44,7 @@
           android:layout_gravity="center"
           android:layout_width="@dimen/controls_header_app_icon_size"
           android:layout_height="@dimen/controls_header_app_icon_size"
+          android:contentDescription="@null"
           android:layout_marginEnd="10dp" />
 
       <TextView
diff --git a/packages/SystemUI/res/layout/notification_conversation_info.xml b/packages/SystemUI/res/layout/notification_conversation_info.xml
index 87cb5c7f..9dc502e 100644
--- a/packages/SystemUI/res/layout/notification_conversation_info.xml
+++ b/packages/SystemUI/res/layout/notification_conversation_info.xml
@@ -20,7 +20,7 @@
     android:id="@+id/notification_guts"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:clickable="true"
+    android:focusable="true"
     android:clipChildren="false"
     android:clipToPadding="true"
     android:orientation="vertical"
diff --git a/packages/SystemUI/res/layout/notification_guts.xml b/packages/SystemUI/res/layout/notification_guts.xml
index dc94697..5399f57 100644
--- a/packages/SystemUI/res/layout/notification_guts.xml
+++ b/packages/SystemUI/res/layout/notification_guts.xml
@@ -19,8 +19,8 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
+    android:focusable="true"
     android:id="@+id/notification_guts"
     android:visibility="gone"
-    android:clickable="true"
     android:gravity="top|start"
     android:theme="@*android:style/Theme.DeviceDefault.Light"/>
diff --git a/packages/SystemUI/res/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml
index 73b711d..5b36382 100644
--- a/packages/SystemUI/res/layout/notification_info.xml
+++ b/packages/SystemUI/res/layout/notification_info.xml
@@ -20,7 +20,7 @@
     android:id="@+id/notification_guts"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:clickable="true"
+    android:focusable="true"
     android:clipChildren="false"
     android:clipToPadding="true"
     android:orientation="vertical"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 6a8a4b9..2a4d5ef 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1250,6 +1250,12 @@
     <dimen name="control_base_item_margin">2dp</dimen>
     <dimen name="control_status_padding">3dp</dimen>
 
+    <!-- Home Controls activity view detail panel-->
+    <dimen name="controls_activity_view_top_padding">25dp</dimen>
+    <dimen name="controls_activity_view_side_padding">12dp</dimen>
+    <dimen name="controls_activity_view_top_offset">200dp</dimen>
+    <dimen name="controls_activity_view_text_size">17sp</dimen>
+
     <!-- Home Controls management screens -->
     <dimen name="controls_management_top_padding">48dp</dimen>
     <dimen name="controls_management_side_padding">8dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 566d143..7c0b605 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2670,8 +2670,11 @@
     <string name="controls_favorite_default_title">Controls</string>
     <!-- Controls management controls screen subtitle [CHAR LIMIT=NONE] -->
     <string name="controls_favorite_subtitle">Choose controls to access from the power menu</string>
-    <!-- Controls management controls screen, user direction for rearranging controls [CHAR LIMIT=NONE] -->
-    <string name="controls_favorite_rearrange">Hold and drag a control to move it</string>
+    <!-- Controls management editing screen, user direction for rearranging controls [CHAR LIMIT=NONE] -->
+    <string name="controls_favorite_rearrange">Hold &amp; drag to rearrange controls</string>
+
+    <!-- Controls management editing screen, text to indicate that all the favorites have been removed [CHAR LIMIT=NONE] -->
+    <string name="controls_favorite_removed">All controls removed</string>
 
     <!-- Controls management controls screen error on load message [CHAR LIMIT=60] -->
     <string name="controls_favorite_load_error">The list of all controls could not be loaded.</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 3e02b30..118aa5b 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -671,6 +671,19 @@
         <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
     </style>
 
+    <style name="Theme.SystemUI.Dialog.Control.DetailPanel" parent="@android:style/Theme.DeviceDefault.Dialog.NoActionBar">
+      <item name="android:windowAnimationStyle">@style/Animation.Bottomsheet</item>
+      <item name="android:windowFullscreen">true</item>
+      <item name="android:windowIsFloating">false</item>
+      <item name="android:windowBackground">@null</item>
+      <item name="android:backgroundDimEnabled">true</item>
+    </style>
+
+    <style name="Animation.Bottomsheet">
+      <item name="android:windowEnterAnimation">@anim/bottomsheet_in</item>
+      <item name="android:windowExitAnimation">@anim/bottomsheet_out</item>
+    </style>
+
     <style name="Control" />
 
     <style name="Control.MenuItem">
@@ -713,6 +726,11 @@
         <item name="android:textSize">@dimen/control_text_size</item>
         <item name="android:textColor">@color/control_secondary_text</item>
     </style>
+    <style name="TextAppearance.ControlDialog">
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
+        <item name="android:textSize">@dimen/controls_activity_view_text_size</item>
+        <item name="android:textColor">@color/control_primary_text</item>
+    </style>
     <style name="Control.ListPopupWindow" parent="@*android:style/Widget.DeviceDefault.ListPopupWindow">
         <item name="android:overlapAnchor">true</item>
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
index 3bda3c8..806678f 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -261,6 +261,11 @@
                         animationHandler.onAnimationCanceled(
                                 taskSnapshot != null ? new ThumbnailData(taskSnapshot) : null);
                     }
+
+                    @Override
+                    public void onTaskAppeared(RemoteAnimationTarget app) {
+                        animationHandler.onTaskAppeared(new RemoteAnimationTargetCompat(app));
+                    }
                 };
             }
             ActivityTaskManager.getService().startRecentsActivity(intent, receiver, runner);
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java
index bbb83c7..76513c6 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java
@@ -109,4 +109,16 @@
             Log.e(TAG, "Failed to set overview reached state", e);
         }
     }
+
+    /**
+     * @see IRecentsAnimationController#removeTask
+     */
+    public boolean removeTask(int taskId) {
+        try {
+            return mAnimationController.removeTask(taskId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to remove remote animation target", e);
+            return false;
+        }
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java
index 2c99c5c..c4cd192 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java
@@ -32,4 +32,10 @@
      * Called when the animation into Recents was canceled. This call is made on the binder thread.
      */
     void onAnimationCanceled(ThumbnailData thumbnailData);
+
+    /**
+     * Called when the task of an activity that has been started while the recents animation
+     * was running becomes ready for control.
+     */
+    void onTaskAppeared(RemoteAnimationTargetCompat app);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 61e6f39..71dbbbc 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -1349,7 +1349,8 @@
         if (show
                 && mShouldShowManageEducation
                 && mManageEducationView.getVisibility() != VISIBLE
-                && mIsExpanded) {
+                && mIsExpanded
+                && mExpandedBubble.getExpandedView() != null) {
             mManageEducationView.setAlpha(0);
             mManageEducationView.setVisibility(VISIBLE);
             mManageEducationView.post(() -> {
@@ -1909,7 +1910,8 @@
             Log.d(TAG, "updateExpandedBubble()");
         }
         mExpandedViewContainer.removeAllViews();
-        if (mIsExpanded && mExpandedBubble != null) {
+        if (mIsExpanded && mExpandedBubble != null
+                && mExpandedBubble.getExpandedView() != null) {
             BubbleExpandedView bev = mExpandedBubble.getExpandedView();
             mExpandedViewContainer.addView(bev);
             bev.populateExpandedView();
@@ -1929,7 +1931,7 @@
             if (!mExpandedViewYAnim.isRunning()) {
                 // We're not animating so set the value
                 mExpandedViewContainer.setTranslationY(y);
-                if (mExpandedBubble != null) {
+                if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
                     mExpandedBubble.getExpandedView().updateView();
                 }
             } else {
@@ -1967,7 +1969,7 @@
     }
 
     private void updatePointerPosition() {
-        if (mExpandedBubble == null) {
+        if (mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) {
             return;
         }
         int index = getBubbleIndex(mExpandedBubble);
@@ -2049,7 +2051,7 @@
      * a back key down/up event pair is forwarded to the bubble Activity.
      */
     boolean performBackPressIfNeeded() {
-        if (!isExpanded() || mExpandedBubble == null) {
+        if (!isExpanded() || mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) {
             return false;
         }
         return mExpandedBubble.getExpandedView().performBackPressIfNeeded();
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt
index dec6007..5891a7f 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt
@@ -18,10 +18,34 @@
 
 import android.content.ComponentName
 import android.service.controls.Control
+import android.service.controls.DeviceTypes
+
+interface ControlInterface {
+    val favorite: Boolean
+    val component: ComponentName
+    val controlId: String
+    val title: CharSequence
+    val subtitle: CharSequence
+    val removed: Boolean
+        get() = false
+    @DeviceTypes.DeviceType val deviceType: Int
+}
 
 data class ControlStatus(
     val control: Control,
-    val component: ComponentName,
-    var favorite: Boolean,
-    val removed: Boolean = false
-)
+    override val component: ComponentName,
+    override var favorite: Boolean,
+    override val removed: Boolean = false
+) : ControlInterface {
+    override val controlId: String
+        get() = control.controlId
+
+    override val title: CharSequence
+        get() = control.title
+
+    override val subtitle: CharSequence
+        get() = control.subtitle
+
+    @DeviceTypes.DeviceType override val deviceType: Int
+        get() = control.deviceType
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlInfo.kt
index 6e59ac1..40606c2 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlInfo.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.controls.controller
 
+import android.service.controls.Control
 import android.service.controls.DeviceTypes
 
 /**
@@ -39,6 +40,14 @@
 
     companion object {
         private const val SEPARATOR = ":"
+        fun fromControl(control: Control): ControlInfo {
+            return ControlInfo(
+                    control.controlId,
+                    control.title,
+                    control.subtitle,
+                    control.deviceType
+            )
+        }
     }
 
     /**
@@ -49,13 +58,4 @@
     override fun toString(): String {
         return "$SEPARATOR$controlId$SEPARATOR$controlTitle$SEPARATOR$deviceType"
     }
-
-    class Builder {
-        lateinit var controlId: String
-        lateinit var controlTitle: CharSequence
-        lateinit var controlSubtitle: CharSequence
-        var deviceType: Int = DeviceTypes.TYPE_UNKNOWN
-
-        fun build() = ControlInfo(controlId, controlTitle, controlSubtitle, deviceType)
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
index 568fb28..7cab847 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
@@ -148,6 +148,18 @@
     fun getFavoritesForComponent(componentName: ComponentName): List<StructureInfo>
 
     /**
+     * Get all the favorites for a given structure.
+     *
+     * @param componentName the name of the service that provides the [Control]
+     * @param structureName the name of the structure
+     * @return a list of the current favorites in that structure
+     */
+    fun getFavoritesForStructure(
+        componentName: ComponentName,
+        structureName: CharSequence
+    ): List<ControlInfo>
+
+    /**
      * Adds a single favorite to a given component and structure
      * @param componentName the name of the service that provides the [Control]
      * @param structureName the name of the structure that holds the [Control]
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
index 8805694..6d34009 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -544,6 +544,15 @@
     override fun getFavoritesForComponent(componentName: ComponentName): List<StructureInfo> =
         Favorites.getStructuresForComponent(componentName)
 
+    override fun getFavoritesForStructure(
+        componentName: ComponentName,
+        structureName: CharSequence
+    ): List<ControlInfo> {
+        return Favorites.getControlsForStructure(
+                StructureInfo(componentName, structureName, emptyList())
+        )
+    }
+
     override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
         pw.println("ControlsController state:")
         pw.println("  Available: $available")
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
index 946a236..3bed559 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.controller.ControlsControllerImpl
 import com.android.systemui.controls.controller.ControlsFavoritePersistenceWrapper
+import com.android.systemui.controls.management.ControlsEditingActivity
 import com.android.systemui.controls.management.ControlsFavoritingActivity
 import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.controls.management.ControlsListingControllerImpl
@@ -73,6 +74,13 @@
 
     @Binds
     @IntoMap
+    @ClassKey(ControlsEditingActivity::class)
+    abstract fun provideControlsEditingActivity(
+        activity: ControlsEditingActivity
+    ): Activity
+
+    @Binds
+    @IntoMap
     @ClassKey(ControlsRequestDialog::class)
     abstract fun provideControlsRequestDialog(
         activity: ControlsRequestDialog
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt b/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt
index 11181e5..175ed06 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt
@@ -37,23 +37,22 @@
  * @property controls List of controls as returned by loading
  * @property initialFavoriteIds sorted ids of favorite controls.
  * @property noZoneString text to use as header for all controls that have blank or `null` zone.
+ * @property controlsModelCallback callback to notify that favorites have changed for the first time
  */
 class AllModel(
     private val controls: List<ControlStatus>,
     initialFavoriteIds: List<String>,
-    private val emptyZoneString: CharSequence
+    private val emptyZoneString: CharSequence,
+    private val controlsModelCallback: ControlsModel.ControlsModelCallback
 ) : ControlsModel {
 
-    override val favorites: List<ControlInfo.Builder>
+    private var modified = false
+
+    override val favorites: List<ControlInfo>
         get() = favoriteIds.mapNotNull { id ->
             val control = controls.firstOrNull { it.control.controlId == id }?.control
             control?.let {
-                ControlInfo.Builder().apply {
-                    controlId = it.controlId
-                    controlTitle = it.title
-                    controlSubtitle = it.subtitle
-                    deviceType = it.deviceType
-                }
+                ControlInfo.fromControl(it)
             }
         }
 
@@ -66,14 +65,18 @@
 
     override fun changeFavoriteStatus(controlId: String, favorite: Boolean) {
         val toChange = elements.firstOrNull {
-            it is ControlWrapper && it.controlStatus.control.controlId == controlId
-        } as ControlWrapper?
+            it is ControlStatusWrapper && it.controlStatus.control.controlId == controlId
+        } as ControlStatusWrapper?
         if (favorite == toChange?.controlStatus?.favorite) return
-        if (favorite) {
+        val changed: Boolean = if (favorite) {
             favoriteIds.add(controlId)
         } else {
             favoriteIds.remove(controlId)
         }
+        if (changed && !modified) {
+            modified = true
+            controlsModelCallback.onFirstChange()
+        }
         toChange?.let {
             it.controlStatus.favorite = favorite
         }
@@ -84,9 +87,9 @@
             it.control.zone ?: ""
         }
         val output = mutableListOf<ElementWrapper>()
-        var emptyZoneValues: Sequence<ControlWrapper>? = null
+        var emptyZoneValues: Sequence<ControlStatusWrapper>? = null
         for (zoneName in map.orderedKeys) {
-            val values = map.getValue(zoneName).asSequence().map { ControlWrapper(it) }
+            val values = map.getValue(zoneName).asSequence().map { ControlStatusWrapper(it) }
             if (TextUtils.isEmpty(zoneName)) {
                 emptyZoneValues = values
             } else {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
index 1291dd9..607934c 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
@@ -28,6 +28,7 @@
 import androidx.recyclerview.widget.GridLayoutManager
 import androidx.recyclerview.widget.RecyclerView
 import com.android.systemui.R
+import com.android.systemui.controls.ControlInterface
 import com.android.systemui.controls.ui.RenderInfo
 
 private typealias ModelFavoriteChanger = (String, Boolean) -> Unit
@@ -35,11 +36,10 @@
 /**
  * Adapter for binding [Control] information to views.
  *
- * The model for this adapter is provided by a [FavoriteModel] that is set using
+ * The model for this adapter is provided by a [ControlModel] that is set using
  * [changeFavoritesModel]. This allows for updating the model if there's a reload.
  *
- * @param layoutInflater an inflater for the views in the containing [RecyclerView]
- * @param onlyFavorites set to true to only display favorites instead of all controls
+ * @property elevation elevation of each control view
  */
 class ControlAdapter(
     private val elevation: Float
@@ -48,11 +48,12 @@
     companion object {
         private const val TYPE_ZONE = 0
         private const val TYPE_CONTROL = 1
+        private const val TYPE_DIVIDER = 2
     }
 
     val spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
         override fun getSpanSize(position: Int): Int {
-            return if (getItemViewType(position) == TYPE_ZONE) 2 else 1
+            return if (getItemViewType(position) != TYPE_CONTROL) 2 else 1
         }
     }
 
@@ -78,6 +79,10 @@
             TYPE_ZONE -> {
                 ZoneHolder(layoutInflater.inflate(R.layout.controls_zone_header, parent, false))
             }
+            TYPE_DIVIDER -> {
+                DividerHolder(layoutInflater.inflate(
+                        R.layout.controls_horizontal_divider_withEmpty, parent, false))
+            }
             else -> throw IllegalStateException("Wrong viewType: $viewType")
         }
     }
@@ -95,11 +100,26 @@
         }
     }
 
+    override fun onBindViewHolder(holder: Holder, position: Int, payloads: MutableList<Any>) {
+        if (payloads.isEmpty()) {
+            super.onBindViewHolder(holder, position, payloads)
+        } else {
+            model?.let {
+                val el = it.elements[position]
+                if (el is ControlInterface) {
+                    holder.updateFavorite(el.favorite)
+                }
+            }
+        }
+    }
+
     override fun getItemViewType(position: Int): Int {
         model?.let {
             return when (it.elements.get(position)) {
                 is ZoneNameWrapper -> TYPE_ZONE
-                is ControlWrapper -> TYPE_CONTROL
+                is ControlStatusWrapper -> TYPE_CONTROL
+                is ControlInfoWrapper -> TYPE_CONTROL
+                is DividerWrapper -> TYPE_DIVIDER
             }
         } ?: throw IllegalStateException("Getting item type for null model")
     }
@@ -115,6 +135,24 @@
      * Bind the data from the model into the view
      */
     abstract fun bindData(wrapper: ElementWrapper)
+
+    open fun updateFavorite(favorite: Boolean) {}
+}
+
+/**
+ * Holder for using with [DividerWrapper] to display a divider between zones.
+ *
+ * The divider can be shown or hidden. It also has a frame view the height of a control, that can
+ * be toggled visible or gone.
+ */
+private class DividerHolder(view: View) : Holder(view) {
+    private val frame: View = itemView.requireViewById(R.id.frame)
+    private val divider: View = itemView.requireViewById(R.id.divider)
+    override fun bindData(wrapper: ElementWrapper) {
+        wrapper as DividerWrapper
+        frame.visibility = if (wrapper.showNone) View.VISIBLE else View.GONE
+        divider.visibility = if (wrapper.showDivider) View.VISIBLE else View.GONE
+    }
 }
 
 /**
@@ -130,11 +168,14 @@
 }
 
 /**
- * Holder for using with [ControlWrapper] to display names of zones.
+ * Holder for using with [ControlStatusWrapper] to display names of zones.
  * @param favoriteCallback this callback will be called whenever the favorite state of the
  *                         [Control] this view represents changes.
  */
-private class ControlHolder(view: View, val favoriteCallback: ModelFavoriteChanger) : Holder(view) {
+internal class ControlHolder(
+    view: View,
+    val favoriteCallback: ModelFavoriteChanger
+) : Holder(view) {
     private val icon: ImageView = itemView.requireViewById(R.id.icon)
     private val title: TextView = itemView.requireViewById(R.id.title)
     private val subtitle: TextView = itemView.requireViewById(R.id.subtitle)
@@ -144,20 +185,23 @@
     }
 
     override fun bindData(wrapper: ElementWrapper) {
-        wrapper as ControlWrapper
-        val data = wrapper.controlStatus
-        val renderInfo = getRenderInfo(data.component, data.control.deviceType)
-        title.text = data.control.title
-        subtitle.text = data.control.subtitle
-        favorite.isChecked = data.favorite
-        removed.text = if (data.removed) "Removed" else ""
+        wrapper as ControlInterface
+        val renderInfo = getRenderInfo(wrapper.component, wrapper.deviceType)
+        title.text = wrapper.title
+        subtitle.text = wrapper.subtitle
+        favorite.isChecked = wrapper.favorite
+        removed.text = if (wrapper.removed) "Removed" else ""
         itemView.setOnClickListener {
             favorite.isChecked = !favorite.isChecked
-            favoriteCallback(data.control.controlId, favorite.isChecked)
+            favoriteCallback(wrapper.controlId, favorite.isChecked)
         }
         applyRenderInfo(renderInfo)
     }
 
+    override fun updateFavorite(favorite: Boolean) {
+        this.favorite.isChecked = favorite
+    }
+
     private fun getRenderInfo(
         component: ComponentName,
         @DeviceTypes.DeviceType deviceType: Int
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
new file mode 100644
index 0000000..ee1ce7a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
@@ -0,0 +1,176 @@
+/*
+ * 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.systemui.controls.management
+
+import android.app.Activity
+import android.content.ComponentName
+import android.content.Intent
+import android.os.Bundle
+import android.view.View
+import android.view.ViewStub
+import android.widget.Button
+import android.widget.TextView
+import androidx.recyclerview.widget.GridLayoutManager
+import androidx.recyclerview.widget.ItemTouchHelper
+import androidx.recyclerview.widget.RecyclerView
+import com.android.systemui.R
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.controls.controller.ControlsControllerImpl
+import com.android.systemui.controls.controller.StructureInfo
+import com.android.systemui.settings.CurrentUserTracker
+import javax.inject.Inject
+
+/**
+ * Activity for rearranging and removing controls for a given structure
+ */
+class ControlsEditingActivity @Inject constructor(
+    private val controller: ControlsControllerImpl,
+    broadcastDispatcher: BroadcastDispatcher
+) : Activity() {
+
+    companion object {
+        private const val TAG = "ControlsEditingActivity"
+        private const val EXTRA_STRUCTURE = ControlsFavoritingActivity.EXTRA_STRUCTURE
+        private val SUBTITLE_ID = R.string.controls_favorite_rearrange
+        private val EMPTY_TEXT_ID = R.string.controls_favorite_removed
+    }
+
+    private lateinit var component: ComponentName
+    private lateinit var structure: CharSequence
+    private lateinit var model: FavoritesModel
+    private lateinit var subtitle: TextView
+    private lateinit var saveButton: View
+
+    private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) {
+        private val startingUser = controller.currentUserId
+
+        override fun onUserSwitched(newUserId: Int) {
+            if (newUserId != startingUser) {
+                stopTracking()
+                finish()
+            }
+        }
+    }
+
+    override fun onBackPressed() {
+        finish()
+    }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        intent.getParcelableExtra<ComponentName>(Intent.EXTRA_COMPONENT_NAME)?.let {
+            component = it
+        } ?: run(this::finish)
+
+        intent.getCharSequenceExtra(EXTRA_STRUCTURE)?.let {
+            structure = it
+        } ?: run(this::finish)
+
+        bindViews()
+
+        bindButtons()
+
+        setUpList()
+
+        currentUserTracker.startTracking()
+    }
+
+    private fun bindViews() {
+        setContentView(R.layout.controls_management)
+        requireViewById<ViewStub>(R.id.stub).apply {
+            layoutResource = R.layout.controls_management_editing
+            inflate()
+        }
+        requireViewById<TextView>(R.id.title).text = structure
+        subtitle = requireViewById<TextView>(R.id.subtitle).apply {
+            setText(SUBTITLE_ID)
+        }
+    }
+
+    private fun bindButtons() {
+        requireViewById<Button>(R.id.other_apps).apply {
+            visibility = View.VISIBLE
+            setText(R.string.controls_menu_add)
+            setOnClickListener {
+                saveFavorites()
+                val intent = Intent(this@ControlsEditingActivity,
+                        ControlsFavoritingActivity::class.java).apply {
+                    putExtras(this@ControlsEditingActivity.intent)
+                    putExtra(ControlsFavoritingActivity.EXTRA_SINGLE_STRUCTURE, true)
+                }
+                startActivity(intent)
+                finish()
+            }
+        }
+
+        saveButton = requireViewById<Button>(R.id.done).apply {
+            isEnabled = false
+            setText(R.string.save)
+            setOnClickListener {
+                saveFavorites()
+                finishAffinity()
+            }
+        }
+    }
+
+    private fun saveFavorites() {
+        controller.replaceFavoritesForStructure(
+                StructureInfo(component, structure, model.favorites))
+    }
+
+    private val favoritesModelCallback = object : FavoritesModel.FavoritesModelCallback {
+        override fun onNoneChanged(showNoFavorites: Boolean) {
+            if (showNoFavorites) {
+                subtitle.setText(EMPTY_TEXT_ID)
+            } else {
+                subtitle.setText(SUBTITLE_ID)
+            }
+        }
+
+        override fun onFirstChange() {
+            saveButton.isEnabled = true
+        }
+    }
+
+    private fun setUpList() {
+        val controls = controller.getFavoritesForStructure(component, structure)
+        model = FavoritesModel(component, controls, favoritesModelCallback)
+        val elevation = resources.getFloat(R.dimen.control_card_elevation)
+        val adapter = ControlAdapter(elevation)
+        val recycler = requireViewById<RecyclerView>(R.id.list)
+        val margin = resources
+                .getDimensionPixelSize(R.dimen.controls_card_margin)
+        val itemDecorator = MarginItemDecorator(margin, margin)
+
+        recycler.apply {
+            this.adapter = adapter
+            layoutManager = GridLayoutManager(recycler.context, 2).apply {
+                spanSizeLookup = adapter.spanSizeLookup
+            }
+            addItemDecoration(itemDecorator)
+        }
+        adapter.changeModel(model)
+        model.attachAdapter(adapter)
+        ItemTouchHelper(model.itemTouchHelperCallback).attachToRecyclerView(recycler)
+    }
+
+    override fun onDestroy() {
+        currentUserTracker.stopTracking()
+        super.onDestroy()
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
index fe1e632..6f34dee 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
@@ -61,6 +61,7 @@
 
         // If provided, show this structure page first
         const val EXTRA_STRUCTURE = "extra_structure"
+        const val EXTRA_SINGLE_STRUCTURE = "extra_single_structure"
         private const val TOOLTIP_PREFS_KEY = Prefs.Key.CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT
         private const val TOOLTIP_MAX_SHOWN = 2
     }
@@ -131,6 +132,12 @@
         currentUserTracker.startTracking()
     }
 
+    private val controlsModelCallback = object : ControlsModel.ControlsModelCallback {
+        override fun onFirstChange() {
+            doneButton.isEnabled = true
+        }
+    }
+
     private fun loadControls() {
         component?.let {
             statusText.text = resources.getText(com.android.internal.R.string.loading)
@@ -142,15 +149,20 @@
                 val error = data.errorOnLoad
                 val controlsByStructure = allControls.groupBy { it.control.structure ?: "" }
                 listOfStructures = controlsByStructure.map {
-                    StructureContainer(it.key, AllModel(it.value, favoriteKeys, emptyZoneString))
+                    StructureContainer(it.key, AllModel(
+                            it.value, favoriteKeys, emptyZoneString, controlsModelCallback))
                 }.sortedWith(comparator)
 
                 val structureIndex = listOfStructures.indexOfFirst {
                     sc -> sc.structureName == structureExtra
                 }.let { if (it == -1) 0 else it }
 
+                // If we were requested to show a single structure, set the list to just that one
+                if (intent.getBooleanExtra(EXTRA_SINGLE_STRUCTURE, false)) {
+                    listOfStructures = listOf(listOfStructures[structureIndex])
+                }
+
                 executor.execute {
-                    doneButton.isEnabled = true
                     structurePager.adapter = StructureAdapter(listOfStructures)
                     structurePager.setCurrentItem(structureIndex)
                     if (error) {
@@ -239,8 +251,11 @@
             }
         }
 
+        val title = structureExtra
+            ?: (appName ?: resources.getText(R.string.controls_favorite_default_title))
+        setTitle(title)
         titleView = requireViewById<TextView>(R.id.title).apply {
-            text = appName ?: resources.getText(R.string.controls_favorite_default_title)
+            text = title
         }
         requireViewById<TextView>(R.id.subtitle).text =
                 resources.getText(R.string.controls_favorite_subtitle)
@@ -272,7 +287,7 @@
             setOnClickListener {
                 if (component == null) return@setOnClickListener
                 listOfStructures.forEach {
-                    val favoritesForStorage = it.model.favorites.map { it.build() }
+                    val favoritesForStorage = it.model.favorites
                     controller.replaceFavoritesForStructure(
                         StructureInfo(component!!, it.structureName, favoritesForStorage)
                     )
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt
index a995a2e..37b6d15 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt
@@ -16,6 +16,9 @@
 
 package com.android.systemui.controls.management
 
+import android.content.ComponentName
+import androidx.recyclerview.widget.RecyclerView
+import com.android.systemui.controls.ControlInterface
 import com.android.systemui.controls.ControlStatus
 import com.android.systemui.controls.controller.ControlInfo
 
@@ -27,12 +30,12 @@
 interface ControlsModel {
 
     /**
-     * List of favorites (builders) in order.
+     * List of favorites in order.
      *
      * This should be obtained prior to storing the favorites using
      * [ControlsController.replaceFavoritesForComponent].
      */
-    val favorites: List<ControlInfo.Builder>
+    val favorites: List<ControlInfo>
 
     /**
      * List of all the elements to display by the corresponding [RecyclerView].
@@ -48,6 +51,24 @@
      * Move an item (in elements) from one position to another.
      */
     fun onMoveItem(from: Int, to: Int) {}
+
+    /**
+     * Attach an adapter to the model.
+     *
+     * This can be used to notify the adapter of changes in the model.
+     */
+    fun attachAdapter(adapter: RecyclerView.Adapter<*>) {}
+
+    /**
+     * Callback to notify elements (other than the adapter) of relevant changes in the model.
+     */
+    interface ControlsModelCallback {
+
+        /**
+         * Use to notify that the model has changed for the first time
+         */
+        fun onFirstChange()
+    }
 }
 
 /**
@@ -55,5 +76,29 @@
  * [ControlAdapter].
  */
 sealed class ElementWrapper
+
 data class ZoneNameWrapper(val zoneName: CharSequence) : ElementWrapper()
-data class ControlWrapper(val controlStatus: ControlStatus) : ElementWrapper()
\ No newline at end of file
+
+data class ControlStatusWrapper(
+    val controlStatus: ControlStatus
+) : ElementWrapper(), ControlInterface by controlStatus
+
+data class ControlInfoWrapper(
+    override val component: ComponentName,
+    val controlInfo: ControlInfo,
+    override var favorite: Boolean
+) : ElementWrapper(), ControlInterface {
+    override val controlId: String
+        get() = controlInfo.controlId
+    override val title: CharSequence
+        get() = controlInfo.controlTitle
+    override val subtitle: CharSequence
+        get() = controlInfo.controlSubtitle
+    override val deviceType: Int
+        get() = controlInfo.deviceType
+}
+
+data class DividerWrapper(
+    var showNone: Boolean = false,
+    var showDivider: Boolean = false
+) : ElementWrapper()
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
index 0c41f7e..3be5900 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
@@ -64,6 +64,7 @@
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
+
         setContentView(R.layout.controls_management)
         requireViewById<ViewStub>(R.id.stub).apply {
             layoutResource = R.layout.controls_management_apps
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/FavoriteModel.kt b/packages/SystemUI/src/com/android/systemui/controls/management/FavoriteModel.kt
deleted file mode 100644
index 5c51e3d..0000000
--- a/packages/SystemUI/src/com/android/systemui/controls/management/FavoriteModel.kt
+++ /dev/null
@@ -1,145 +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.systemui.controls.management
-
-import android.text.TextUtils
-import android.util.Log
-import com.android.systemui.controls.ControlStatus
-import java.util.Collections
-import java.util.Comparator
-
-/**
- * Model for keeping track of current favorites and their order.
- *
- * This model is to be used with two [ControlAdapter] one that shows only favorites in the current
- * order and another that shows all controls, separated by zone. When the favorite state of any
- * control is modified or when the favorites are reordered, the adapters are notified of the change.
- *
- * @param listControls list of all the [ControlStatus] to display. This includes controls currently
- *                     marked as favorites as well as those that have been removed (not returned
- *                     from load)
- * @param listFavoritesIds list of the [Control.controlId] for all the favorites, including those
- *                         that have been removed.
- * @param favoritesAdapter [ControlAdapter] used by the [RecyclerView] that shows only favorites
- * @param allAdapter [ControlAdapter] used by the [RecyclerView] that shows all controls
- */
-class FavoriteModel(
-    private val listControls: List<ControlStatus>,
-    listFavoritesIds: List<String>,
-    private val favoritesAdapter: ControlAdapter,
-    private val allAdapter: ControlAdapter
-) {
-
-    companion object {
-        private const val TAG = "FavoriteModel"
-    }
-
-    /**
-     * List of favorite controls ([ControlWrapper]) in order.
-     *
-     * Initially, this list will give a list of wrappers in the order specified by the constructor
-     * variable `listFavoriteIds`.
-     *
-     * As the favorites are added, removed or moved, this list will keep track of those changes.
-     */
-    val favorites: List<ControlWrapper> = listFavoritesIds.map { id ->
-            ControlWrapper(listControls.first { it.control.controlId == id })
-        }.toMutableList()
-
-    /**
-     * List of all controls by zones.
-     *
-     * Lists all the controls with the zone names interleaved as a flat list. After each zone name,
-     * the controls in that zone are listed. Zones are listed in alphabetical order
-     */
-    val all: List<ElementWrapper> = listControls.groupBy { it.control.zone }
-            .mapKeys { it.key ?: "" } // map null to empty
-            .toSortedMap(CharSequenceComparator())
-            .flatMap {
-                val controls = it.value.map { ControlWrapper(it) }
-                if (!TextUtils.isEmpty(it.key)) {
-                    listOf(ZoneNameWrapper(it.key)) + controls
-                } else {
-                    controls
-                }
-            }
-
-    /**
-     * Change the favorite status of a [Control].
-     *
-     * This can be invoked from any of the [ControlAdapter]. It will change the status of that
-     * control and either add it to the list of favorites (at the end) or remove it from it.
-     *
-     * Removing the favorite status from a Removed control will make it disappear completely if
-     * changes are saved.
-     *
-     * @param controlId the id of the [Control] to change the status
-     * @param favorite `true` if and only if it's set to be a favorite.
-     */
-    fun changeFavoriteStatus(controlId: String, favorite: Boolean) {
-        favorites as MutableList
-        val index = all.indexOfFirst {
-            it is ControlWrapper && it.controlStatus.control.controlId == controlId
-        }
-        val control = (all[index] as ControlWrapper).controlStatus
-        if (control.favorite == favorite) {
-            Log.d(TAG, "Changing favorite to same state for ${control.control.controlId} ")
-            return
-        } else {
-            control.favorite = favorite
-        }
-        allAdapter.notifyItemChanged(index)
-        if (favorite) {
-            favorites.add(all[index] as ControlWrapper)
-            favoritesAdapter.notifyItemInserted(favorites.size - 1)
-        } else {
-            val i = favorites.indexOfFirst { it.controlStatus.control.controlId == controlId }
-            favorites.removeAt(i)
-            favoritesAdapter.notifyItemRemoved(i)
-        }
-    }
-
-    /**
-     * Move items in the model and notify the [favoritesAdapter].
-     */
-    fun onMoveItem(from: Int, to: Int) {
-        if (from < to) {
-            for (i in from until to) {
-                Collections.swap(favorites, i, i + 1)
-            }
-        } else {
-            for (i in from downTo to + 1) {
-                Collections.swap(favorites, i, i - 1)
-            }
-        }
-        favoritesAdapter.notifyItemMoved(from, to)
-    }
-}
-
-/**
- * Compares [CharSequence] as [String].
- *
- * It will have empty strings as the first element
- */
-class CharSequenceComparator : Comparator<CharSequence> {
-    override fun compare(p0: CharSequence?, p1: CharSequence?): Int {
-        if (p0 == null && p1 == null) return 0
-        else if (p0 == null && p1 != null) return -1
-        else if (p0 != null && p1 == null) return 1
-        return p0.toString().compareTo(p1.toString())
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/FavoritesModel.kt b/packages/SystemUI/src/com/android/systemui/controls/management/FavoritesModel.kt
new file mode 100644
index 0000000..411170cb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/FavoritesModel.kt
@@ -0,0 +1,221 @@
+/*
+ * 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.systemui.controls.management
+
+import android.content.ComponentName
+import androidx.recyclerview.widget.ItemTouchHelper
+import androidx.recyclerview.widget.RecyclerView
+import com.android.systemui.controls.ControlInterface
+import com.android.systemui.controls.controller.ControlInfo
+import java.util.Collections
+
+/**
+ * Model used to show and rearrange favorites.
+ *
+ * The model will show all the favorite controls and a divider that can be toggled visible/gone.
+ * It will place the items selected as favorites before the divider and the ones unselected after.
+ *
+ * @property componentName used by the [ControlAdapter] to retrieve resources.
+ * @property favorites list of current favorites
+ * @property favoritesModelCallback callback to notify on first change and empty favorites
+ */
+class FavoritesModel(
+    private val componentName: ComponentName,
+    favorites: List<ControlInfo>,
+    private val favoritesModelCallback: FavoritesModelCallback
+) : ControlsModel {
+
+    private var adapter: RecyclerView.Adapter<*>? = null
+    private var modified = false
+
+    override fun attachAdapter(adapter: RecyclerView.Adapter<*>) {
+        this.adapter = adapter
+    }
+
+    override val favorites: List<ControlInfo>
+        get() = elements.take(dividerPosition).map {
+            (it as ControlInfoWrapper).controlInfo
+        }
+
+    override val elements: List<ElementWrapper> = favorites.map {
+        ControlInfoWrapper(componentName, it, true)
+    } + DividerWrapper()
+
+    /**
+     * Indicates the position of the divider to determine
+     */
+    private var dividerPosition = elements.size - 1
+
+    override fun changeFavoriteStatus(controlId: String, favorite: Boolean) {
+        val position = elements.indexOfFirst { it is ControlInterface && it.controlId == controlId }
+        if (position == -1) {
+            return // controlId not found
+        }
+        if (position < dividerPosition && favorite || position > dividerPosition && !favorite) {
+            return // Does not change favorite status
+        }
+        if (favorite) {
+            onMoveItemInternal(position, dividerPosition)
+        } else {
+            onMoveItemInternal(position, elements.size - 1)
+        }
+    }
+
+    override fun onMoveItem(from: Int, to: Int) {
+        onMoveItemInternal(from, to)
+    }
+
+    private fun updateDividerNone(oldDividerPosition: Int, show: Boolean) {
+        (elements[oldDividerPosition] as DividerWrapper).showNone = show
+        favoritesModelCallback.onNoneChanged(show)
+    }
+
+    private fun updateDividerShow(oldDividerPosition: Int, show: Boolean) {
+        (elements[oldDividerPosition] as DividerWrapper).showDivider = show
+    }
+
+    /**
+     * Performs the update in the model.
+     *
+     *   * update the favorite field of the [ControlInterface]
+     *   * update the fields of the [DividerWrapper]
+     *   * move the corresponding element in [elements]
+     *
+     * It may emit the following signals:
+     *   * [RecyclerView.Adapter.notifyItemChanged] if a [ControlInterface.favorite] has changed
+     *     (in the new position) or if the information in [DividerWrapper] has changed (in the
+     *     old position).
+     *   * [RecyclerView.Adapter.notifyItemMoved]
+     *   * [FavoritesModelCallback.onNoneChanged] whenever we go from 1 to 0 favorites and back
+     *   * [ControlsModel.ControlsModelCallback.onFirstChange] upon the first change in the model
+     */
+    private fun onMoveItemInternal(from: Int, to: Int) {
+        if (from == dividerPosition) return // divider does not move
+        var changed = false
+        if (from < dividerPosition && to >= dividerPosition ||
+                from > dividerPosition && to <= dividerPosition) {
+            if (from < dividerPosition && to >= dividerPosition) {
+                // favorite to not favorite
+                (elements[from] as ControlInfoWrapper).favorite = false
+            } else if (from > dividerPosition && to <= dividerPosition) {
+                // not favorite to favorite
+                (elements[from] as ControlInfoWrapper).favorite = true
+            }
+            changed = true
+            updateDivider(from, to)
+        }
+        moveElement(from, to)
+        adapter?.notifyItemMoved(from, to)
+        if (changed) {
+            adapter?.notifyItemChanged(to, Any())
+        }
+        if (!modified) {
+            modified = true
+            favoritesModelCallback.onFirstChange()
+        }
+    }
+
+    private fun updateDivider(from: Int, to: Int) {
+        var dividerChanged = false
+        val oldDividerPosition = dividerPosition
+        if (from < dividerPosition && to >= dividerPosition) { // favorite to not favorite
+            dividerPosition--
+            if (dividerPosition == 0) {
+                updateDividerNone(oldDividerPosition, true)
+                dividerChanged = true
+            }
+            if (dividerPosition == elements.size - 2) {
+                updateDividerShow(oldDividerPosition, true)
+                dividerChanged = true
+            }
+        } else if (from > dividerPosition && to <= dividerPosition) { // not favorite to favorite
+            dividerPosition++
+            if (dividerPosition == 1) {
+                updateDividerNone(oldDividerPosition, false)
+                dividerChanged = true
+            }
+            if (dividerPosition == elements.size - 1) {
+                updateDividerShow(oldDividerPosition, false)
+                dividerChanged = true
+            }
+        }
+        if (dividerChanged) {
+            adapter?.notifyItemChanged(oldDividerPosition)
+        }
+    }
+
+    private fun moveElement(from: Int, to: Int) {
+        if (from < to) {
+            for (i in from until to) {
+                Collections.swap(elements, i, i + 1)
+            }
+        } else {
+            for (i in from downTo to + 1) {
+                Collections.swap(elements, i, i - 1)
+            }
+        }
+    }
+
+    /**
+     * Touch helper to facilitate dragging in the [RecyclerView].
+     *
+     * Only views above the divider line (favorites) can be dragged or accept drops.
+     */
+    val itemTouchHelperCallback = object : ItemTouchHelper.SimpleCallback(0, 0) {
+
+        private val MOVEMENT = ItemTouchHelper.UP or
+                ItemTouchHelper.DOWN or
+                ItemTouchHelper.LEFT or
+                ItemTouchHelper.RIGHT
+
+        override fun onMove(
+            recyclerView: RecyclerView,
+            viewHolder: RecyclerView.ViewHolder,
+            target: RecyclerView.ViewHolder
+        ): Boolean {
+            onMoveItem(viewHolder.adapterPosition, target.adapterPosition)
+            return true
+        }
+
+        override fun getMovementFlags(
+            recyclerView: RecyclerView,
+            viewHolder: RecyclerView.ViewHolder
+        ): Int {
+            if (viewHolder.adapterPosition < dividerPosition) {
+                return ItemTouchHelper.Callback.makeMovementFlags(MOVEMENT, 0)
+            } else {
+                return ItemTouchHelper.Callback.makeMovementFlags(0, 0)
+            }
+        }
+
+        override fun canDropOver(
+            recyclerView: RecyclerView,
+            current: RecyclerView.ViewHolder,
+            target: RecyclerView.ViewHolder
+        ): Boolean {
+            return target.adapterPosition < dividerPosition
+        }
+
+        override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {}
+
+        override fun isItemViewSwipeEnabled() = false
+    }
+
+    interface FavoritesModelCallback : ControlsModel.ControlsModelCallback {
+        fun onNoneChanged(showNoFavorites: Boolean)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt
index 680d006..ad86eeb 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt
@@ -16,19 +16,27 @@
 
 package com.android.systemui.controls.ui
 
+import android.app.Dialog
 import android.app.PendingIntent
 import android.content.Intent
-import android.provider.Settings
+import android.service.controls.Control
 import android.service.controls.actions.BooleanAction
 import android.service.controls.actions.CommandAction
 import android.util.Log
 import android.view.HapticFeedbackConstants
 
+import com.android.systemui.R
+
 object ControlActionCoordinator {
     public const val MIN_LEVEL = 0
     public const val MAX_LEVEL = 10000
 
-    private var useDetailDialog: Boolean? = null
+    private var dialog: Dialog? = null
+
+    fun closeDialog() {
+        dialog?.dismiss()
+        dialog = null
+    }
 
     fun toggle(cvh: ControlViewHolder, templateId: String, isChecked: Boolean) {
         cvh.action(BooleanAction(templateId, !isChecked))
@@ -37,31 +45,39 @@
         cvh.clipLayer.setLevel(nextLevel)
     }
 
-    fun touch(cvh: ControlViewHolder, templateId: String) {
-        cvh.action(CommandAction(templateId))
+    fun touch(cvh: ControlViewHolder, templateId: String, control: Control) {
+        if (cvh.usePanel()) {
+            showDialog(cvh, control.getAppIntent().getIntent())
+        } else {
+            cvh.action(CommandAction(templateId))
+        }
     }
 
+    /**
+     * Allow apps to specify whether they would like to appear in a detail panel or within
+     * the full activity by setting the {@link Control#EXTRA_USE_PANEL} flag. In order for
+     * activities to determine how they are being launched, they should inspect the
+     * {@link Control#EXTRA_USE_PANEL} flag for a value of true.
+     */
     fun longPress(cvh: ControlViewHolder) {
         // Long press snould only be called when there is valid control state, otherwise ignore
         cvh.cws.control?.let {
-            if (useDetailDialog == null) {
-                useDetailDialog = Settings.Secure.getInt(cvh.context.getContentResolver(),
-                    "systemui.controls_use_detail_panel", 0) != 0
-            }
-
             try {
+                it.getAppIntent().send()
                 cvh.layout.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
-                if (useDetailDialog!!) {
-                    DetailDialog(cvh.context, it.getAppIntent()).show()
-                } else {
-                    it.getAppIntent().send()
-                    val closeDialog = Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)
-                    cvh.context.sendBroadcast(closeDialog)
-                }
+                cvh.context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
             } catch (e: PendingIntent.CanceledException) {
                 Log.e(ControlsUiController.TAG, "Error sending pending intent", e)
-                cvh.setTransientStatus("Error opening application")
+                cvh.setTransientStatus(
+                    cvh.context.resources.getString(R.string.controls_error_failed))
             }
         }
     }
+
+    private fun showDialog(cvh: ControlViewHolder, intent: Intent) {
+        dialog = DetailDialog(cvh, intent).also {
+            it.setOnDismissListener { _ -> dialog = null }
+            it.show()
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
index b1cb04e..0eb6cb1 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
@@ -39,16 +39,29 @@
 
 import kotlin.reflect.KClass
 
-private const val UPDATE_DELAY_IN_MILLIS = 3000L
-private const val ALPHA_ENABLED = (255.0 * 0.2).toInt()
-private const val ALPHA_DISABLED = 255
-
+/**
+ * Wraps the widgets that make up the UI representation of a {@link Control}. Updates to the view
+ * are signaled via calls to {@link #bindData}. Similar to the ViewHolder concept used in
+ * RecyclerViews.
+ */
 class ControlViewHolder(
     val layout: ViewGroup,
     val controlsController: ControlsController,
     val uiExecutor: DelayableExecutor,
-    val bgExecutor: DelayableExecutor
+    val bgExecutor: DelayableExecutor,
+    val usePanels: Boolean
 ) {
+
+    companion object {
+        private const val UPDATE_DELAY_IN_MILLIS = 3000L
+        private const val ALPHA_ENABLED = (255.0 * 0.2).toInt()
+        private const val ALPHA_DISABLED = 255
+        private val FORCE_PANEL_DEVICES = setOf(
+            DeviceTypes.TYPE_THERMOSTAT,
+            DeviceTypes.TYPE_CAMERA
+        )
+    }
+
     val icon: ImageView = layout.requireViewById(R.id.icon)
     val status: TextView = layout.requireViewById(R.id.status)
     val title: TextView = layout.requireViewById(R.id.title)
@@ -59,6 +72,8 @@
     var cancelUpdate: Runnable? = null
     var behavior: Behavior? = null
     var lastAction: ControlAction? = null
+    val deviceType: Int
+        get() = cws.control?.let { it.getDeviceType() } ?: cws.ci.deviceType
 
     init {
         val ld = layout.getBackground() as LayerDrawable
@@ -76,7 +91,7 @@
         val (controlStatus, template) = cws.control?.let {
             title.setText(it.getTitle())
             subtitle.setText(it.getSubtitle())
-            Pair(it.getStatus(), it.getControlTemplate())
+            Pair(it.status, it.controlTemplate)
         } ?: run {
             title.setText(cws.ci.controlTitle)
             subtitle.setText(cws.ci.controlSubtitle)
@@ -91,7 +106,7 @@
             })
         }
 
-        val clazz = findBehavior(controlStatus, template)
+        val clazz = findBehavior(controlStatus, template, deviceType)
         if (behavior == null || behavior!!::class != clazz) {
             // Behavior changes can signal a change in template from the app or
             // first time setup
@@ -126,9 +141,17 @@
         controlsController.action(cws.componentName, cws.ci, action)
     }
 
-    private fun findBehavior(status: Int, template: ControlTemplate): KClass<out Behavior> {
+    fun usePanel(): Boolean =
+        usePanels && deviceType in ControlViewHolder.FORCE_PANEL_DEVICES
+
+    private fun findBehavior(
+        status: Int,
+        template: ControlTemplate,
+        deviceType: Int
+    ): KClass<out Behavior> {
         return when {
             status == Control.STATUS_UNKNOWN -> UnknownBehavior::class
+            deviceType == DeviceTypes.TYPE_CAMERA -> TouchBehavior::class
             template is ToggleTemplate -> ToggleBehavior::class
             template is StatelessTemplate -> TouchBehavior::class
             template is ToggleRangeTemplate -> ToggleRangeBehavior::class
@@ -140,7 +163,6 @@
     internal fun applyRenderInfo(enabled: Boolean, offset: Int = 0) {
         setEnabled(enabled)
 
-        val deviceType = cws.control?.let { it.getDeviceType() } ?: cws.ci.deviceType
         val ri = RenderInfo.lookup(context, cws.componentName, deviceType, enabled, offset)
 
         val fg = context.getResources().getColorStateList(ri.foreground, context.getTheme())
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 934a695..fab6fc7 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -30,18 +30,19 @@
 import android.graphics.drawable.Drawable
 import android.graphics.drawable.LayerDrawable
 import android.os.Process
+import android.provider.Settings
 import android.service.controls.Control
 import android.service.controls.actions.ControlAction
-import android.util.TypedValue
 import android.util.Log
-import android.view.animation.AccelerateInterpolator
-import android.view.animation.DecelerateInterpolator
+import android.util.TypedValue
 import android.view.ContextThemeWrapper
 import android.view.LayoutInflater
 import android.view.View
 import android.view.View.MeasureSpec
 import android.view.ViewGroup
 import android.view.WindowManager
+import android.view.animation.AccelerateInterpolator
+import android.view.animation.DecelerateInterpolator
 import android.widget.AdapterView
 import android.widget.ArrayAdapter
 import android.widget.ImageView
@@ -49,23 +50,21 @@
 import android.widget.ListPopupWindow
 import android.widget.Space
 import android.widget.TextView
+import com.android.systemui.R
 import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.controller.ControlInfo
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.controller.StructureInfo
+import com.android.systemui.controls.management.ControlsEditingActivity
 import com.android.systemui.controls.management.ControlsFavoritingActivity
 import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.controls.management.ControlsProviderSelectorActivity
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.util.concurrency.DelayableExecutor
-import com.android.systemui.R
-
 import dagger.Lazy
-
 import java.text.Collator
 import java.util.function.Consumer
-
 import javax.inject.Inject
 import javax.inject.Singleton
 
@@ -85,6 +84,7 @@
         private const val PREF_COMPONENT = "controls_component"
         private const val PREF_STRUCTURE = "controls_structure"
 
+        private const val USE_PANELS = "systemui.controls_use_panel"
         private const val FADE_IN_MILLIS = 225L
 
         private val EMPTY_COMPONENT = ComponentName("", "")
@@ -210,16 +210,30 @@
     }
 
     private fun startFavoritingActivity(context: Context, si: StructureInfo) {
-        val i = Intent(context, ControlsFavoritingActivity::class.java).apply {
-            putExtra(ControlsFavoritingActivity.EXTRA_APP,
-                controlsListingController.get().getAppLabel(si.componentName))
-            putExtra(ControlsFavoritingActivity.EXTRA_STRUCTURE, si.structure)
-            putExtra(Intent.EXTRA_COMPONENT_NAME, si.componentName)
+        startTargetedActivity(context, si, ControlsFavoritingActivity::class.java)
+    }
+
+    private fun startEditingActivity(context: Context, si: StructureInfo) {
+        startTargetedActivity(context, si, ControlsEditingActivity::class.java)
+    }
+
+    private fun startTargetedActivity(context: Context, si: StructureInfo, klazz: Class<*>) {
+        val i = Intent(context, klazz).apply {
             addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
         }
+        putIntentExtras(i, si)
         startActivity(context, i)
     }
 
+    private fun putIntentExtras(intent: Intent, si: StructureInfo) {
+        intent.apply {
+            putExtra(ControlsFavoritingActivity.EXTRA_APP,
+                    controlsListingController.get().getAppLabel(si.componentName))
+            putExtra(ControlsFavoritingActivity.EXTRA_STRUCTURE, si.structure)
+            putExtra(Intent.EXTRA_COMPONENT_NAME, si.componentName)
+        }
+    }
+
     private fun startProviderSelectorActivity(context: Context) {
         val i = Intent(context, ControlsProviderSelectorActivity::class.java).apply {
             addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
@@ -253,6 +267,7 @@
     private fun createMenu() {
         val items = arrayOf(
             context.resources.getString(R.string.controls_menu_add),
+            context.resources.getString(R.string.controls_menu_edit),
             "Reset"
         )
         var adapter = ArrayAdapter<String>(context, R.layout.controls_more_item, items)
@@ -273,8 +288,10 @@
                             when (pos) {
                                 // 0: Add Control
                                 0 -> startFavoritingActivity(view.context, selectedStructure)
-                                // 1: TEMPORARY for reset controls
-                                1 -> showResetConfirmation()
+                                // 1: Edit controls
+                                1 -> startEditingActivity(view.context, selectedStructure)
+                                // 2: TEMPORARY for reset controls
+                                2 -> showResetConfirmation()
                                 else -> Log.w(ControlsUiController.TAG,
                                     "Unsupported index ($pos) on 'more' menu selection")
                             }
@@ -361,7 +378,6 @@
                 .setTint(context.resources.getColor(R.color.control_spinner_dropdown, null))
         }
         parent.requireViewById<ImageView>(R.id.app_icon).apply {
-            setContentDescription(selectionItem.getTitle())
             setImageDrawable(selectionItem.icon)
         }
 
@@ -408,6 +424,9 @@
 
         val maxColumns = findMaxColumns()
 
+        // use flag only temporarily for testing
+        val usePanels = Settings.Secure.getInt(context.contentResolver, USE_PANELS, 0) == 1
+
         val listView = parent.requireViewById(R.id.global_actions_controls_list) as ViewGroup
         var lastRow: ViewGroup = createRow(inflater, listView)
         selectedStructure.controls.forEach {
@@ -421,7 +440,8 @@
                 baseLayout,
                 controlsController.get(),
                 uiExecutor,
-                bgExecutor
+                bgExecutor,
+                usePanels
             )
             val key = ControlKey(selectedStructure.componentName, it.controlId)
             cvh.bindData(controlsById.getValue(key))
@@ -501,6 +521,7 @@
         hidden = true
         popup?.dismiss()
         activeDialog?.dismiss()
+        ControlActionCoordinator.closeDialog()
 
         controlsController.get().unsubscribe()
 
@@ -586,7 +607,6 @@
             setText(item.getTitle())
         }
         view.requireViewById<ImageView>(R.id.app_icon).apply {
-            setContentDescription(item.appName)
             setImageDrawable(item.icon)
         }
         return view
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
index d3d4287..15c41a2 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
@@ -17,18 +17,16 @@
 package com.android.systemui.controls.ui
 
 import android.app.ActivityView
-import android.app.ActivityOptions
 import android.app.Dialog
-import android.app.PendingIntent
 import android.content.ComponentName
-import android.content.Context
 import android.content.Intent
+import android.provider.Settings
 import android.view.View
 import android.view.ViewGroup
-import android.view.ViewGroup.LayoutParams.MATCH_PARENT
-import android.view.Window
+import android.view.WindowInsets
 import android.view.WindowManager
-import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+import android.widget.ImageView
+import android.widget.TextView
 
 import com.android.systemui.R
 
@@ -38,20 +36,26 @@
  * The activity being launched is specified by {@link android.service.controls.Control#getAppIntent}.
  */
 class DetailDialog(
-    val parentContext: Context,
-    val intent: PendingIntent
-) : Dialog(parentContext) {
+    val cvh: ControlViewHolder,
+    val intent: Intent
+) : Dialog(cvh.context, R.style.Theme_SystemUI_Dialog_Control_DetailPanel) {
 
-    var activityView: ActivityView
+    companion object {
+        private const val ALPHA = (0.8f * 255).toInt()
+        private const val PANEL_TOP_OFFSET = "systemui.controls_panel_top_offset"
+    }
+
+    lateinit var activityView: ActivityView
 
     val stateCallback: ActivityView.StateCallback = object : ActivityView.StateCallback() {
         override fun onActivityViewReady(view: ActivityView) {
-            val fillInIntent = Intent()
+            val launchIntent = Intent(intent)
 
             // Apply flags to make behaviour match documentLaunchMode=always.
-            fillInIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-            fillInIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
-            view.startActivity(intent, fillInIntent, ActivityOptions.makeBasic())
+            launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+            launchIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
+
+            view.startActivity(launchIntent)
         }
 
         override fun onActivityViewDestroyed(view: ActivityView) {}
@@ -61,28 +65,8 @@
         override fun onTaskRemovalStarted(taskId: Int) {}
     }
 
-    @Suppress("DEPRECATION")
-    private fun Window.setWindowParams() {
-        requestFeature(Window.FEATURE_NO_TITLE)
-
-        // Inflate the decor view, so the attributes below are not overwritten by the theme.
-        decorView
-        attributes.systemUiVisibility =
-                (attributes.systemUiVisibility
-                        or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
-                        or View.SYSTEM_UI_FLAG_LAYOUT_STABLE)
-
-        setLayout(MATCH_PARENT, MATCH_PARENT)
-        clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
-        addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
-                or WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
-                or WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED)
-        setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY)
-        getAttributes().setFitInsetsTypes(0 /* types */)
-    }
-
     init {
-        getWindow()?.setWindowParams()
+        window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY)
 
         setContentView(R.layout.controls_detail_dialog)
 
@@ -90,19 +74,61 @@
         requireViewById<ViewGroup>(R.id.controls_activity_view).apply {
             addView(activityView)
         }
+
+        requireViewById<ImageView>(R.id.control_detail_close).apply {
+            setOnClickListener { _: View -> dismiss() }
+        }
+
+        requireViewById<TextView>(R.id.title).apply {
+            setText(cvh.title.text)
+        }
+
+        requireViewById<TextView>(R.id.subtitle).apply {
+            setText(cvh.subtitle.text)
+        }
+
+        requireViewById<ImageView>(R.id.control_detail_open_in_app).apply {
+            setOnClickListener { v: View ->
+                dismiss()
+                context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
+                v.context.startActivity(intent)
+            }
+        }
+
+        // consume all insets to achieve slide under effect
+        window.getDecorView().setOnApplyWindowInsetsListener {
+            v: View, insets: WindowInsets ->
+                activityView.apply {
+                    val l = getPaddingLeft()
+                    val t = getPaddingTop()
+                    val r = getPaddingRight()
+                    setPadding(l, t, r, insets.getSystemWindowInsets().bottom)
+                }
+
+                insets.consumeSystemWindowInsets()
+        }
+
+        requireViewById<ViewGroup>(R.id.control_detail_root).apply {
+            // use flag only temporarily for testing
+            val resolver = cvh.context.contentResolver
+            val defaultOffsetInPx = cvh.context.resources
+                .getDimensionPixelSize(R.dimen.controls_activity_view_top_offset)
+            val offsetInPx = Settings.Secure.getInt(resolver, PANEL_TOP_OFFSET, defaultOffsetInPx)
+
+            val lp = getLayoutParams() as ViewGroup.MarginLayoutParams
+            lp.topMargin = offsetInPx
+            setLayoutParams(lp)
+        }
     }
 
     override fun show() {
-        val attrs = getWindow()?.attributes
-        attrs?.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
-        getWindow()?.attributes = attrs
-
         activityView.setCallback(stateCallback)
 
         super.show()
     }
 
     override fun dismiss() {
+        if (!isShowing()) return
         activityView.release()
 
         super.dismiss()
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt
index 15c1dab..6340db1 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt
@@ -33,6 +33,10 @@
 
     override fun initialize(cvh: ControlViewHolder) {
         this.cvh = cvh
+
+        cvh.layout.setOnClickListener { _ ->
+            ControlActionCoordinator.touch(cvh, template.getTemplateId(), control)
+        }
     }
 
     override fun bind(cws: ControlWithState) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/TouchBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/TouchBehavior.kt
index d64a5f0..b02c9c8 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/TouchBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/TouchBehavior.kt
@@ -20,7 +20,7 @@
 import android.graphics.drawable.LayerDrawable
 import android.view.View
 import android.service.controls.Control
-import android.service.controls.templates.StatelessTemplate
+import android.service.controls.templates.ControlTemplate
 
 import com.android.systemui.R
 import com.android.systemui.controls.ui.ControlActionCoordinator.MIN_LEVEL
@@ -31,7 +31,7 @@
  */
 class TouchBehavior : Behavior {
     lateinit var clipLayer: Drawable
-    lateinit var template: StatelessTemplate
+    lateinit var template: ControlTemplate
     lateinit var control: Control
     lateinit var cvh: ControlViewHolder
 
@@ -40,14 +40,14 @@
         cvh.applyRenderInfo(false)
 
         cvh.layout.setOnClickListener(View.OnClickListener() {
-            ControlActionCoordinator.touch(cvh, template.getTemplateId())
+            ControlActionCoordinator.touch(cvh, template.getTemplateId(), control)
         })
     }
 
     override fun bind(cws: ControlWithState) {
         this.control = cws.control!!
         cvh.status.setText(control.getStatusText())
-        template = control.getControlTemplate() as StatelessTemplate
+        template = control.getControlTemplate()
 
         val ld = cvh.layout.getBackground() as LayerDrawable
         clipLayer = ld.findDrawableByLayerId(R.id.clip_layer)
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index a4a5894..3f095dc 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -222,11 +222,27 @@
     private ControlsController mControlsController;
     private SharedPreferences mControlsPreferences;
     private final RingerModeTracker mRingerModeTracker;
+    private int mDialogPressDelay = DIALOG_PRESS_DELAY; // ms
 
     @VisibleForTesting
     public enum GlobalActionsEvent implements UiEventLogger.UiEventEnum {
         @UiEvent(doc = "The global actions / power menu surface became visible on the screen.")
-        GA_POWER_MENU_OPEN(337);
+        GA_POWER_MENU_OPEN(337),
+
+        @UiEvent(doc = "The global actions bugreport button was pressed.")
+        GA_BUGREPORT_PRESS(344),
+
+        @UiEvent(doc = "The global actions bugreport button was long pressed.")
+        GA_BUGREPORT_LONG_PRESS(345),
+
+        @UiEvent(doc = "The global actions emergency button was pressed.")
+        GA_EMERGENCY_DIALER_PRESS(346),
+
+        @UiEvent(doc = "The global actions screenshot button was pressed.")
+        GA_SCREENSHOT_PRESS(347),
+
+        @UiEvent(doc = "The global actions screenshot button was long pressed.")
+        GA_SCREENSHOT_LONG_PRESS(348);
 
         private final int mId;
 
@@ -679,7 +695,8 @@
         }
     }
 
-    private class EmergencyDialerAction extends EmergencyAction {
+    @VisibleForTesting
+    class EmergencyDialerAction extends EmergencyAction {
         private EmergencyDialerAction() {
             super(com.android.systemui.R.drawable.ic_emergency_star,
                     R.string.global_action_emergency);
@@ -688,6 +705,7 @@
         @Override
         public void onPress() {
             mMetricsLogger.action(MetricsEvent.ACTION_EMERGENCY_DIALER_FROM_POWER_MENU);
+            mUiEventLogger.log(GlobalActionsEvent.GA_EMERGENCY_DIALER_PRESS);
             if (mTelecomManager != null) {
                 Intent intent = mTelecomManager.createLaunchEmergencyDialerIntent(
                         null /* number */);
@@ -701,6 +719,11 @@
         }
     }
 
+    @VisibleForTesting
+    EmergencyDialerAction makeEmergencyDialerActionForTesting() {
+        return new EmergencyDialerAction();
+    }
+
     private final class RestartAction extends SinglePressAction implements LongPressAction {
         private RestartAction() {
             super(R.drawable.ic_restart, R.string.global_action_restart);
@@ -731,7 +754,8 @@
         }
     }
 
-    private class ScreenshotAction extends SinglePressAction implements LongPressAction {
+    @VisibleForTesting
+    class ScreenshotAction extends SinglePressAction implements LongPressAction {
         public ScreenshotAction() {
             super(R.drawable.ic_screenshot, R.string.global_action_screenshot);
         }
@@ -747,8 +771,9 @@
                 public void run() {
                     mScreenshotHelper.takeScreenshot(1, true, true, mHandler, null);
                     mMetricsLogger.action(MetricsEvent.ACTION_SCREENSHOT_POWER_MENU);
+                    mUiEventLogger.log(GlobalActionsEvent.GA_SCREENSHOT_PRESS);
                 }
-            }, 500);
+            }, mDialogPressDelay);
         }
 
         @Override
@@ -764,6 +789,7 @@
         @Override
         public boolean onLongPress() {
             if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SCREENRECORD_LONG_PRESS)) {
+                mUiEventLogger.log(GlobalActionsEvent.GA_SCREENSHOT_LONG_PRESS);
                 mScreenRecordHelper.launchRecordPrompt();
             } else {
                 onPress();
@@ -772,7 +798,13 @@
         }
     }
 
-    private class BugReportAction extends SinglePressAction implements LongPressAction {
+    @VisibleForTesting
+    ScreenshotAction makeScreenshotActionForTesting() {
+        return new ScreenshotAction();
+    }
+
+    @VisibleForTesting
+    class BugReportAction extends SinglePressAction implements LongPressAction {
 
         public BugReportAction() {
             super(R.drawable.ic_lock_bugreport, R.string.bugreport_title);
@@ -795,6 +827,7 @@
                         // Take an "interactive" bugreport.
                         mMetricsLogger.action(
                                 MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_INTERACTIVE);
+                        mUiEventLogger.log(GlobalActionsEvent.GA_BUGREPORT_PRESS);
                         if (!mIActivityManager.launchBugReportHandlerApp()) {
                             Log.w(TAG, "Bugreport handler could not be launched");
                             mIActivityManager.requestInteractiveBugReport();
@@ -802,7 +835,7 @@
                     } catch (RemoteException e) {
                     }
                 }
-            }, 500);
+            }, mDialogPressDelay);
         }
 
         @Override
@@ -815,6 +848,7 @@
             try {
                 // Take a "full" bugreport.
                 mMetricsLogger.action(MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_FULL);
+                mUiEventLogger.log(GlobalActionsEvent.GA_BUGREPORT_LONG_PRESS);
                 mIActivityManager.requestFullBugReport();
             } catch (RemoteException e) {
             }
@@ -831,6 +865,11 @@
         }
     }
 
+    @VisibleForTesting
+    BugReportAction makeBugReportActionForTesting() {
+        return new BugReportAction();
+    }
+
     private final class LogoutAction extends SinglePressAction {
         private LogoutAction() {
             super(R.drawable.ic_logout, R.string.global_action_logout);
@@ -858,7 +897,7 @@
                 } catch (RemoteException re) {
                     Log.e(TAG, "Couldn't logout user " + re);
                 }
-            }, 500);
+            }, mDialogPressDelay);
         }
     }
 
@@ -1599,6 +1638,11 @@
     private static final int MESSAGE_REFRESH = 1;
     private static final int MESSAGE_SHOW = 2;
     private static final int DIALOG_DISMISS_DELAY = 300; // ms
+    private static final int DIALOG_PRESS_DELAY = 500; // ms
+
+    @VisibleForTesting void setZeroDialogPressDelayForTesting() {
+        mDialogPressDelay = 0; // ms
+    }
 
     private Handler mHandler = new Handler() {
         public void handleMessage(Message msg) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index 9873d24..62efd8c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -44,9 +44,11 @@
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
+import androidx.annotation.Nullable;
 import androidx.core.graphics.drawable.RoundedBitmapDrawable;
 import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
 
+import com.android.settingslib.media.LocalMediaManager;
 import com.android.settingslib.media.MediaDevice;
 import com.android.settingslib.media.MediaOutputSliceConstants;
 import com.android.settingslib.widget.AdaptiveIcon;
@@ -66,6 +68,7 @@
 public class MediaControlPanel {
     private static final String TAG = "MediaControlPanel";
     private final NotificationMediaManager mMediaManager;
+    @Nullable private final LocalMediaManager mLocalMediaManager;
     private final Executor mForegroundExecutor;
     private final Executor mBackgroundExecutor;
 
@@ -77,6 +80,7 @@
     private int mForegroundColor;
     private int mBackgroundColor;
     protected ComponentName mRecvComponent;
+    private MediaDevice mDevice;
     private boolean mIsRegistered = false;
 
     private final int[] mActionIds;
@@ -121,19 +125,44 @@
         }
     };
 
+    private final LocalMediaManager.DeviceCallback mDeviceCallback =
+            new LocalMediaManager.DeviceCallback() {
+        @Override
+        public void onDeviceListUpdate(List<MediaDevice> devices) {
+            if (mLocalMediaManager == null) {
+                return;
+            }
+            MediaDevice currentDevice = mLocalMediaManager.getCurrentConnectedDevice();
+            // Check because this can be called several times while changing devices
+            if (mDevice == null || !mDevice.equals(currentDevice)) {
+                mDevice = currentDevice;
+                updateDevice(mDevice);
+            }
+        }
+
+        @Override
+        public void onSelectedDeviceStateChanged(MediaDevice device, int state) {
+            if (mDevice == null || !mDevice.equals(device)) {
+                mDevice = device;
+                updateDevice(mDevice);
+            }
+        }
+    };
+
     /**
      * Initialize a new control panel
      * @param context
      * @param parent
      * @param manager
+     * @param routeManager Manager used to listen for device change events.
      * @param layoutId layout resource to use for this control panel
      * @param actionIds resource IDs for action buttons in the layout
      * @param foregroundExecutor foreground executor
      * @param backgroundExecutor background executor, used for processing artwork
      */
     public MediaControlPanel(Context context, ViewGroup parent, NotificationMediaManager manager,
-            @LayoutRes int layoutId, int[] actionIds, Executor foregroundExecutor,
-            Executor backgroundExecutor) {
+            @Nullable LocalMediaManager routeManager, @LayoutRes int layoutId, int[] actionIds,
+            Executor foregroundExecutor, Executor backgroundExecutor) {
         mContext = context;
         LayoutInflater inflater = LayoutInflater.from(mContext);
         mMediaNotifView = (LinearLayout) inflater.inflate(layoutId, parent, false);
@@ -144,6 +173,7 @@
         // mStateListener to be unregistered in detach.
         mMediaNotifView.addOnAttachStateChangeListener(mStateListener);
         mMediaManager = manager;
+        mLocalMediaManager = routeManager;
         mActionIds = actionIds;
         mForegroundExecutor = foregroundExecutor;
         mBackgroundExecutor = backgroundExecutor;
@@ -176,7 +206,7 @@
      * @param device
      */
     public void setMediaSession(MediaSession.Token token, Icon icon, int iconColor,
-            int bgColor, PendingIntent contentIntent, String appNameString, MediaDevice device) {
+            int bgColor, PendingIntent contentIntent, String appNameString) {
         mToken = token;
         mForegroundColor = iconColor;
         mBackgroundColor = bgColor;
@@ -253,9 +283,9 @@
 
         // Transfer chip
         mSeamless = mMediaNotifView.findViewById(R.id.media_seamless);
-        if (mSeamless != null) {
+        if (mSeamless != null && mLocalMediaManager != null) {
             mSeamless.setVisibility(View.VISIBLE);
-            updateDevice(device);
+            updateDevice(mLocalMediaManager.getCurrentConnectedDevice());
             ActivityStarter mActivityStarter = Dependency.get(ActivityStarter.class);
             mSeamless.setOnClickListener(v -> {
                 final Intent intent = new Intent()
@@ -366,7 +396,7 @@
      * Update the current device information
      * @param device device information to display
      */
-    public void updateDevice(MediaDevice device) {
+    private void updateDevice(MediaDevice device) {
         if (mSeamless == null) {
             return;
         }
@@ -456,6 +486,10 @@
         Assert.isMainThread();
         if (!mIsRegistered) {
             mMediaManager.addCallback(mMediaListener);
+            if (mLocalMediaManager != null) {
+                mLocalMediaManager.registerCallback(mDeviceCallback);
+                mLocalMediaManager.startScan();
+            }
             mIsRegistered = true;
         }
     }
@@ -463,6 +497,10 @@
     private void makeInactive() {
         Assert.isMainThread();
         if (mIsRegistered) {
+            if (mLocalMediaManager != null) {
+                mLocalMediaManager.stopScan();
+                mLocalMediaManager.unregisterCallback(mDeviceCallback);
+            }
             mMediaManager.removeCallback(mMediaListener);
             mIsRegistered = false;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
index 6779479..bab1f39 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
@@ -240,6 +240,12 @@
         mInitialized = true;
         mContext = context;
         mPipBoundsHandler = pipBoundsHandler;
+        // Ensure that we have the display info in case we get calls to update the bounds before the
+        // listener calls back
+        final DisplayInfo displayInfo = new DisplayInfo();
+        context.getDisplay().getDisplayInfo(displayInfo);
+        mPipBoundsHandler.onDisplayInfoChanged(displayInfo);
+
         mResizeAnimationDuration = context.getResources()
                 .getInteger(R.integer.config_pipResizeAnimationDuration);
         mPipTaskOrganizer = new PipTaskOrganizer(mContext, mPipBoundsHandler,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
index 339a408..e636707 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
@@ -34,7 +34,7 @@
 import android.widget.SeekBar;
 import android.widget.TextView;
 
-import com.android.settingslib.media.MediaDevice;
+import com.android.settingslib.media.LocalMediaManager;
 import com.android.systemui.R;
 import com.android.systemui.media.MediaControlPanel;
 import com.android.systemui.media.SeekBarObserver;
@@ -74,9 +74,10 @@
      * @param backgroundExecutor
      */
     public QSMediaPlayer(Context context, ViewGroup parent, NotificationMediaManager manager,
-            Executor foregroundExecutor, DelayableExecutor backgroundExecutor) {
-        super(context, parent, manager, R.layout.qs_media_panel, QS_ACTION_IDS, foregroundExecutor,
-                backgroundExecutor);
+            LocalMediaManager routeManager, Executor foregroundExecutor,
+            DelayableExecutor backgroundExecutor) {
+        super(context, parent, manager, routeManager, R.layout.qs_media_panel, QS_ACTION_IDS,
+                foregroundExecutor, backgroundExecutor);
         mParent = (QSPanel) parent;
         mBackgroundExecutor = backgroundExecutor;
         mSeekBarViewModel = new SeekBarViewModel(backgroundExecutor);
@@ -101,12 +102,12 @@
      * @param device current playback device
      */
     public void setMediaSession(MediaSession.Token token, Icon icon, int iconColor,
-            int bgColor, View actionsContainer, Notification notif, MediaDevice device) {
+            int bgColor, View actionsContainer, Notification notif) {
 
         String appName = Notification.Builder.recoverBuilder(getContext(), notif)
                 .loadHeaderAppName();
         super.setMediaSession(token, icon, iconColor, bgColor, notif.contentIntent,
-                appName, device);
+                appName);
 
         // Media controls
         LinearLayout parentActionsLayout = (LinearLayout) actionsContainer;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index c8412ff..0566b2e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -47,7 +47,6 @@
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.settingslib.media.InfoMediaManager;
 import com.android.settingslib.media.LocalMediaManager;
-import com.android.settingslib.media.MediaDevice;
 import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
@@ -75,7 +74,6 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.List;
 import java.util.concurrent.Executor;
 import java.util.stream.Collectors;
 
@@ -105,8 +103,6 @@
     private final LocalBluetoothManager mLocalBluetoothManager;
     private final Executor mForegroundExecutor;
     private final DelayableExecutor mBackgroundExecutor;
-    private LocalMediaManager mLocalMediaManager;
-    private MediaDevice mDevice;
     private boolean mUpdateCarousel = false;
 
     protected boolean mExpanded;
@@ -130,34 +126,6 @@
     private BrightnessMirrorController mBrightnessMirrorController;
     private View mDivider;
 
-    private final LocalMediaManager.DeviceCallback mDeviceCallback =
-            new LocalMediaManager.DeviceCallback() {
-        @Override
-        public void onDeviceListUpdate(List<MediaDevice> devices) {
-            if (mLocalMediaManager == null) {
-                return;
-            }
-            MediaDevice currentDevice = mLocalMediaManager.getCurrentConnectedDevice();
-            // Check because this can be called several times while changing devices
-            if (mDevice == null || !mDevice.equals(currentDevice)) {
-                mDevice = currentDevice;
-                for (QSMediaPlayer p : mMediaPlayers) {
-                    p.updateDevice(mDevice);
-                }
-            }
-        }
-
-        @Override
-        public void onSelectedDeviceStateChanged(MediaDevice device, int state) {
-            if (mDevice == null || !mDevice.equals(device)) {
-                mDevice = device;
-                for (QSMediaPlayer p : mMediaPlayers) {
-                    p.updateDevice(mDevice);
-                }
-            }
-        }
-    };
-
     @Inject
     public QSPanel(
             @Named(VIEW_CONTEXT) Context context,
@@ -277,7 +245,14 @@
 
         if (player == null) {
             Log.d(TAG, "creating new player");
-            player = new QSMediaPlayer(mContext, this, mNotificationMediaManager,
+            // Set up listener for device changes
+            // TODO: integrate with MediaTransferManager?
+            InfoMediaManager imm = new InfoMediaManager(mContext, notif.getPackageName(),
+                    notif.getNotification(), mLocalBluetoothManager);
+            LocalMediaManager routeManager = new LocalMediaManager(mContext, mLocalBluetoothManager,
+                    imm, notif.getPackageName());
+
+            player = new QSMediaPlayer(mContext, this, mNotificationMediaManager, routeManager,
                     mForegroundExecutor, mBackgroundExecutor);
             player.setListening(mListening);
             if (player.isPlaying()) {
@@ -292,22 +267,10 @@
 
         Log.d(TAG, "setting player session");
         player.setMediaSession(token, icon, iconColor, bgColor, actionsContainer,
-                notif.getNotification(), mDevice);
+                notif.getNotification());
 
         if (mMediaPlayers.size() > 0) {
             ((View) mMediaCarousel.getParent()).setVisibility(View.VISIBLE);
-
-            if (mLocalMediaManager == null) {
-                // Set up listener for device changes
-                // TODO: integrate with MediaTransferManager?
-                InfoMediaManager imm =
-                        new InfoMediaManager(mContext, null, null, mLocalBluetoothManager);
-                mLocalMediaManager = new LocalMediaManager(mContext, mLocalBluetoothManager, imm,
-                        null);
-                mLocalMediaManager.startScan();
-                mDevice = mLocalMediaManager.getCurrentConnectedDevice();
-                mLocalMediaManager.registerCallback(mDeviceCallback);
-            }
         }
     }
 
@@ -330,11 +293,6 @@
         mMediaCarousel.removeView(player.getView());
         if (mMediaPlayers.size() == 0) {
             ((View) mMediaCarousel.getParent()).setVisibility(View.GONE);
-            if (mLocalMediaManager != null) {
-                mLocalMediaManager.stopScan();
-                mLocalMediaManager.unregisterCallback(mDeviceCallback);
-                mLocalMediaManager = null;
-            }
         }
         return true;
     }
@@ -404,11 +362,6 @@
             mBrightnessMirrorController.removeCallback(this);
         }
         mDumpManager.unregisterDumpable(getDumpableTag());
-        if (mLocalMediaManager != null) {
-            mLocalMediaManager.stopScan();
-            mLocalMediaManager.unregisterCallback(mDeviceCallback);
-            mLocalMediaManager = null;
-        }
         super.onDetachedFromWindow();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
index 0c50194..0ba4cb1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
@@ -53,7 +53,7 @@
      */
     public QuickQSMediaPlayer(Context context, ViewGroup parent, NotificationMediaManager manager,
             Executor foregroundExecutor, Executor backgroundExecutor) {
-        super(context, parent, manager, R.layout.qqs_media_panel, QQS_ACTION_IDS,
+        super(context, parent, manager, null, R.layout.qqs_media_panel, QQS_ACTION_IDS,
                 foregroundExecutor, backgroundExecutor);
     }
 
@@ -84,7 +84,7 @@
             return;
         }
 
-        super.setMediaSession(token, icon, iconColor, bgColor, contentIntent, null, null);
+        super.setMediaSession(token, icon, iconColor, bgColor, contentIntent, null);
 
         LinearLayout parentActionsLayout = (LinearLayout) actionsContainer;
         int i = 0;
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index 366ef93..2df4506 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -1042,7 +1042,8 @@
         dockedTaskRect = dockedTaskRect == null ? dockedRect : dockedTaskRect;
         otherTaskRect = otherTaskRect == null ? otherRect : otherTaskRect;
 
-        mDividerPositionX = dockedRect.right;
+        mDividerPositionX = mSplitLayout.getPrimarySplitSide() == DOCKED_RIGHT
+                ? otherRect.right : dockedRect.right;
         mDividerPositionY = dockedRect.bottom;
 
         if (DEBUG) {
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitDisplayLayout.java b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitDisplayLayout.java
index 3b8addb..92f6b4a 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitDisplayLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitDisplayLayout.java
@@ -101,7 +101,16 @@
     }
 
     int getPrimarySplitSide() {
-        return mDisplayLayout.isLandscape() ? DOCKED_LEFT : DOCKED_TOP;
+        switch (mDisplayLayout.getNavigationBarPosition(mContext.getResources())) {
+            case DisplayLayout.NAV_BAR_BOTTOM:
+                return mDisplayLayout.isLandscape() ? DOCKED_LEFT : DOCKED_TOP;
+            case DisplayLayout.NAV_BAR_LEFT:
+                return DOCKED_RIGHT;
+            case DisplayLayout.NAV_BAR_RIGHT:
+                return DOCKED_LEFT;
+            default:
+                return DOCKED_INVALID;
+        }
     }
 
     boolean isMinimized() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
index c8b34f1..4e6df0a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
@@ -75,6 +75,9 @@
             // We never want to open the app directly if the user clicks in between
             // the notifications.
             return;
+        } else if (row.areGutsExposed()) {
+            // ignore click if guts are exposed
+            return;
         }
 
         // Mark notification for one frame.
diff --git a/packages/SystemUI/src/com/android/systemui/wifi/WifiDebuggingActivity.java b/packages/SystemUI/src/com/android/systemui/wifi/WifiDebuggingActivity.java
index a94af24..1c7a9b5 100644
--- a/packages/SystemUI/src/com/android/systemui/wifi/WifiDebuggingActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/wifi/WifiDebuggingActivity.java
@@ -175,6 +175,9 @@
         IntentFilter filter = new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION);
         filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
         registerReceiver(mWifiChangeReceiver, filter);
+        // Close quick shade
+        sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/wifi/WifiDebuggingSecondaryUserActivity.java b/packages/SystemUI/src/com/android/systemui/wifi/WifiDebuggingSecondaryUserActivity.java
index 0266a84..7a31fa5 100644
--- a/packages/SystemUI/src/com/android/systemui/wifi/WifiDebuggingSecondaryUserActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/wifi/WifiDebuggingSecondaryUserActivity.java
@@ -96,6 +96,8 @@
         IntentFilter filter = new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION);
         filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
         registerReceiver(mWifiChangeReceiver, filter);
+        // Close quick shade
+        sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java
index 4652abf..cfec1c0 100644
--- a/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java
@@ -27,6 +27,7 @@
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -46,16 +47,27 @@
 
 import com.android.internal.R;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Contains information about the layout-properties of a display. This refers to internal layout
  * like insets/cutout/rotation. In general, this can be thought of as the System-UI analog to
  * DisplayPolicy.
  */
 public class DisplayLayout {
+    @IntDef(prefix = { "NAV_BAR_" }, value = {
+            NAV_BAR_LEFT,
+            NAV_BAR_RIGHT,
+            NAV_BAR_BOTTOM,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface NavBarPosition {}
+
     // Navigation bar position values
-    private static final int NAV_BAR_LEFT = 1 << 0;
-    private static final int NAV_BAR_RIGHT = 1 << 1;
-    private static final int NAV_BAR_BOTTOM = 1 << 2;
+    public static final int NAV_BAR_LEFT = 1 << 0;
+    public static final int NAV_BAR_RIGHT = 1 << 1;
+    public static final int NAV_BAR_BOTTOM = 1 << 2;
 
     private int mUiMode;
     private int mWidth;
@@ -214,6 +226,14 @@
     }
 
     /**
+     * Gets navigation bar position for this layout
+     * @return Navigation bar position for this layout.
+     */
+    public @NavBarPosition int getNavigationBarPosition(Resources res) {
+        return navigationBarPosition(res, mWidth, mHeight, mRotation);
+    }
+
+    /**
      * Rotates bounds as if parentBounds and bounds are a group. The group is rotated by `delta`
      * 90-degree counter-clockwise increments. This assumes that parentBounds is at 0,0 and
      * remains at 0,0 after rotation.
@@ -437,8 +457,8 @@
     }
 
     /** Retrieve navigation bar position from resources based on rotation and size. */
-    public static int navigationBarPosition(Resources res, int displayWidth, int displayHeight,
-            int rotation) {
+    public static @NavBarPosition int navigationBarPosition(Resources res, int displayWidth,
+            int displayHeight, int rotation) {
         boolean navBarCanMove = displayWidth != displayHeight && res.getBoolean(
                 com.android.internal.R.bool.config_navBarCanMove);
         if (navBarCanMove && displayWidth > displayHeight) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
index 8630570..f6ee46b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
@@ -87,9 +87,6 @@
     private lateinit var structureInfoCaptor: ArgumentCaptor<StructureInfo>
 
     @Captor
-    private lateinit var booleanConsumer: ArgumentCaptor<Consumer<Boolean>>
-
-    @Captor
     private lateinit var controlLoadCallbackCaptor:
             ArgumentCaptor<ControlsBindingController.LoadCallback>
     @Captor
@@ -936,4 +933,33 @@
         verifyNoMoreInteractions(persistenceWrapper)
         verifyNoMoreInteractions(auxiliaryPersistenceWrapper)
     }
+
+    @Test
+    fun testGetFavoritesForStructure() {
+        controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO)
+        controller.replaceFavoritesForStructure(
+                TEST_STRUCTURE_INFO_2.copy(componentName = TEST_COMPONENT))
+        delayableExecutor.runAllReady()
+
+        assertEquals(TEST_STRUCTURE_INFO.controls,
+                controller.getFavoritesForStructure(TEST_COMPONENT, TEST_STRUCTURE))
+        assertEquals(TEST_STRUCTURE_INFO_2.controls,
+                controller.getFavoritesForStructure(TEST_COMPONENT, TEST_STRUCTURE_2))
+    }
+
+    @Test
+    fun testGetFavoritesForStructure_wrongStructure() {
+        controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO)
+        delayableExecutor.runAllReady()
+
+        assertTrue(controller.getFavoritesForStructure(TEST_COMPONENT, TEST_STRUCTURE_2).isEmpty())
+    }
+
+    @Test
+    fun testGetFavoritesForStructure_wrongComponent() {
+        controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO)
+        delayableExecutor.runAllReady()
+
+        assertTrue(controller.getFavoritesForStructure(TEST_COMPONENT_2, TEST_STRUCTURE).isEmpty())
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/AllModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/AllModelTest.kt
index 5e0d28f..236384b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/AllModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/AllModelTest.kt
@@ -31,6 +31,8 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -43,6 +45,8 @@
 
     @Mock
     lateinit var pendingIntent: PendingIntent
+    @Mock
+    lateinit var controlsModelCallback: ControlsModel.ControlsModelCallback
 
     val idPrefix = "controlId"
     val favoritesIndices = listOf(7, 3, 1, 9)
@@ -84,7 +88,7 @@
                     it in favoritesIndices
             )
         }
-        model = AllModel(controls, favoritesList, EMPTY_STRING)
+        model = AllModel(controls, favoritesList, EMPTY_STRING, controlsModelCallback)
     }
 
     @Test
@@ -93,28 +97,28 @@
         // Zones are sorted by order of appearance, with empty at the end with special header.
         val expected = listOf(
                 ZoneNameWrapper("1"),
-                ControlWrapper(controls[0]),
-                ControlWrapper(controls[3]),
-                ControlWrapper(controls[6]),
-                ControlWrapper(controls[9]),
+                ControlStatusWrapper(controls[0]),
+                ControlStatusWrapper(controls[3]),
+                ControlStatusWrapper(controls[6]),
+                ControlStatusWrapper(controls[9]),
                 ZoneNameWrapper("2"),
-                ControlWrapper(controls[1]),
-                ControlWrapper(controls[4]),
-                ControlWrapper(controls[7]),
+                ControlStatusWrapper(controls[1]),
+                ControlStatusWrapper(controls[4]),
+                ControlStatusWrapper(controls[7]),
                 ZoneNameWrapper("0"),
-                ControlWrapper(controls[2]),
-                ControlWrapper(controls[5]),
-                ControlWrapper(controls[8]),
+                ControlStatusWrapper(controls[2]),
+                ControlStatusWrapper(controls[5]),
+                ControlStatusWrapper(controls[8]),
                 ZoneNameWrapper(EMPTY_STRING),
-                ControlWrapper(controls[10]),
-                ControlWrapper(controls[11])
+                ControlStatusWrapper(controls[10]),
+                ControlStatusWrapper(controls[11])
         )
         expected.zip(model.elements).forEachIndexed { index, it ->
             assertEquals("Error in item at index $index", it.first, it.second)
         }
     }
 
-    private fun sameControl(controlInfo: ControlInfo.Builder, control: Control): Boolean {
+    private fun sameControl(controlInfo: ControlInfo, control: Control): Boolean {
         return controlInfo.controlId == control.controlId &&
                 controlInfo.controlTitle == control.title &&
                 controlInfo.controlSubtitle == control.subtitle &&
@@ -124,10 +128,11 @@
     @Test
     fun testAllEmpty_noHeader() {
         val selected_controls = listOf(controls[10], controls[11])
-        val new_model = AllModel(selected_controls, emptyList(), EMPTY_STRING)
+        val new_model = AllModel(selected_controls, emptyList(), EMPTY_STRING,
+                controlsModelCallback)
         val expected = listOf(
-                ControlWrapper(controls[10]),
-                ControlWrapper(controls[11])
+                ControlStatusWrapper(controls[10]),
+                ControlStatusWrapper(controls[11])
         )
 
         expected.zip(new_model.elements).forEachIndexed { index, it ->
@@ -154,6 +159,8 @@
         model.favorites.zip(expectedFavorites).forEach {
             assertTrue(sameControl(it.first, it.second))
         }
+
+        verify(controlsModelCallback).onFirstChange()
     }
 
     @Test
@@ -163,10 +170,12 @@
         model.changeFavoriteStatus(id, true)
         assertTrue(
                 (model.elements.first {
-                    it is ControlWrapper && it.controlStatus.control.controlId == id
-                } as ControlWrapper)
+                    it is ControlStatusWrapper && it.controlStatus.control.controlId == id
+                } as ControlStatusWrapper)
                         .controlStatus.favorite
         )
+
+        verify(controlsModelCallback).onFirstChange()
     }
 
     @Test
@@ -180,6 +189,8 @@
         model.favorites.zip(expectedFavorites).forEach {
             assertTrue(sameControl(it.first, it.second))
         }
+
+        verify(controlsModelCallback, never()).onFirstChange()
     }
 
     @Test
@@ -194,6 +205,8 @@
         model.favorites.zip(expectedFavorites).forEach {
             assertTrue(sameControl(it.first, it.second))
         }
+
+        verify(controlsModelCallback).onFirstChange()
     }
 
     @Test
@@ -203,10 +216,12 @@
         model.changeFavoriteStatus(id, false)
         assertFalse(
                 (model.elements.first {
-                    it is ControlWrapper && it.controlStatus.control.controlId == id
-                } as ControlWrapper)
+                    it is ControlStatusWrapper && it.controlStatus.control.controlId == id
+                } as ControlStatusWrapper)
                         .controlStatus.favorite
         )
+
+        verify(controlsModelCallback).onFirstChange()
     }
 
     @Test
@@ -219,5 +234,7 @@
         model.favorites.zip(expectedFavorites).forEach {
             assertTrue(sameControl(it.first, it.second))
         }
+
+        verify(controlsModelCallback, never()).onFirstChange()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoriteModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoriteModelTest.kt
deleted file mode 100644
index c330b38..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoriteModelTest.kt
+++ /dev/null
@@ -1,200 +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.systemui.controls.management
-
-import android.app.PendingIntent
-import android.content.ComponentName
-import android.service.controls.Control
-import android.testing.AndroidTestingRunner
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.controls.ControlStatus
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertFalse
-import org.junit.Assert.assertTrue
-import org.junit.Assert.fail
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyNoMoreInteractions
-import org.mockito.MockitoAnnotations
-
-open class FavoriteModelTest : SysuiTestCase() {
-
-    @Mock
-    lateinit var pendingIntent: PendingIntent
-    @Mock
-    lateinit var allAdapter: ControlAdapter
-    @Mock
-    lateinit var favoritesAdapter: ControlAdapter
-
-    val idPrefix = "controlId"
-    val favoritesIndices = listOf(7, 3, 1, 9)
-    val favoritesList = favoritesIndices.map { "controlId$it" }
-    lateinit var controls: List<ControlStatus>
-
-    lateinit var model: FavoriteModel
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-
-        // controlId0 --> zone = 0
-        // controlId1 --> zone = 1, favorite
-        // controlId2 --> zone = 2
-        // controlId3 --> zone = 0, favorite
-        // controlId4 --> zone = 1
-        // controlId5 --> zone = 2
-        // controlId6 --> zone = 0
-        // controlId7 --> zone = 1, favorite
-        // controlId8 --> zone = 2
-        // controlId9 --> zone = 0, favorite
-        controls = (0..9).map {
-            ControlStatus(
-                    Control.StatelessBuilder("$idPrefix$it", pendingIntent)
-                            .setZone((it % 3).toString())
-                            .build(),
-                    ComponentName("", ""),
-                    it in favoritesIndices
-            )
-        }
-
-        model = FavoriteModel(controls, favoritesList, favoritesAdapter, allAdapter)
-    }
-}
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-class FavoriteModelNonParametrizedTests : FavoriteModelTest() {
-    @Test
-    fun testAll() {
-        // Zones are sorted alphabetically
-        val expected = listOf(
-                ZoneNameWrapper("0"),
-                ControlWrapper(controls[0]),
-                ControlWrapper(controls[3]),
-                ControlWrapper(controls[6]),
-                ControlWrapper(controls[9]),
-                ZoneNameWrapper("1"),
-                ControlWrapper(controls[1]),
-                ControlWrapper(controls[4]),
-                ControlWrapper(controls[7]),
-                ZoneNameWrapper("2"),
-                ControlWrapper(controls[2]),
-                ControlWrapper(controls[5]),
-                ControlWrapper(controls[8])
-        )
-        assertEquals(expected, model.all)
-    }
-
-    @Test
-    fun testFavoritesInOrder() {
-        val expected = favoritesIndices.map { ControlWrapper(controls[it]) }
-        assertEquals(expected, model.favorites)
-    }
-
-    @Test
-    fun testChangeFavoriteStatus_addFavorite() {
-        val controlToAdd = 6
-        model.changeFavoriteStatus("$idPrefix$controlToAdd", true)
-
-        val pair = model.all.findControl(controlToAdd)
-        pair?.let {
-            assertTrue(it.second.favorite)
-            assertEquals(it.second, model.favorites.last().controlStatus)
-            verify(favoritesAdapter).notifyItemInserted(model.favorites.size - 1)
-            verify(allAdapter).notifyItemChanged(it.first)
-            verifyNoMoreInteractions(favoritesAdapter, allAdapter)
-        } ?: run {
-            fail("control not found")
-        }
-    }
-
-    @Test
-    fun testChangeFavoriteStatus_removeFavorite() {
-        val controlToRemove = 3
-        model.changeFavoriteStatus("$idPrefix$controlToRemove", false)
-
-        val pair = model.all.findControl(controlToRemove)
-        pair?.let {
-            assertFalse(it.second.favorite)
-            assertTrue(model.favorites.none {
-                it.controlStatus.control.controlId == "$idPrefix$controlToRemove"
-            })
-            verify(favoritesAdapter).notifyItemRemoved(favoritesIndices.indexOf(controlToRemove))
-            verify(allAdapter).notifyItemChanged(it.first)
-            verifyNoMoreInteractions(favoritesAdapter, allAdapter)
-        } ?: run {
-            fail("control not found")
-        }
-    }
-
-    @Test
-    fun testChangeFavoriteStatus_sameStatus() {
-        model.changeFavoriteStatus("${idPrefix}7", true)
-        model.changeFavoriteStatus("${idPrefix}6", false)
-
-        val expected = favoritesIndices.map { ControlWrapper(controls[it]) }
-        assertEquals(expected, model.favorites)
-
-        verifyNoMoreInteractions(favoritesAdapter, allAdapter)
-    }
-
-    private fun List<ElementWrapper>.findControl(controlIndex: Int): Pair<Int, ControlStatus>? {
-        val index = indexOfFirst {
-            it is ControlWrapper &&
-                it.controlStatus.control.controlId == "$idPrefix$controlIndex"
-        }
-        return if (index == -1) null else index to (get(index) as ControlWrapper).controlStatus
-    }
-}
-
-@SmallTest
-@RunWith(Parameterized::class)
-class FavoriteModelParameterizedTest(val from: Int, val to: Int) : FavoriteModelTest() {
-
-    companion object {
-        @JvmStatic
-        @Parameterized.Parameters(name = "{0} -> {1}")
-        fun data(): Collection<Array<Int>> {
-            return (0..3).flatMap { from ->
-                (0..3).map { to ->
-                    arrayOf(from, to)
-                }
-            }.filterNot { it[0] == it[1] }
-        }
-    }
-
-    @Test
-    fun testMoveItem() {
-        val originalFavorites = model.favorites.toList()
-        val originalFavoritesIds =
-                model.favorites.map { it.controlStatus.control.controlId }.toSet()
-        model.onMoveItem(from, to)
-        assertEquals(originalFavorites[from], model.favorites[to])
-        // Check that we still have the same favorites
-        assertEquals(originalFavoritesIds,
-                model.favorites.map { it.controlStatus.control.controlId }.toSet())
-
-        verify(favoritesAdapter).notifyItemMoved(from, to)
-
-        verifyNoMoreInteractions(allAdapter, favoritesAdapter)
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoritesModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoritesModelTest.kt
new file mode 100644
index 0000000..ce33a8d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoritesModelTest.kt
@@ -0,0 +1,291 @@
+/*
+ * 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.systemui.controls.management
+
+import android.content.ComponentName
+import android.testing.AndroidTestingRunner
+import androidx.recyclerview.widget.RecyclerView
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.ControlInterface
+import com.android.systemui.controls.controller.ControlInfo
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class FavoritesModelTest : SysuiTestCase() {
+
+    companion object {
+        private val TEST_COMPONENT = ComponentName.unflattenFromString("test_pkg/.test_cls")!!
+        private val ID_PREFIX = "control"
+        private val INITIAL_FAVORITES = (0..5).map {
+            ControlInfo("$ID_PREFIX$it", "title$it", "subtitle$it", it)
+        }
+    }
+
+    @Mock
+    private lateinit var callback: FavoritesModel.FavoritesModelCallback
+    @Mock
+    private lateinit var adapter: RecyclerView.Adapter<*>
+    private lateinit var model: FavoritesModel
+    private lateinit var dividerWrapper: DividerWrapper
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        model = FavoritesModel(TEST_COMPONENT, INITIAL_FAVORITES, callback)
+        model.attachAdapter(adapter)
+        dividerWrapper = model.elements.first { it is DividerWrapper } as DividerWrapper
+    }
+
+    @After
+    fun testListConsistency() {
+        assertEquals(INITIAL_FAVORITES.size + 1, model.elements.toSet().size)
+        val dividerIndex = getDividerPosition()
+        model.elements.forEachIndexed { index, element ->
+            if (index == dividerIndex) {
+                assertEquals(dividerWrapper, element)
+            } else {
+                element as ControlInterface
+                assertEquals(index < dividerIndex, element.favorite)
+            }
+        }
+        assertEquals(model.favorites, model.elements.take(dividerIndex).map {
+            (it as ControlInfoWrapper).controlInfo
+        })
+    }
+
+    @Test
+    fun testInitialElements() {
+        val expected = INITIAL_FAVORITES.map {
+            ControlInfoWrapper(TEST_COMPONENT, it, true)
+        } + DividerWrapper()
+        assertEquals(expected, model.elements)
+    }
+
+    @Test
+    fun testFavorites() {
+        assertEquals(INITIAL_FAVORITES, model.favorites)
+    }
+
+    @Test
+    fun testRemoveFavorite_notInFavorites() {
+        val removed = 4
+        val id = "$ID_PREFIX$removed"
+
+        model.changeFavoriteStatus(id, false)
+
+        assertTrue(model.favorites.none { it.controlId == id })
+
+        verify(callback).onFirstChange()
+    }
+
+    @Test
+    fun testRemoveFavorite_endOfElements() {
+        val removed = 4
+        val id = "$ID_PREFIX$removed"
+        model.changeFavoriteStatus(id, false)
+
+        assertEquals(ControlInfoWrapper(
+                TEST_COMPONENT, INITIAL_FAVORITES[4], false), model.elements.last())
+        verify(callback).onFirstChange()
+    }
+
+    @Test
+    fun testRemoveFavorite_adapterNotified() {
+        val removed = 4
+        val id = "$ID_PREFIX$removed"
+        model.changeFavoriteStatus(id, false)
+
+        val lastPos = model.elements.size - 1
+        verify(adapter).notifyItemChanged(eq(lastPos), any(Any::class.java))
+        verify(adapter).notifyItemMoved(removed, lastPos)
+
+        verify(callback).onFirstChange()
+    }
+
+    @Test
+    fun testRemoveFavorite_dividerMovedBack() {
+        val oldDividerPosition = getDividerPosition()
+        val removed = 4
+        val id = "$ID_PREFIX$removed"
+        model.changeFavoriteStatus(id, false)
+
+        assertEquals(oldDividerPosition - 1, getDividerPosition())
+
+        verify(callback).onFirstChange()
+    }
+
+    @Test
+    fun testRemoveFavorite_ShowDivider() {
+        val oldDividerPosition = getDividerPosition()
+        val removed = 4
+        val id = "$ID_PREFIX$removed"
+        model.changeFavoriteStatus(id, false)
+
+        assertTrue(dividerWrapper.showDivider)
+        verify(adapter).notifyItemChanged(oldDividerPosition)
+
+        verify(callback).onFirstChange()
+    }
+
+    @Test
+    fun testDoubleRemove_onlyOnce() {
+        val removed = 4
+        val id = "$ID_PREFIX$removed"
+        model.changeFavoriteStatus(id, false)
+        model.changeFavoriteStatus(id, false)
+
+        verify(adapter /* only once */).notifyItemChanged(anyInt(), any(Any::class.java))
+        verify(adapter /* only once */).notifyItemMoved(anyInt(), anyInt())
+        verify(adapter /* only once (divider) */).notifyItemChanged(anyInt())
+
+        verify(callback).onFirstChange()
+    }
+
+    @Test
+    fun testRemoveTwo_InSameOrder() {
+        val removedFirst = 3
+        val removedSecond = 0
+        model.changeFavoriteStatus("$ID_PREFIX$removedFirst", false)
+        model.changeFavoriteStatus("$ID_PREFIX$removedSecond", false)
+
+        assertEquals(listOf(
+                ControlInfoWrapper(TEST_COMPONENT, INITIAL_FAVORITES[removedFirst], false),
+                ControlInfoWrapper(TEST_COMPONENT, INITIAL_FAVORITES[removedSecond], false)
+        ), model.elements.takeLast(2))
+
+        verify(callback).onFirstChange()
+    }
+
+    @Test
+    fun testRemoveAll_showNone() {
+        INITIAL_FAVORITES.forEach {
+            model.changeFavoriteStatus(it.controlId, false)
+        }
+        assertEquals(dividerWrapper, model.elements.first())
+        assertTrue(dividerWrapper.showNone)
+        verify(adapter, times(2)).notifyItemChanged(anyInt()) // divider
+        verify(callback).onNoneChanged(true)
+
+        verify(callback).onFirstChange()
+    }
+
+    @Test
+    fun testAddFavorite_movedToEnd() {
+        val added = 2
+        val id = "$ID_PREFIX$added"
+        model.changeFavoriteStatus(id, false)
+        model.changeFavoriteStatus(id, true)
+
+        assertEquals(id, model.favorites.last().controlId)
+
+        verify(callback).onFirstChange()
+    }
+
+    @Test
+    fun testAddFavorite_onlyOnce() {
+        val added = 2
+        val id = "$ID_PREFIX$added"
+        model.changeFavoriteStatus(id, false)
+        model.changeFavoriteStatus(id, true)
+        model.changeFavoriteStatus(id, true)
+
+        // Once for remove and once for add
+        verify(adapter, times(2)).notifyItemChanged(anyInt(), any(Any::class.java))
+        verify(adapter, times(2)).notifyItemMoved(anyInt(), anyInt())
+
+        verify(callback).onFirstChange()
+    }
+
+    @Test
+    fun testAddFavorite_notRemoved() {
+        val added = 2
+        val id = "$ID_PREFIX$added"
+        model.changeFavoriteStatus(id, true)
+
+        verifyNoMoreInteractions(adapter)
+
+        verify(callback, never()).onFirstChange()
+    }
+
+    @Test
+    fun testAddOnlyRemovedFavorite_dividerStopsShowing() {
+        val added = 2
+        val id = "$ID_PREFIX$added"
+        model.changeFavoriteStatus(id, false)
+        model.changeFavoriteStatus(id, true)
+
+        assertFalse(dividerWrapper.showDivider)
+        val inOrder = inOrder(adapter)
+        inOrder.verify(adapter).notifyItemChanged(model.elements.size - 1)
+        inOrder.verify(adapter).notifyItemChanged(model.elements.size - 2)
+
+        verify(callback).onFirstChange()
+    }
+
+    @Test
+    fun testAddFirstFavorite_dividerNotShowsNone() {
+        INITIAL_FAVORITES.forEach {
+            model.changeFavoriteStatus(it.controlId, false)
+        }
+
+        verify(callback).onNoneChanged(true)
+
+        model.changeFavoriteStatus("${ID_PREFIX}3", true)
+        assertEquals(1, getDividerPosition())
+
+        verify(callback).onNoneChanged(false)
+
+        verify(callback).onFirstChange()
+    }
+
+    @Test
+    fun testMoveBetweenFavorites() {
+        val from = 2
+        val to = 4
+
+        model.onMoveItem(from, to)
+        assertEquals(
+                listOf(0, 1, 3, 4, 2, 5).map { "$ID_PREFIX$it" },
+                model.favorites.map(ControlInfo::controlId)
+        )
+        verify(adapter).notifyItemMoved(from, to)
+        verify(adapter, never()).notifyItemChanged(anyInt(), any(Any::class.java))
+
+        verify(callback).onFirstChange()
+    }
+
+    private fun getDividerPosition(): Int = model.elements.indexOf(dividerWrapper)
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
index 300cb21..3af164d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
@@ -32,6 +32,7 @@
 import android.telephony.TelephonyManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.util.FeatureFlagUtils;
 import android.view.IWindowManager;
 
 import androidx.test.filters.SmallTest;
@@ -66,7 +67,7 @@
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
+@TestableLooper.RunWithLooper()
 public class GlobalActionsDialogTest extends SysuiTestCase {
     private GlobalActionsDialog mGlobalActionsDialog;
 
@@ -143,11 +144,60 @@
                 mUiEventLogger,
                 mRingerModeTracker
         );
+        mGlobalActionsDialog.setZeroDialogPressDelayForTesting();
     }
+
     @Test
     public void testShouldLogVisibility() {
         mGlobalActionsDialog.onShow(null);
+        mTestableLooper.processAllMessages();
+        verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_POWER_MENU_OPEN);
+    }
+
+    @Test
+    public void testShouldLogBugreportPress() throws InterruptedException {
+        GlobalActionsDialog.BugReportAction bugReportAction =
+                mGlobalActionsDialog.makeBugReportActionForTesting();
+        bugReportAction.onPress();
+        verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_BUGREPORT_PRESS);
+    }
+
+    @Test
+    public void testShouldLogBugreportLongPress() {
+        GlobalActionsDialog.BugReportAction bugReportAction =
+                mGlobalActionsDialog.makeBugReportActionForTesting();
+        bugReportAction.onLongPress();
+        verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_BUGREPORT_LONG_PRESS);
+    }
+
+    @Test
+    public void testShouldLogEmergencyDialerPress() {
+        GlobalActionsDialog.EmergencyDialerAction emergencyDialerAction =
+                mGlobalActionsDialog.makeEmergencyDialerActionForTesting();
+        emergencyDialerAction.onPress();
+        verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_EMERGENCY_DIALER_PRESS);
+    }
+
+    @Test
+    public void testShouldLogScreenshotPress() {
+        GlobalActionsDialog.ScreenshotAction screenshotAction =
+                mGlobalActionsDialog.makeScreenshotActionForTesting();
+        screenshotAction.onPress();
+        verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_SCREENSHOT_PRESS);
+    }
+
+    @Test
+    public void testShouldLogScreenshotLongPress() {
+        FeatureFlagUtils.setEnabled(mContext, FeatureFlagUtils.SCREENRECORD_LONG_PRESS, true);
+        GlobalActionsDialog.ScreenshotAction screenshotAction =
+                mGlobalActionsDialog.makeScreenshotActionForTesting();
+        screenshotAction.onLongPress();
+        verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_SCREENSHOT_LONG_PRESS);
+    }
+
+    private void verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent event) {
+        mTestableLooper.processAllMessages();
         verify(mUiEventLogger, times(1))
-                .log(GlobalActionsDialog.GlobalActionsEvent.GA_POWER_MENU_OPEN);
+                .log(event);
     }
 }
diff --git a/packages/Tethering/Android.bp b/packages/Tethering/Android.bp
index 190b443..5b052df 100644
--- a/packages/Tethering/Android.bp
+++ b/packages/Tethering/Android.bp
@@ -25,7 +25,7 @@
     ],
     static_libs: [
         "androidx.annotation_annotation",
-        "netd_aidl_interface-unstable-java",
+        "netd_aidl_interface-V3-java",
         "netlink-client",
         "networkstack-aidl-interfaces-unstable-java",
         "android.hardware.tetheroffload.config-V1.0-java",
diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index 3509801..cc095a0 100644
--- a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -571,9 +571,8 @@
             /**
              * Configure tethering with static IPv4 assignment.
              *
-             * The clientAddress must be in the localIPv4Address prefix. A DHCP server will be
-             * started, but will only be able to offer the client address. The two addresses must
-             * be in the same prefix.
+             * A DHCP server will be started, but will only be able to offer the client address.
+             * The two addresses must be in the same prefix.
              *
              * @param localIPv4Address The preferred local IPv4 link address to use.
              * @param clientAddress The static client address.
@@ -584,10 +583,7 @@
                     @NonNull final LinkAddress clientAddress) {
                 Objects.requireNonNull(localIPv4Address);
                 Objects.requireNonNull(clientAddress);
-                if (localIPv4Address.getPrefixLength() != clientAddress.getPrefixLength()
-                        || !localIPv4Address.isIpv4() || !clientAddress.isIpv4()
-                        || !new IpPrefix(localIPv4Address.toString()).equals(
-                        new IpPrefix(clientAddress.toString()))) {
+                if (!checkStaticAddressConfiguration(localIPv4Address, clientAddress)) {
                     throw new IllegalArgumentException("Invalid server or client addresses");
                 }
 
@@ -657,6 +653,19 @@
         }
 
         /**
+         * Check whether the two addresses are ipv4 and in the same prefix.
+         * @hide
+         */
+        public static boolean checkStaticAddressConfiguration(
+                @NonNull final LinkAddress localIPv4Address,
+                @NonNull final LinkAddress clientAddress) {
+            return localIPv4Address.getPrefixLength() == clientAddress.getPrefixLength()
+                    && localIPv4Address.isIpv4() && clientAddress.isIpv4()
+                    && new IpPrefix(localIPv4Address.toString()).equals(
+                    new IpPrefix(clientAddress.toString()));
+        }
+
+        /**
          * Get a TetheringRequestParcel from the configuration
          * @hide
          */
diff --git a/packages/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java b/packages/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java
index d6bc063..82a26be 100644
--- a/packages/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java
+++ b/packages/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java
@@ -18,10 +18,12 @@
 
 import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTH;
 
-import android.annotation.NonNull;
 import android.net.LinkAddress;
 import android.util.ArraySet;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import java.net.Inet4Address;
 import java.util.Collection;
 import java.util.Collections;
@@ -160,6 +162,17 @@
         return this;
     }
 
+    /**
+     * Set the client address to tell DHCP server only offer this address.
+     * The client's prefix length is the same as server's.
+     *
+     * <p>If not set, the default value is null.
+     */
+    public DhcpServingParamsParcelExt setSingleClientAddr(@Nullable Inet4Address clientAddr) {
+        this.clientAddr = clientAddr == null ? 0 : inet4AddressToIntHTH(clientAddr);
+        return this;
+    }
+
     private static int[] toIntArray(@NonNull Collection<Inet4Address> addrs) {
         int[] res = new int[addrs.size()];
         int i = 0;
diff --git a/packages/Tethering/src/android/net/ip/IpServer.java b/packages/Tethering/src/android/net/ip/IpServer.java
index 5b6fe91..1dac5b7 100644
--- a/packages/Tethering/src/android/net/ip/IpServer.java
+++ b/packages/Tethering/src/android/net/ip/IpServer.java
@@ -18,6 +18,7 @@
 
 import static android.net.InetAddresses.parseNumericAddress;
 import static android.net.RouteInfo.RTN_UNICAST;
+import static android.net.TetheringManager.TetheringRequest.checkStaticAddressConfiguration;
 import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
 import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH;
 import static android.net.util.NetworkConstants.FF;
@@ -511,17 +512,24 @@
         }
     }
 
-    private boolean startDhcp(Inet4Address addr, int prefixLen) {
+    private boolean startDhcp(final LinkAddress serverLinkAddr, final LinkAddress clientLinkAddr) {
         if (mUsingLegacyDhcp) {
             return true;
         }
+
+        final Inet4Address addr = (Inet4Address) serverLinkAddr.getAddress();
+        final int prefixLen = serverLinkAddr.getPrefixLength();
+        final Inet4Address clientAddr = clientLinkAddr == null ? null :
+                (Inet4Address) clientLinkAddr.getAddress();
+
         final DhcpServingParamsParcel params;
         params = new DhcpServingParamsParcelExt()
                 .setDefaultRouters(addr)
                 .setDhcpLeaseTimeSecs(DHCP_LEASE_TIME_SECS)
                 .setDnsServers(addr)
-                .setServerAddr(new LinkAddress(addr, prefixLen))
-                .setMetered(true);
+                .setServerAddr(serverLinkAddr)
+                .setMetered(true)
+                .setSingleClientAddr(clientAddr);
         // TODO: also advertise link MTU
 
         mDhcpServerStartIndex++;
@@ -556,9 +564,10 @@
         }
     }
 
-    private boolean configureDhcp(boolean enable, Inet4Address addr, int prefixLen) {
+    private boolean configureDhcp(boolean enable, final LinkAddress serverAddr,
+            final LinkAddress clientAddr) {
         if (enable) {
-            return startDhcp(addr, prefixLen);
+            return startDhcp(serverAddr, clientAddr);
         } else {
             stopDhcp();
             return true;
@@ -606,7 +615,7 @@
                 // code that calls into NetworkManagementService directly.
                 srvAddr = (Inet4Address) parseNumericAddress(BLUETOOTH_IFACE_ADDR);
                 mIpv4Address = new LinkAddress(srvAddr, BLUETOOTH_DHCP_PREFIX_LENGTH);
-                return configureDhcp(enabled, srvAddr, BLUETOOTH_DHCP_PREFIX_LENGTH);
+                return configureDhcp(enabled, mIpv4Address, null /* clientAddress */);
             }
             mIpv4Address = new LinkAddress(srvAddr, prefixLen);
         } catch (IllegalArgumentException e) {
@@ -643,7 +652,7 @@
             mLinkProperties.removeRoute(route);
         }
 
-        return configureDhcp(enabled, srvAddr, prefixLen);
+        return configureDhcp(enabled, mIpv4Address, mStaticIpv4ClientAddr);
     }
 
     private String getRandomWifiIPv4Address() {
@@ -962,7 +971,14 @@
     }
 
     private void maybeConfigureStaticIp(final TetheringRequestParcel request) {
-        if (request == null) return;
+        // Ignore static address configuration if they are invalid or null. In theory, static
+        // addresses should not be invalid here because TetheringManager do not allow caller to
+        // specify invalid static address configuration.
+        if (request == null || request.localIPv4Address == null
+                || request.staticClientAddress == null || !checkStaticAddressConfiguration(
+                request.localIPv4Address, request.staticClientAddress)) {
+            return;
+        }
 
         mStaticIpv4ServerAddr = request.localIPv4Address;
         mStaticIpv4ClientAddr = request.staticClientAddress;
diff --git a/packages/Tethering/tests/unit/src/android/net/dhcp/DhcpServingParamsParcelExtTest.java b/packages/Tethering/tests/unit/src/android/net/dhcp/DhcpServingParamsParcelExtTest.java
index e8add98..f8eb147 100644
--- a/packages/Tethering/tests/unit/src/android/net/dhcp/DhcpServingParamsParcelExtTest.java
+++ b/packages/Tethering/tests/unit/src/android/net/dhcp/DhcpServingParamsParcelExtTest.java
@@ -42,7 +42,9 @@
 @SmallTest
 public class DhcpServingParamsParcelExtTest {
     private static final Inet4Address TEST_ADDRESS = inet4Addr("192.168.0.123");
+    private static final Inet4Address TEST_CLIENT_ADDRESS = inet4Addr("192.168.0.42");
     private static final int TEST_ADDRESS_PARCELED = 0xc0a8007b;
+    private static final int TEST_CLIENT_ADDRESS_PARCELED = 0xc0a8002a;
     private static final int TEST_PREFIX_LENGTH = 17;
     private static final int TEST_LEASE_TIME_SECS = 120;
     private static final int TEST_MTU = 1000;
@@ -105,6 +107,12 @@
         assertFalse(mParcel.metered);
     }
 
+    @Test
+    public void testSetClientAddr() {
+        mParcel.setSingleClientAddr(TEST_CLIENT_ADDRESS);
+        assertEquals(TEST_CLIENT_ADDRESS_PARCELED, mParcel.clientAddr);
+    }
+
     private static Inet4Address inet4Addr(String addr) {
         return (Inet4Address) parseNumericAddress(addr);
     }
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
index 3a580dd..2955903c 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
@@ -38,6 +38,7 @@
 import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STARTED;
 import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STOPPED;
 import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
+import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH;
 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME;
 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_MODE;
 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_STATE;
@@ -1668,10 +1669,13 @@
     }
 
     @Test
-    public void testRequestStaticServerIp() throws Exception {
-        final LinkAddress serverLinkAddr = new LinkAddress("192.168.20.1/24");
-        final LinkAddress clientLinkAddr = new LinkAddress("192.168.20.42/24");
-        final String serverAddr = "192.168.20.1";
+    public void testRequestStaticIp() throws Exception {
+        final LinkAddress serverLinkAddr = new LinkAddress("192.168.0.123/24");
+        final LinkAddress clientLinkAddr = new LinkAddress("192.168.0.42/24");
+        final String serverAddr = "192.168.0.123";
+        final int clientAddrParceled = 0xc0a8002a;
+        final ArgumentCaptor<DhcpServingParamsParcel> dhcpParamsCaptor =
+                ArgumentCaptor.forClass(DhcpServingParamsParcel.class);
         mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB,
                   serverLinkAddr, clientLinkAddr), null);
         mLooper.dispatchAll();
@@ -1680,8 +1684,12 @@
         sendUsbBroadcast(true, true, true, TETHERING_USB);
         mLooper.dispatchAll();
         verify(mNetd).interfaceSetCfg(argThat(cfg -> serverAddr.equals(cfg.ipv4Addr)));
-
-        // TODO: test static client address.
+        verify(mIpServerDependencies, times(1)).makeDhcpServer(any(), dhcpParamsCaptor.capture(),
+                any());
+        final DhcpServingParamsParcel params = dhcpParamsCaptor.getValue();
+        assertEquals(serverAddr, intToInet4AddressHTH(params.serverAddr).getHostAddress());
+        assertEquals(24, params.serverAddrPrefixLength);
+        assertEquals(clientAddrParceled, params.clientAddr);
     }
 
     // TODO: Test that a request for hotspot mode doesn't interfere with an
diff --git a/proto/src/typed_features.proto b/proto/src/typed_features.proto
new file mode 100644
index 0000000..c2b3b18
--- /dev/null
+++ b/proto/src/typed_features.proto
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+package com.android.service;
+option java_multiple_files = true;
+
+// This message is to specify feature params that are a list of strings.
+message StringListParamProto {
+  repeated string element = 1;
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/am/BugReportHandlerUtil.java b/services/core/java/com/android/server/am/BugReportHandlerUtil.java
index ba89fce..0a0d8d8 100644
--- a/services/core/java/com/android/server/am/BugReportHandlerUtil.java
+++ b/services/core/java/com/android/server/am/BugReportHandlerUtil.java
@@ -16,15 +16,20 @@
 
 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;
@@ -43,6 +48,8 @@
     private static final String SHELL_APP_PACKAGE = "com.android.shell";
     private static final String INTENT_BUGREPORT_REQUESTED =
             "com.android.internal.intent.action.BUGREPORT_REQUESTED";
+    private static final String INTENT_GET_BUGREPORT_HANDLER_RESPONSE =
+            "com.android.internal.intent.action.GET_BUGREPORT_HANDLER_RESPONSE";
 
     /**
      * Check is BugReportHandler enabled on the device.
@@ -100,6 +107,43 @@
             return false;
         }
 
+        if (getBugReportHandlerAppResponseReceivers(context, handlerApp, handlerUser).isEmpty()) {
+            // Just try to launch bugreport handler app to handle bugreport request
+            // because the bugreport handler app is old and not support to provide response to
+            // let BugReportHandlerUtil know it is available or not.
+            launchBugReportHandlerApp(context, handlerApp, handlerUser);
+            return true;
+        }
+
+        Slog.i(TAG, "Getting response from bug report handler app: " + handlerApp);
+        Intent intent = new Intent(INTENT_GET_BUGREPORT_HANDLER_RESPONSE);
+        intent.setPackage(handlerApp);
+        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            // Handler app's BroadcastReceiver should call setResultCode(Activity.RESULT_OK) to
+            // let BugreportHandlerResponseBroadcastReceiver know the handler app is available.
+            context.sendOrderedBroadcastAsUser(intent,
+                    UserHandle.of(handlerUser),
+                    android.Manifest.permission.DUMP,
+                    OP_NONE, /* options= */ null,
+                    new BugreportHandlerResponseBroadcastReceiver(handlerApp, handlerUser),
+                    /* scheduler= */ null,
+                    Activity.RESULT_CANCELED,
+                    /* initialData= */ null,
+                    /* initialExtras= */ null);
+        } catch (RuntimeException e) {
+            Slog.e(TAG, "Error while trying to get response from bug report handler app.", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+        return true;
+    }
+
+    private static void launchBugReportHandlerApp(Context context, String handlerApp,
+            int handlerUser) {
         Slog.i(TAG, "Launching bug report handler app: " + handlerApp);
         Intent intent = new Intent(INTENT_BUGREPORT_REQUESTED);
         intent.setPackage(handlerApp);
@@ -115,11 +159,9 @@
                     options.toBundle());
         } catch (RuntimeException e) {
             Slog.e(TAG, "Error while trying to launch bugreport handler app.", e);
-            return false;
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
-        return true;
     }
 
     private static String getCustomBugReportHandlerApp(Context context) {
@@ -159,6 +201,16 @@
                         handlerUser);
     }
 
+    private static List<ResolveInfo> getBugReportHandlerAppResponseReceivers(Context context,
+            String handlerApp, int handlerUser) {
+        // Use the app package and the user id to retrieve the receiver that can provide response
+        Intent intent = new Intent(INTENT_GET_BUGREPORT_HANDLER_RESPONSE);
+        intent.setPackage(handlerApp);
+        return context.getPackageManager()
+                .queryBroadcastReceiversAsUser(intent, PackageManager.MATCH_SYSTEM_ONLY,
+                        handlerUser);
+    }
+
     private static String getDefaultBugReportHandlerApp(Context context) {
         return context.getResources().getString(
                 com.android.internal.R.string.config_defaultBugReportHandlerApp);
@@ -176,4 +228,30 @@
             Binder.restoreCallingIdentity(identity);
         }
     }
+
+    private static class BugreportHandlerResponseBroadcastReceiver extends BroadcastReceiver {
+        private final String handlerApp;
+        private final int handlerUser;
+
+        BugreportHandlerResponseBroadcastReceiver(String handlerApp, int handlerUser) {
+            this.handlerApp = handlerApp;
+            this.handlerUser = handlerUser;
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (getResultCode() == Activity.RESULT_OK) {
+                // Try to launch bugreport handler app to handle bugreport request because the
+                // bugreport handler app is available.
+                launchBugReportHandlerApp(context, handlerApp, handlerUser);
+                return;
+            }
+
+            Slog.w(TAG, "Request bug report because no response from handler app.");
+            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/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
index 2aa53cc..a5de90c 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
@@ -169,6 +169,8 @@
     }
 
     public void rebindIfDisconnected() {
+        //TODO: When we are connecting to the service, calling this will unbind and bind again.
+        // We'd better not unbind if we are connecting.
         if (mActiveConnection == null && shouldBind()) {
             unbind();
             bind();
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
index fe118e5..b688e09 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
@@ -83,7 +83,7 @@
 
             // Scan packages.
             // Also has the side-effect of restarting providers if needed.
-            mHandler.post(mScanPackagesRunnable);
+            postScanPackagesIfNeeded();
         }
     }
 
@@ -92,7 +92,7 @@
             mRunning = false;
 
             mContext.unregisterReceiver(mScanPackagesReceiver);
-            mHandler.removeCallbacks(mScanPackagesRunnable);
+            mHandler.removeCallbacks(this::scanPackages);
 
             // Stop all providers.
             for (int i = mProxies.size() - 1; i >= 0; i--) {
@@ -154,20 +154,19 @@
         return -1;
     }
 
+    private void postScanPackagesIfNeeded() {
+        if (!mHandler.hasCallbacks(this::scanPackages)) {
+            mHandler.post(this::scanPackages);
+        }
+    }
+
     private final BroadcastReceiver mScanPackagesReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             if (DEBUG) {
                 Slog.d(TAG, "Received package manager broadcast: " + intent);
             }
-            scanPackages();
-        }
-    };
-
-    private final Runnable mScanPackagesRunnable = new Runnable() {
-        @Override
-        public void run() {
-            scanPackages();
+            postScanPackagesIfNeeded();
         }
     };
 
diff --git a/services/core/java/com/android/server/om/IdmapManager.java b/services/core/java/com/android/server/om/IdmapManager.java
index 43fc7ed..90c85ad 100644
--- a/services/core/java/com/android/server/om/IdmapManager.java
+++ b/services/core/java/com/android/server/om/IdmapManager.java
@@ -63,20 +63,23 @@
         mIdmapDaemon = IdmapDaemon.getInstance();
     }
 
+    /**
+     * Creates the idmap for the target/overlay combination and returns whether the idmap file was
+     * modified.
+     */
     boolean createIdmap(@NonNull final PackageInfo targetPackage,
             @NonNull final PackageInfo overlayPackage, int userId) {
         if (DEBUG) {
             Slog.d(TAG, "create idmap for " + targetPackage.packageName + " and "
                     + overlayPackage.packageName);
         }
-        final int sharedGid = UserHandle.getSharedAppGid(targetPackage.applicationInfo.uid);
         final String targetPath = targetPackage.applicationInfo.getBaseCodePath();
         final String overlayPath = overlayPackage.applicationInfo.getBaseCodePath();
         try {
             int policies = calculateFulfilledPolicies(targetPackage, overlayPackage, userId);
             boolean enforce = enforceOverlayable(overlayPackage);
             if (mIdmapDaemon.verifyIdmap(targetPath, overlayPath, policies, enforce, userId)) {
-                return true;
+                return false;
             }
             return mIdmapDaemon.createIdmap(targetPath, overlayPath, policies,
                     enforce, userId) != null;
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index d108e76..05a4a38 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -700,14 +700,15 @@
         final PackageInfo overlayPackage = mPackageManager.getPackageInfo(overlayPackageName,
                 userId);
 
-        // Immutable RROs targeting to "android", ie framework-res.apk, are handled by native layers.
+        // Immutable RROs targeting to "android", ie framework-res.apk, are handled by native
+        // layers.
+        boolean modified = false;
         if (targetPackage != null && overlayPackage != null
                 && !("android".equals(targetPackageName)
                     && !isPackageConfiguredMutable(overlayPackageName))) {
-            mIdmapManager.createIdmap(targetPackage, overlayPackage, userId);
+            modified |= mIdmapManager.createIdmap(targetPackage, overlayPackage, userId);
         }
 
-        boolean modified = false;
         if (overlayPackage != null) {
             modified |= mSettings.setBaseCodePath(overlayPackageName, userId,
                     overlayPackage.applicationInfo.getBaseCodePath());
diff --git a/services/core/java/com/android/server/om/TEST_MAPPING b/services/core/java/com/android/server/om/TEST_MAPPING
index 75229a1..6edd76f 100644
--- a/services/core/java/com/android/server/om/TEST_MAPPING
+++ b/services/core/java/com/android/server/om/TEST_MAPPING
@@ -15,6 +15,9 @@
       "name": "OverlayHostTests"
     },
     {
+      "name": "OverlayRemountedTest"
+    },
+    {
       "name": "CtsAppSecurityHostTestCases",
       "options": [
         {
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index eb7057d..6dcf71e 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -232,6 +232,10 @@
     private boolean isSystemServerDexPathSupportedForOdex(String dexPath) {
         ArrayList<PackagePartitions.SystemPartition> partitions =
                 PackagePartitions.getOrderedPartitions(identity());
+        // First check the apex partition as it's not part of the SystemPartitions.
+        if (dexPath.startsWith("/apex/")) {
+            return true;
+        }
         for (int i = 0; i < partitions.size(); i++) {
             if (partitions.get(i).containsPath(dexPath)) {
                 return true;
diff --git a/services/core/java/com/android/server/stats/pull/SettingsStatsUtil.java b/services/core/java/com/android/server/stats/pull/SettingsStatsUtil.java
new file mode 100644
index 0000000..7cdb84b
--- /dev/null
+++ b/services/core/java/com/android/server/stats/pull/SettingsStatsUtil.java
@@ -0,0 +1,220 @@
+/*
+ * 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.stats.pull;
+
+import static com.android.internal.util.FrameworkStatsLog.SETTING_SNAPSHOT__TYPE__ASSIGNED_BOOL_TYPE;
+import static com.android.internal.util.FrameworkStatsLog.SETTING_SNAPSHOT__TYPE__ASSIGNED_FLOAT_TYPE;
+import static com.android.internal.util.FrameworkStatsLog.SETTING_SNAPSHOT__TYPE__ASSIGNED_INT_TYPE;
+import static com.android.internal.util.FrameworkStatsLog.SETTING_SNAPSHOT__TYPE__ASSIGNED_STRING_TYPE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.provider.DeviceConfig;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Base64;
+import android.util.Slog;
+import android.util.StatsEvent;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.service.nano.StringListParamProto;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Utility methods for creating {@link StatsEvent} data.
+ */
+final class SettingsStatsUtil {
+    private static final String TAG = "SettingsStatsUtil";
+    private static final FlagsData[] GLOBAL_SETTINGS = new FlagsData[]{
+            new FlagsData("GlobalFeature__boolean_whitelist",
+                    SETTING_SNAPSHOT__TYPE__ASSIGNED_BOOL_TYPE),
+            new FlagsData("GlobalFeature__integer_whitelist",
+                    SETTING_SNAPSHOT__TYPE__ASSIGNED_INT_TYPE),
+            new FlagsData("GlobalFeature__float_whitelist",
+                    SETTING_SNAPSHOT__TYPE__ASSIGNED_FLOAT_TYPE),
+            new FlagsData("GlobalFeature__string_whitelist",
+                    SETTING_SNAPSHOT__TYPE__ASSIGNED_STRING_TYPE)
+    };
+    private static final FlagsData[] SECURE_SETTINGS = new FlagsData[]{
+            new FlagsData("SecureFeature__boolean_whitelist",
+                    SETTING_SNAPSHOT__TYPE__ASSIGNED_BOOL_TYPE),
+            new FlagsData("SecureFeature__integer_whitelist",
+                    SETTING_SNAPSHOT__TYPE__ASSIGNED_INT_TYPE),
+            new FlagsData("SecureFeature__float_whitelist",
+                    SETTING_SNAPSHOT__TYPE__ASSIGNED_FLOAT_TYPE),
+            new FlagsData("SecureFeature__string_whitelist",
+                    SETTING_SNAPSHOT__TYPE__ASSIGNED_STRING_TYPE)
+    };
+    private static final FlagsData[] SYSTEM_SETTINGS = new FlagsData[]{
+            new FlagsData("SystemFeature__boolean_whitelist",
+                    SETTING_SNAPSHOT__TYPE__ASSIGNED_BOOL_TYPE),
+            new FlagsData("SystemFeature__integer_whitelist",
+                    SETTING_SNAPSHOT__TYPE__ASSIGNED_INT_TYPE),
+            new FlagsData("SystemFeature__float_whitelist",
+                    SETTING_SNAPSHOT__TYPE__ASSIGNED_FLOAT_TYPE),
+            new FlagsData("SystemFeature__string_whitelist",
+                    SETTING_SNAPSHOT__TYPE__ASSIGNED_STRING_TYPE)
+    };
+
+    @VisibleForTesting
+    @NonNull
+    static List<StatsEvent> logGlobalSettings(Context context, int atomTag, int userId) {
+        final List<StatsEvent> output = new ArrayList<>();
+        final ContentResolver resolver = context.getContentResolver();
+
+        for (FlagsData flagsData : GLOBAL_SETTINGS) {
+            StringListParamProto proto = getList(flagsData.mFlagName);
+            if (proto == null) {
+                continue;
+            }
+            for (String key : proto.element) {
+                final String value = Settings.Global.getStringForUser(resolver, key, userId);
+                output.add(createStatsEvent(atomTag, key, value, userId,
+                        flagsData.mDataType));
+            }
+        }
+        return output;
+    }
+
+    @NonNull
+    static List<StatsEvent> logSystemSettings(Context context, int atomTag, int userId) {
+        final List<StatsEvent> output = new ArrayList<>();
+        final ContentResolver resolver = context.getContentResolver();
+
+        for (FlagsData flagsData : SYSTEM_SETTINGS) {
+            StringListParamProto proto = getList(flagsData.mFlagName);
+            if (proto == null) {
+                continue;
+            }
+            for (String key : proto.element) {
+                final String value = Settings.System.getStringForUser(resolver, key, userId);
+                output.add(createStatsEvent(atomTag, key, value, userId,
+                        flagsData.mDataType));
+            }
+        }
+        return output;
+    }
+
+    @NonNull
+    static List<StatsEvent> logSecureSettings(Context context, int atomTag, int userId) {
+        final List<StatsEvent> output = new ArrayList<>();
+        final ContentResolver resolver = context.getContentResolver();
+
+        for (FlagsData flagsData : SECURE_SETTINGS) {
+            StringListParamProto proto = getList(flagsData.mFlagName);
+            if (proto == null) {
+                continue;
+            }
+            for (String key : proto.element) {
+                final String value = Settings.Secure.getStringForUser(resolver, key, userId);
+                output.add(createStatsEvent(atomTag, key, value, userId,
+                        flagsData.mDataType));
+            }
+        }
+        return output;
+    }
+
+    @VisibleForTesting
+    @Nullable
+    static StringListParamProto getList(String flag) {
+        final String base64 = DeviceConfig.getProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS, flag);
+        if (TextUtils.isEmpty(base64)) {
+            return null;
+        }
+        final byte[] decode = Base64.decode(base64, Base64.NO_PADDING | Base64.NO_WRAP);
+        StringListParamProto list = null;
+        try {
+            list = StringListParamProto.parseFrom(decode);
+        } catch (Exception e) {
+            Slog.e(TAG, "Error parsing string list proto", e);
+        }
+        return list;
+    }
+
+    /**
+     * Create {@link StatsEvent} for SETTING_SNAPSHOT atom
+     */
+    @NonNull
+    private static StatsEvent createStatsEvent(int atomTag, String key, String value, int userId,
+            int type) {
+        final StatsEvent.Builder builder = StatsEvent.newBuilder()
+                .setAtomId(atomTag)
+                .writeString(key);
+        boolean booleanValue = false;
+        int intValue = 0;
+        float floatValue = 0;
+        String stringValue = "";
+        if (TextUtils.isEmpty(value)) {
+            builder.writeInt(FrameworkStatsLog.SETTING_SNAPSHOT__TYPE__NOTASSIGNED)
+                    .writeBoolean(booleanValue)
+                    .writeInt(intValue)
+                    .writeFloat(floatValue)
+                    .writeString(stringValue)
+                    .writeInt(userId);
+        } else {
+            switch (type) {
+                case SETTING_SNAPSHOT__TYPE__ASSIGNED_BOOL_TYPE:
+                    booleanValue = "1".equals(value);
+                    break;
+                case FrameworkStatsLog.SETTING_SNAPSHOT__TYPE__ASSIGNED_INT_TYPE:
+                    try {
+                        intValue = Integer.parseInt(value);
+                    } catch (NumberFormatException e) {
+                        Slog.w(TAG, "Can not parse value to float: " + value);
+                    }
+                    break;
+                case SETTING_SNAPSHOT__TYPE__ASSIGNED_FLOAT_TYPE:
+                    try {
+                        floatValue = Float.parseFloat(value);
+                    } catch (NumberFormatException e) {
+                        Slog.w(TAG, "Can not parse value to float: " + value);
+                    }
+                    break;
+                case FrameworkStatsLog.SETTING_SNAPSHOT__TYPE__ASSIGNED_STRING_TYPE:
+                    stringValue = value;
+                    break;
+                default:
+                    Slog.w(TAG, "Unexpected value type " + type);
+            }
+            builder.writeInt(type)
+                    .writeBoolean(booleanValue)
+                    .writeInt(intValue)
+                    .writeFloat(floatValue)
+                    .writeString(stringValue)
+                    .writeInt(userId);
+        }
+        return builder.build();
+    }
+
+    /** Class for defining flag name and its data type. */
+    static final class FlagsData {
+        /** {@link DeviceConfig} flag name, value of the flag is {@link StringListParamProto} */
+        String mFlagName;
+        /** Data type of the value getting from {@link Settings} keys. */
+        int mDataType;
+
+        FlagsData(String flagName, int dataType) {
+            mFlagName = flagName;
+            mDataType = dataType;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index e9da2c4..288c22a 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -416,6 +416,8 @@
                         return pullHealthHal(atomTag, data);
                     case FrameworkStatsLog.ATTRIBUTED_APP_OPS:
                         return pullAttributedAppOps(atomTag, data);
+                    case FrameworkStatsLog.SETTING_SNAPSHOT:
+                        return pullSettingsStats(atomTag, data);
                     default:
                         throw new UnsupportedOperationException("Unknown tagId=" + atomTag);
                 }
@@ -580,6 +582,7 @@
         registerFullBatteryCapacity();
         registerBatteryVoltage();
         registerBatteryCycleCount();
+        registerSettingsStats();
     }
 
     /**
@@ -3244,6 +3247,43 @@
         return StatsManager.PULL_SUCCESS;
     }
 
+    private void registerSettingsStats() {
+        int tagId = FrameworkStatsLog.SETTING_SNAPSHOT;
+        mStatsManager.setPullAtomCallback(
+                tagId,
+                null, // use default PullAtomMetadata values
+                BackgroundThread.getExecutor(),
+                mStatsCallbackImpl
+        );
+    }
+
+    int pullSettingsStats(int atomTag, List<StatsEvent> pulledData) {
+        UserManager userManager = mContext.getSystemService(UserManager.class);
+        if (userManager == null) {
+            return StatsManager.PULL_SKIP;
+        }
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            for (UserInfo user : userManager.getUsers()) {
+                final int userId = user.getUserHandle().getIdentifier();
+
+                if (userId == UserHandle.USER_SYSTEM) {
+                    pulledData.addAll(SettingsStatsUtil.logGlobalSettings(mContext, atomTag,
+                            UserHandle.USER_SYSTEM));
+                }
+                pulledData.addAll(SettingsStatsUtil.logSystemSettings(mContext, atomTag, userId));
+                pulledData.addAll(SettingsStatsUtil.logSecureSettings(mContext, atomTag, userId));
+            }
+        } catch (Exception e) {
+            Slog.e(TAG, "failed to pullSettingsStats", e);
+            return StatsManager.PULL_SKIP;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        return StatsManager.PULL_SUCCESS;
+    }
+
     // Thermal event received from vendor thermal management subsystem
     private static final class ThermalEventListener extends IThermalEventListener.Stub {
         @Override
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 856fbc7..ed9b019 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -7368,7 +7368,8 @@
         }
         final ActivityStack stack = getRootTask();
         return stack != null &&
-                stack.checkKeyguardVisibility(this, true /* shouldBeVisible */, true /* isTop */);
+                stack.checkKeyguardVisibility(this, true /* shouldBeVisible */,
+                        stack.topRunningActivity() == this /* isTop */);
     }
 
     void setTurnScreenOn(boolean turnScreenOn) {
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 4648398..a215a61 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -757,7 +757,7 @@
                 // warning toast about it.
                 mAtmService.getTaskChangeNotificationController()
                         .notifyActivityDismissingDockedStack();
-                taskDisplayArea.onSplitScreenModeDismissed();
+                taskDisplayArea.onSplitScreenModeDismissed(this);
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index 0470ffa..9de8ef7 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -2198,7 +2198,7 @@
                 // split-screen in split-screen.
                 mService.getTaskChangeNotificationController()
                         .notifyActivityDismissingDockedStack();
-                taskDisplayArea.onSplitScreenModeDismissed();
+                taskDisplayArea.onSplitScreenModeDismissed(task.getStack());
                 taskDisplayArea.mDisplayContent.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS,
                         true /* notifyClients */);
             }
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 78ee1de7..6f1ddcd 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -403,11 +403,18 @@
                 mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN;
     }
 
-
     boolean isNextAppTransitionOpenCrossProfileApps() {
         return mNextAppTransitionType == NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS;
     }
 
+    boolean isNextAppTransitionCustomFromRecents() {
+        final RecentTasks recentTasks = mService.mAtmService.getRecentTasks();
+        final String recentsPackageName =
+                (recentTasks != null) ? recentTasks.getRecentsComponent().getPackageName() : null;
+        return mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM
+                && mNextAppTransitionPackage.equals(recentsPackageName);
+    }
+
     /**
      * @return true if and only if we are currently fetching app transition specs from the future
      *         passed into {@link #overridePendingAppTransitionMultiThumbFuture}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 40243e8..80a1a45 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -198,6 +198,7 @@
 import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.view.WindowManagerPolicyConstants.PointerEventListener;
+import android.window.ITaskOrganizer;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
@@ -3421,10 +3422,7 @@
 
     private void setInputMethodTarget(WindowState target, boolean targetWaitingAnim) {
         // Always update control target. This is needed to handle rotation.
-        // We cannot set target as the control target, because mInputMethodTarget can only help
-        // decide the z-order of IME, but cannot control IME. Only the IME target reported from
-        // updateInputMethodTargetWindow can control IME.
-        updateImeControlTarget(mInputMethodControlTarget);
+        updateImeControlTarget(target);
         if (target == mInputMethodTarget && mInputMethodTargetWaitingAnim == targetWaitingAnim) {
             return;
         }
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index a031fe8..0a9878d 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -442,10 +442,6 @@
             // Always prepare an app transition since we rely on the transition callbacks to cleanup
             mWindowManager.prepareAppTransition(TRANSIT_NONE, false);
             controller.setCancelOnNextTransitionStart();
-        } else {
-            // Just cancel directly to unleash from launcher when the next launching task is the
-            // current top task.
-            mWindowManager.cancelRecentsAnimation(REORDER_KEEP_IN_PLACE, "stackOrderChanged");
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 6fda117..54210ae 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -43,6 +43,7 @@
 import android.os.SystemClock;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.IntArray;
 import android.util.Slog;
 import android.util.SparseBooleanArray;
 import android.util.proto.ProtoOutputStream;
@@ -99,6 +100,8 @@
     private IRecentsAnimationRunner mRunner;
     private final RecentsAnimationCallbacks mCallbacks;
     private final ArrayList<TaskAnimationAdapter> mPendingAnimations = new ArrayList<>();
+    private final IntArray mPendingNewTaskTargets = new IntArray(0);
+
     private final ArrayList<WallpaperAnimationAdapter> mPendingWallpaperAnimations =
             new ArrayList<>();
     private final int mDisplayId;
@@ -220,6 +223,10 @@
                     if (mCanceled) {
                         return;
                     }
+                    // Remove all new task targets.
+                    for (int i = mPendingNewTaskTargets.size() - 1; i >= 0; i--) {
+                        removeTaskInternal(mPendingNewTaskTargets.get(i));
+                    }
                 }
 
                 // Note, the callback will handle its own synchronization, do not lock on WM lock
@@ -310,6 +317,18 @@
                 mWillFinishToHome = willFinishToHome;
             }
         }
+
+        @Override
+        public boolean removeTask(int taskId) {
+            final long token = Binder.clearCallingIdentity();
+            try {
+                synchronized (mService.getWindowManagerLock()) {
+                    return removeTaskInternal(taskId);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
     };
 
     /**
@@ -405,11 +424,17 @@
 
     @VisibleForTesting
     AnimationAdapter addAnimation(Task task, boolean isRecentTaskInvisible) {
+        return addAnimation(task, isRecentTaskInvisible, null /* finishedCallback */);
+    }
+
+    @VisibleForTesting
+    AnimationAdapter addAnimation(Task task, boolean isRecentTaskInvisible,
+            OnAnimationFinishedCallback finishedCallback) {
         ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "addAnimation(%s)", task.getName());
         final TaskAnimationAdapter taskAdapter = new TaskAnimationAdapter(task,
                 isRecentTaskInvisible);
         task.startAnimation(task.getPendingTransaction(), taskAdapter, false /* hidden */,
-                ANIMATION_TYPE_RECENTS);
+                ANIMATION_TYPE_RECENTS, finishedCallback);
         task.commitPendingTransaction();
         mPendingAnimations.add(taskAdapter);
         return taskAdapter;
@@ -489,6 +514,49 @@
         }
     }
 
+    void addTaskToTargets(Task task, OnAnimationFinishedCallback finishedCallback) {
+        if (mRunner != null) {
+            final RemoteAnimationTarget target = createTaskRemoteAnimation(task, finishedCallback);
+            if (target == null) return;
+
+            ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "addTaskToTargets, target: %s", target);
+            try {
+                mRunner.onTaskAppeared(target);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to report task appeared", e);
+            }
+        }
+    }
+
+    private RemoteAnimationTarget createTaskRemoteAnimation(Task task,
+            OnAnimationFinishedCallback finishedCallback) {
+        final SparseBooleanArray recentTaskIds =
+                mService.mAtmService.getRecentTasks().getRecentTaskIds();
+        TaskAnimationAdapter adapter = (TaskAnimationAdapter) addAnimation(task,
+                !recentTaskIds.get(task.mTaskId), finishedCallback);
+        mPendingNewTaskTargets.add(task.mTaskId);
+        return adapter.createRemoteAnimationTarget();
+    }
+
+    private boolean removeTaskInternal(int taskId) {
+        boolean result = false;
+        for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
+            // Only allows when task target has became visible to user, to prevent
+            // the flickering during remove animation and task visible.
+            final TaskAnimationAdapter target = mPendingAnimations.get(i);
+            if (target.mTask.mTaskId == taskId && target.mTask.isOnTop()) {
+                removeAnimation(target);
+                final int taskIndex = mPendingNewTaskTargets.indexOf(taskId);
+                if (taskIndex != -1) {
+                    mPendingNewTaskTargets.remove(taskIndex);
+                }
+                result = true;
+                break;
+            }
+        }
+        return result;
+    }
+
     private RemoteAnimationTarget[] createAppAnimations() {
         final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>();
         for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 11fda36..26d6c78 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -66,6 +66,7 @@
 import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
 import static com.android.server.wm.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC;
 import static com.android.server.wm.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
+import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE;
 import static com.android.server.wm.RootWindowContainerProto.IS_HOME_RECENTS_COMPONENT;
 import static com.android.server.wm.RootWindowContainerProto.KEYGUARD_CONTROLLER;
 import static com.android.server.wm.RootWindowContainerProto.PENDING_ACTIVITIES;
@@ -664,7 +665,7 @@
 
     void setSecureSurfaceState(int userId, boolean disabled) {
         forAllWindows((w) -> {
-            if (w.mHasSurface && userId == UserHandle.getUserId(w.mOwnerUid)) {
+            if (w.mHasSurface && userId == w.mShowUserId) {
                 w.mWinAnimator.setSecureLocked(disabled);
             }
         }, true /* traverseTopToBottom */);
@@ -1514,6 +1515,7 @@
         // Updates the extra information of the intent.
         if (fromHomeKey) {
             homeIntent.putExtra(WindowManagerPolicy.EXTRA_FROM_HOME_KEY, true);
+            mWindowManager.cancelRecentsAnimation(REORDER_KEEP_IN_PLACE, "startHomeActivity");
         }
         // Update the reason for ANR debugging to verify if the user activity is the one that
         // actually launched.
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 7d8a56e..56147f2 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -162,7 +162,19 @@
             InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) {
         return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
                 outContentInsets, outStableInsets, outDisplayCutout, outInputChannel,
-                outInsetsState, outActiveControls);
+                outInsetsState, outActiveControls, UserHandle.getUserId(mUid));
+    }
+
+
+    @Override
+    public int addToDisplayAsUser(IWindow window, int seq, WindowManager.LayoutParams attrs,
+            int viewVisibility, int displayId, int userId, Rect outFrame,
+            Rect outContentInsets, Rect outStableInsets,
+            DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
+            InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) {
+        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
+                outContentInsets, outStableInsets, outDisplayCutout, outInputChannel,
+                outInsetsState, outActiveControls, userId);
     }
 
     @Override
@@ -172,7 +184,7 @@
         return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
                 new Rect() /* outFrame */, outContentInsets, outStableInsets,
                 new DisplayCutout.ParcelableWrapper() /* cutout */, null /* outInputChannel */,
-                outInsetsState, null);
+                outInsetsState, null, UserHandle.getUserId(mUid));
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 1e70573..fd32724 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -86,6 +86,8 @@
 import static com.android.server.wm.IdentifierProto.TITLE;
 import static com.android.server.wm.IdentifierProto.USER_ID;
 import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
+import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS;
+import static com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
 import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
 import static com.android.server.wm.WindowContainerChildProto.TASK;
@@ -135,6 +137,7 @@
 import android.view.RemoteAnimationTarget;
 import android.view.Surface;
 import android.view.SurfaceControl;
+import android.view.WindowManager;
 import android.window.ITaskOrganizer;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -3402,6 +3405,24 @@
     }
 
     @Override
+    protected void applyAnimationUnchecked(WindowManager.LayoutParams lp, boolean enter,
+            int transit, boolean isVoiceInteraction,
+            @Nullable OnAnimationFinishedCallback finishedCallback) {
+        final RecentsAnimationController control = mWmService.getRecentsAnimationController();
+        if (control != null && enter
+                && getDisplayContent().mAppTransition.isNextAppTransitionCustomFromRecents()) {
+            ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
+                    "addTaskToRecentsAnimationIfNeeded, control: %s, task: %s, transit: %s",
+                    control, asTask(), AppTransition.appTransitionToString(transit));
+            // We let the transition to be controlled by RecentsAnimation, and callback task's
+            // RemoteAnimationTarget for remote runner to animate.
+            control.addTaskToTargets(getRootTask(), finishedCallback);
+        } else {
+            super.applyAnimationUnchecked(lp, enter, transit, isVoiceInteraction, finishedCallback);
+        }
+    }
+
+    @Override
     void dump(PrintWriter pw, String prefix, boolean dumpAll) {
         super.dump(pw, prefix, dumpAll);
         final String doublePrefix = prefix + "  ";
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index fee1c80..735bef8 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -1165,17 +1165,23 @@
     }
 
     void onSplitScreenModeDismissed() {
+        onSplitScreenModeDismissed(null /* toTop */);
+    }
+
+    void onSplitScreenModeDismissed(ActivityStack toTop) {
         mAtmService.deferWindowLayout();
         try {
             mLaunchRootTask = null;
             moveSplitScreenTasksToFullScreen();
         } finally {
-            final ActivityStack topFullscreenStack =
-                    getTopStackInWindowingMode(WINDOWING_MODE_FULLSCREEN);
+            final ActivityStack topFullscreenStack = toTop != null
+                    ? toTop : getTopStackInWindowingMode(WINDOWING_MODE_FULLSCREEN);
             final ActivityStack homeStack = getOrCreateRootHomeTask();
-            if (topFullscreenStack != null && homeStack != null && !isTopStack(homeStack)) {
+            if (homeStack != null && ((topFullscreenStack != null && !isTopStack(homeStack))
+                    || toTop != null)) {
                 // Whenever split-screen is dismissed we want the home stack directly behind the
                 // current top fullscreen stack so it shows up when the top stack is finished.
+                // Or, if the caller specified a stack to be on top after split-screen is dismissed.
                 // TODO: Would be better to use ActivityDisplay.positionChildAt() for this, however
                 // ActivityDisplay doesn't have a direct controller to WM side yet. We can switch
                 // once we have that.
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index ecc07bd..fba22dd 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -2076,8 +2076,7 @@
      * @see #getAnimationAdapter
      */
     boolean applyAnimation(WindowManager.LayoutParams lp, int transit, boolean enter,
-            boolean isVoiceInteraction,
-            @Nullable OnAnimationFinishedCallback animationFinishedCallback) {
+            boolean isVoiceInteraction, @Nullable OnAnimationFinishedCallback finishedCallback) {
         if (mWmService.mDisableTransitionAnimation) {
             ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
                     "applyAnimation: transition animation is disabled or skipped. "
@@ -2092,22 +2091,7 @@
         try {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WC#applyAnimation");
             if (okToAnimate()) {
-                final Pair<AnimationAdapter, AnimationAdapter> adapters = getAnimationAdapter(lp,
-                        transit, enter, isVoiceInteraction);
-                AnimationAdapter adapter = adapters.first;
-                AnimationAdapter thumbnailAdapter = adapters.second;
-                if (adapter != null) {
-                    startAnimation(getPendingTransaction(), adapter, !isVisible(),
-                            ANIMATION_TYPE_APP_TRANSITION, animationFinishedCallback);
-                    if (adapter.getShowWallpaper()) {
-                        getDisplayContent().pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
-                    }
-                    if (thumbnailAdapter != null) {
-                        mSurfaceFreezer.mSnapshot.startAnimation(getPendingTransaction(),
-                                thumbnailAdapter, ANIMATION_TYPE_APP_TRANSITION,
-                                (type, anim) -> { });
-                    }
-                }
+                applyAnimationUnchecked(lp, enter, transit, isVoiceInteraction, finishedCallback);
             } else {
                 cancelAnimation();
             }
@@ -2201,6 +2185,26 @@
         return resultAdapters;
     }
 
+    protected void applyAnimationUnchecked(WindowManager.LayoutParams lp, boolean enter,
+            int transit, boolean isVoiceInteraction,
+            @Nullable OnAnimationFinishedCallback finishedCallback) {
+        final Pair<AnimationAdapter, AnimationAdapter> adapters = getAnimationAdapter(lp,
+                transit, enter, isVoiceInteraction);
+        AnimationAdapter adapter = adapters.first;
+        AnimationAdapter thumbnailAdapter = adapters.second;
+        if (adapter != null) {
+            startAnimation(getPendingTransaction(), adapter, !isVisible(),
+                    ANIMATION_TYPE_APP_TRANSITION, finishedCallback);
+            if (adapter.getShowWallpaper()) {
+                getDisplayContent().pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
+            }
+            if (thumbnailAdapter != null) {
+                mSurfaceFreezer.mSnapshot.startAnimation(getPendingTransaction(),
+                        thumbnailAdapter, ANIMATION_TYPE_APP_TRANSITION, (type, anim) -> { });
+            }
+        }
+    }
+
     final SurfaceAnimationRunner getSurfaceAnimationRunner() {
         return mWmService.mSurfaceAnimationRunner;
     }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index c3fb68f..9d87976 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -25,6 +25,7 @@
 import static android.Manifest.permission.RESTRICTED_VR_ACCESS;
 import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
 import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
+import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
 import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
 import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
 import static android.app.StatusBarManager.DISABLE_MASK;
@@ -1359,7 +1360,8 @@
             LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame,
             Rect outContentInsets, Rect outStableInsets,
             DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
-            InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) {
+            InsetsState outInsetsState, InsetsSourceControl[] outActiveControls,
+            int requestUserId) {
         int[] appOp = new int[1];
         final boolean isRoundedCornerOverlay = (attrs.privateFlags
                 & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0;
@@ -1428,6 +1430,20 @@
                 return WindowManagerGlobal.ADD_INVALID_DISPLAY;
             }
 
+            int userId = UserHandle.getUserId(session.mUid);
+            if (requestUserId != userId) {
+                try {
+                    mAmInternal.handleIncomingUser(callingPid, callingUid, requestUserId,
+                            false /*allowAll*/, ALLOW_NON_FULL, null, null);
+                } catch (Exception exp) {
+                    ProtoLog.w(WM_ERROR, "Trying to add window with invalid user=%d",
+                            requestUserId);
+                    return WindowManagerGlobal.ADD_INVALID_USER;
+                }
+                // It's fine to use this userId
+                userId = requestUserId;
+            }
+
             ActivityRecord activity = null;
             final boolean hasParent = parentWindow != null;
             // Use existing parent window token for child windows since they go in the same token
@@ -1516,7 +1532,7 @@
             }
 
             final WindowState win = new WindowState(this, session, client, token, parentWindow,
-                    appOp[0], seq, attrs, viewVisibility, session.mUid,
+                    appOp[0], seq, attrs, viewVisibility, session.mUid, userId,
                     session.mCanAddInternalSystemWindow);
             if (win.mDeathRecipient == null) {
                 // Client has apparently died, so there is no reason to
@@ -1836,8 +1852,7 @@
         if ((w.mAttrs.flags&WindowManager.LayoutParams.FLAG_SECURE) != 0) {
             return true;
         }
-        if (DevicePolicyCache.getInstance().getScreenCaptureDisabled(
-                UserHandle.getUserId(w.mOwnerUid))) {
+        if (DevicePolicyCache.getInstance().getScreenCaptureDisabled(w.mShowUserId)) {
             return true;
         }
         return false;
@@ -7407,7 +7422,7 @@
             synchronized (mGlobalLock) {
                 WindowState window = mWindowMap.get(token);
                 if (window != null) {
-                    return UserHandle.getUserId(window.mOwnerUid);
+                    return window.mShowUserId;
                 }
                 return UserHandle.USER_NULL;
             }
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 3e2e9be..86bc013 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -156,7 +156,7 @@
                         final PooledConsumer f = PooledLambda.obtainConsumer(
                                 ActivityRecord::ensureActivityConfiguration,
                                 PooledLambda.__(ActivityRecord.class), 0,
-                                false /* preserveWindow */);
+                                true /* preserveWindow */);
                         try {
                             for (int i = haveConfigChanges.size() - 1; i >= 0; --i) {
                                 haveConfigChanges.valueAt(i).forAllActivities(f);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 627fdc3..c11c29b5 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -191,7 +191,6 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.Trace;
-import android.os.UserHandle;
 import android.os.WorkSource;
 import android.provider.Settings;
 import android.text.TextUtils;
@@ -269,6 +268,12 @@
     final int mAppOp;
     // UserId and appId of the owner. Don't display windows of non-current user.
     final int mOwnerUid;
+    /**
+     * Requested userId, if this is not equals with the userId from mOwnerUid, then this window is
+     * created for secondary user.
+     * Use this member instead of get userId from mOwnerUid while query for visibility.
+     */
+    final int mShowUserId;
     /** The owner has {@link android.Manifest.permission#INTERNAL_SYSTEM_WINDOW} */
     final boolean mOwnerCanAddInternalSystemWindow;
     final WindowId mWindowId;
@@ -806,8 +811,9 @@
 
     WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
             WindowState parentWindow, int appOp, int seq, WindowManager.LayoutParams a,
-            int viewVisibility, int ownerId, boolean ownerCanAddInternalSystemWindow) {
-        this(service, s, c, token, parentWindow, appOp, seq, a, viewVisibility, ownerId,
+            int viewVisibility, int ownerId, int showUserId,
+            boolean ownerCanAddInternalSystemWindow) {
+        this(service, s, c, token, parentWindow, appOp, seq, a, viewVisibility, ownerId, showUserId,
                 ownerCanAddInternalSystemWindow, new PowerManagerWrapper() {
                     @Override
                     public void wakeUp(long time, @WakeReason int reason, String details) {
@@ -823,8 +829,8 @@
 
     WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
             WindowState parentWindow, int appOp, int seq, WindowManager.LayoutParams a,
-            int viewVisibility, int ownerId, boolean ownerCanAddInternalSystemWindow,
-            PowerManagerWrapper powerManagerWrapper) {
+            int viewVisibility, int ownerId, int showUserId,
+            boolean ownerCanAddInternalSystemWindow, PowerManagerWrapper powerManagerWrapper) {
         super(service);
         mSession = s;
         mClient = c;
@@ -832,6 +838,7 @@
         mToken = token;
         mActivityRecord = mToken.asActivityRecord();
         mOwnerUid = ownerId;
+        mShowUserId = showUserId;
         mOwnerCanAddInternalSystemWindow = ownerCanAddInternalSystemWindow;
         mWindowId = new WindowId(this);
         mAttrs.copyFrom(a);
@@ -3275,7 +3282,7 @@
         }
 
         return win.showForAllUsers()
-                || mWmService.isCurrentProfile(UserHandle.getUserId(win.mOwnerUid));
+                || mWmService.isCurrentProfile(win.mShowUserId);
     }
 
     private static void applyInsets(Region outRegion, Rect frame, Rect inset) {
@@ -3795,7 +3802,7 @@
     public void writeIdentifierToProto(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
         proto.write(HASH_CODE, System.identityHashCode(this));
-        proto.write(USER_ID, UserHandle.getUserId(mOwnerUid));
+        proto.write(USER_ID, mShowUserId);
         final CharSequence title = getWindowTag();
         if (title != null) {
             proto.write(TITLE, title.toString());
@@ -3979,7 +3986,7 @@
             mLastTitle = title;
             mWasExiting = mAnimatingExit;
             mStringNameCache = "Window{" + Integer.toHexString(System.identityHashCode(this))
-                    + " u" + UserHandle.getUserId(mOwnerUid)
+                    + " u" + mShowUserId
                     + " " + mLastTitle + (mAnimatingExit ? " EXITING}" : "}");
         }
         return mStringNameCache;
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 5976b48..e34b816 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -554,13 +554,12 @@
         // cleared and the configuration is restored from parent.
         if (!changed) {
             clearFixedRotationTransform(null /* applyDisplayRotation */);
-            onConfigurationChanged(getParent().getConfiguration());
         }
     }
 
     /**
-     * Clears the transform and apply display rotation if the action is given. The caller needs to
-     * refresh the configuration of this container after this method call.
+     * Clears the transform and apply display rotation if the action is given. If the display will
+     * not rotate, the transformed containers are restored to their original states.
      */
     void clearFixedRotationTransform(Runnable applyDisplayRotation) {
         final FixedRotationTransformState state = mFixedRotationTransformState;
@@ -574,6 +573,12 @@
         state.mIsTransforming = false;
         if (applyDisplayRotation != null) {
             applyDisplayRotation.run();
+        } else {
+            // The display will not rotate to the rotation of this container, let's cancel them.
+            for (int i = state.mAssociatedTokens.size() - 1; i >= 0; i--) {
+                state.mAssociatedTokens.get(i).cancelFixedRotationTransform();
+            }
+            cancelFixedRotationTransform();
         }
         // The state is cleared at the end, because it is used to indicate that other windows can
         // use seamless rotation when applying rotation to display.
@@ -583,6 +588,16 @@
         mFixedRotationTransformState = null;
     }
 
+    /** Restores the changes that applies to this container. */
+    private void cancelFixedRotationTransform() {
+        final WindowContainer<?> parent = getParent();
+        if (parent == null) {
+            // The window may be detached or detaching.
+            return;
+        }
+        onConfigurationChanged(parent.getConfiguration());
+    }
+
     @Override
     void resolveOverrideConfiguration(Configuration newParentConfig) {
         super.resolveOverrideConfiguration(newParentConfig);
diff --git a/services/incremental/BinderIncrementalService.cpp b/services/incremental/BinderIncrementalService.cpp
index 2dbbc5a..aabc58c 100644
--- a/services/incremental/BinderIncrementalService.cpp
+++ b/services/incremental/BinderIncrementalService.cpp
@@ -17,6 +17,7 @@
 #include "BinderIncrementalService.h"
 
 #include <android-base/logging.h>
+#include <android-base/no_destructor.h>
 #include <binder/IResultReceiver.h>
 #include <binder/PermissionCache.h>
 #include <incfs.h>
@@ -93,8 +94,8 @@
 }
 
 status_t BinderIncrementalService::dump(int fd, const Vector<String16>&) {
-    static const String16 kDump("android.permission.DUMP");
-    if (!PermissionCache::checkCallingPermission(kDump)) {
+    static const android::base::NoDestructor<String16> kDump("android.permission.DUMP");
+    if (!PermissionCache::checkCallingPermission(*kDump)) {
         return PERMISSION_DENIED;
     }
     mImpl.onDump(fd);
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index 92366e5..eb65a2d 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -20,6 +20,7 @@
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
+#include <android-base/no_destructor.h>
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
@@ -163,7 +164,9 @@
         android::base::GetBoolProperty("incremental.perflogging", false);
 
 IncrementalService::IncFsMount::~IncFsMount() {
-    incrementalService.mDataLoaderManager->destroyDataLoader(mountId);
+    if (dataLoaderStub) {
+        dataLoaderStub->destroy();
+    }
     LOG(INFO) << "Unmounting and cleaning up mount " << mountId << " with root '" << root << '\'';
     for (auto&& [target, _] : bindPoints) {
         LOG(INFO) << "\tbind: " << target;
@@ -288,9 +291,12 @@
         dprintf(fd, "\t\tmountId: %d\n", mnt.mountId);
         dprintf(fd, "\t\troot: %s\n", mnt.root.c_str());
         dprintf(fd, "\t\tnextStorageDirNo: %d\n", mnt.nextStorageDirNo.load());
-        dprintf(fd, "\t\tdataLoaderStatus: %d\n", mnt.dataLoaderStatus.load());
-        {
-            const auto& params = mnt.dataLoaderParams;
+        if (mnt.dataLoaderStub) {
+            const auto& dataLoaderStub = *mnt.dataLoaderStub;
+            dprintf(fd, "\t\tdataLoaderStatus: %d\n", dataLoaderStub.status());
+            dprintf(fd, "\t\tdataLoaderStartRequested: %s\n",
+                    dataLoaderStub.startRequested() ? "true" : "false");
+            const auto& params = dataLoaderStub.params();
             dprintf(fd, "\t\tdataLoaderParams:\n");
             dprintf(fd, "\t\t\ttype: %s\n", toString(params.type).c_str());
             dprintf(fd, "\t\t\tpackageName: %s\n", params.packageName.c_str());
@@ -321,10 +327,9 @@
     }
 }
 
-std::optional<std::future<void>> IncrementalService::onSystemReady() {
-    std::promise<void> threadFinished;
+void IncrementalService::onSystemReady() {
     if (mSystemReady.exchange(true)) {
-        return {};
+        return;
     }
 
     std::vector<IfsMountPtr> mounts;
@@ -338,8 +343,8 @@
         }
     }
 
+    /* TODO(b/151241369): restore data loaders on reboot.
     std::thread([this, mounts = std::move(mounts)]() {
-        /* TODO(b/151241369): restore data loaders on reboot.
         for (auto&& ifs : mounts) {
             if (prepareDataLoader(*ifs)) {
                 LOG(INFO) << "Successfully started data loader for mount " << ifs->mountId;
@@ -348,10 +353,8 @@
                 LOG(WARNING) << "Failed to start data loader for mount " << ifs->mountId;
             }
         }
-        */
-        mPrepareDataLoaders.set_value_at_thread_exit();
     }).detach();
-    return mPrepareDataLoaders.get_future();
+    */
 }
 
 auto IncrementalService::getStorageSlotLocked() -> MountMap::iterator {
@@ -468,15 +471,13 @@
         return kInvalidStorageId;
     }
 
-    ifs->dataLoaderParams = std::move(dataLoaderParams);
-
     {
         metadata::Mount m;
         m.mutable_storage()->set_id(ifs->mountId);
-        m.mutable_loader()->set_type((int)ifs->dataLoaderParams.type);
-        m.mutable_loader()->set_package_name(ifs->dataLoaderParams.packageName);
-        m.mutable_loader()->set_class_name(ifs->dataLoaderParams.className);
-        m.mutable_loader()->set_arguments(ifs->dataLoaderParams.arguments);
+        m.mutable_loader()->set_type((int)dataLoaderParams.type);
+        m.mutable_loader()->set_package_name(dataLoaderParams.packageName);
+        m.mutable_loader()->set_class_name(dataLoaderParams.className);
+        m.mutable_loader()->set_arguments(dataLoaderParams.arguments);
         const auto metadata = m.SerializeAsString();
         m.mutable_loader()->release_arguments();
         m.mutable_loader()->release_class_name();
@@ -504,14 +505,20 @@
     // Done here as well, all data structures are in good state.
     secondCleanupOnFailure.release();
 
-    if (!prepareDataLoader(*ifs, &dataLoaderStatusListener)) {
-        LOG(ERROR) << "prepareDataLoader() failed";
-        deleteStorageLocked(*ifs, std::move(l));
-        return kInvalidStorageId;
-    }
+    auto dataLoaderStub =
+            prepareDataLoader(*ifs, std::move(dataLoaderParams), &dataLoaderStatusListener);
+    CHECK(dataLoaderStub);
 
     mountIt->second = std::move(ifs);
     l.unlock();
+
+    if (mSystemReady.load(std::memory_order_relaxed) && !dataLoaderStub->create()) {
+        // failed to create data loader
+        LOG(ERROR) << "initializeDataLoader() failed";
+        deleteStorage(dataLoaderStub->id());
+        return kInvalidStorageId;
+    }
+
     LOG(INFO) << "created storage " << mountId;
     return mountId;
 }
@@ -585,10 +592,10 @@
         return -EINVAL;
     }
 
+    const auto& params = ifs->dataLoaderStub->params();
     if (enableReadLogs) {
-        if (auto status =
-                    mAppOpsManager->checkPermission(kDataUsageStats, kOpUsage,
-                                                    ifs->dataLoaderParams.packageName.c_str());
+        if (auto status = mAppOpsManager->checkPermission(kDataUsageStats, kOpUsage,
+                                                          params.packageName.c_str());
             !status.isOk()) {
             LOG(ERROR) << "checkPermission failed: " << status.toString8();
             return fromBinderStatus(status);
@@ -601,7 +608,7 @@
     }
 
     if (enableReadLogs) {
-        registerAppOpsCallback(ifs->dataLoaderParams.packageName);
+        registerAppOpsCallback(params.packageName);
     }
 
     return 0;
@@ -701,8 +708,8 @@
 const IncrementalService::IfsMountPtr& IncrementalService::getIfsLocked(StorageId storage) const {
     auto it = mMounts.find(storage);
     if (it == mMounts.end()) {
-        static const IfsMountPtr kEmpty = {};
-        return kEmpty;
+        static const android::base::NoDestructor<IfsMountPtr> kEmpty{};
+        return *kEmpty;
     }
     return it->second;
 }
@@ -984,34 +991,19 @@
 }
 
 bool IncrementalService::startLoading(StorageId storage) const {
+    DataLoaderStubPtr dataLoaderStub;
     {
         std::unique_lock l(mLock);
         const auto& ifs = getIfsLocked(storage);
         if (!ifs) {
             return false;
         }
-        if (ifs->dataLoaderStatus != IDataLoaderStatusListener::DATA_LOADER_CREATED) {
-            ifs->dataLoaderStartRequested = true;
-            return true;
+        dataLoaderStub = ifs->dataLoaderStub;
+        if (!dataLoaderStub) {
+            return false;
         }
     }
-    return startDataLoader(storage);
-}
-
-bool IncrementalService::startDataLoader(MountId mountId) const {
-    sp<IDataLoader> dataloader;
-    auto status = mDataLoaderManager->getDataLoader(mountId, &dataloader);
-    if (!status.isOk()) {
-        return false;
-    }
-    if (!dataloader) {
-        return false;
-    }
-    status = dataloader->start(mountId);
-    if (!status.isOk()) {
-        return false;
-    }
-    return true;
+    return dataLoaderStub->start();
 }
 
 void IncrementalService::mountExistingImages() {
@@ -1057,13 +1049,13 @@
     mNextId = std::max(mNextId, ifs->mountId + 1);
 
     // DataLoader params
+    DataLoaderParamsParcel dataLoaderParams;
     {
-        auto& dlp = ifs->dataLoaderParams;
         const auto& loader = mount.loader();
-        dlp.type = (android::content::pm::DataLoaderType)loader.type();
-        dlp.packageName = loader.package_name();
-        dlp.className = loader.class_name();
-        dlp.arguments = loader.arguments();
+        dataLoaderParams.type = (android::content::pm::DataLoaderType)loader.type();
+        dataLoaderParams.packageName = loader.package_name();
+        dataLoaderParams.className = loader.class_name();
+        dataLoaderParams.arguments = loader.arguments();
     }
 
     std::vector<std::pair<std::string, metadata::BindPoint>> bindPoints;
@@ -1135,17 +1127,13 @@
     return true;
 }
 
-bool IncrementalService::prepareDataLoader(IncrementalService::IncFsMount& ifs,
-                                           const DataLoaderStatusListener* externalListener) {
-    if (!mSystemReady.load(std::memory_order_relaxed)) {
-        std::unique_lock l(ifs.lock);
-        return true; // eventually...
-    }
-
+IncrementalService::DataLoaderStubPtr IncrementalService::prepareDataLoader(
+        IncrementalService::IncFsMount& ifs, DataLoaderParamsParcel&& params,
+        const DataLoaderStatusListener* externalListener) {
     std::unique_lock l(ifs.lock);
-    if (ifs.dataLoaderStatus != -1) {
+    if (ifs.dataLoaderStub) {
         LOG(INFO) << "Skipped data loader preparation because it already exists";
-        return true;
+        return ifs.dataLoaderStub;
     }
 
     FileSystemControlParcel fsControlParcel;
@@ -1155,17 +1143,10 @@
             base::unique_fd(::dup(ifs.control.pendingReads())));
     fsControlParcel.incremental->log.reset(base::unique_fd(::dup(ifs.control.logs())));
     fsControlParcel.service = new IncrementalServiceConnector(*this, ifs.mountId);
-    sp<IncrementalDataLoaderListener> listener =
-            new IncrementalDataLoaderListener(*this,
-                                              externalListener ? *externalListener
-                                                               : DataLoaderStatusListener());
-    bool created = false;
-    auto status = mDataLoaderManager->initializeDataLoader(ifs.mountId, ifs.dataLoaderParams, fsControlParcel, listener, &created);
-    if (!status.isOk() || !created) {
-        LOG(ERROR) << "Failed to create a data loader for mount " << ifs.mountId;
-        return false;
-    }
-    return true;
+
+    ifs.dataLoaderStub = new DataLoaderStub(*this, ifs.mountId, std::move(params),
+                                            std::move(fsControlParcel), externalListener);
+    return ifs.dataLoaderStub;
 }
 
 template <class Duration>
@@ -1376,7 +1357,7 @@
         std::lock_guard l(mLock);
         affected.reserve(mMounts.size());
         for (auto&& [id, ifs] : mMounts) {
-            if (ifs->mountId == id && ifs->dataLoaderParams.packageName == packageName) {
+            if (ifs->mountId == id && ifs->dataLoaderStub->params().packageName == packageName) {
                 affected.push_back(ifs);
             }
         }
@@ -1386,37 +1367,79 @@
     }
 }
 
-binder::Status IncrementalService::IncrementalDataLoaderListener::onStatusChanged(MountId mountId,
-                                                                                  int newStatus) {
-    if (externalListener) {
-        // Give an external listener a chance to act before we destroy something.
-        externalListener->onStatusChanged(mountId, newStatus);
+IncrementalService::DataLoaderStub::~DataLoaderStub() {
+    CHECK(mStatus == -1 || mStatus == IDataLoaderStatusListener::DATA_LOADER_DESTROYED)
+            << "Dataloader has to be destroyed prior to destructor: " << mId
+            << ", status: " << mStatus;
+}
+
+bool IncrementalService::DataLoaderStub::create() {
+    bool created = false;
+    auto status = mService.mDataLoaderManager->initializeDataLoader(mId, mParams, mControl, this,
+                                                                    &created);
+    if (!status.isOk() || !created) {
+        LOG(ERROR) << "Failed to create a data loader for mount " << mId;
+        return false;
+    }
+    return true;
+}
+
+bool IncrementalService::DataLoaderStub::start() {
+    if (mStatus != IDataLoaderStatusListener::DATA_LOADER_CREATED) {
+        mStartRequested = true;
+        return true;
     }
 
-    bool startRequested = false;
+    sp<IDataLoader> dataloader;
+    auto status = mService.mDataLoaderManager->getDataLoader(mId, &dataloader);
+    if (!status.isOk()) {
+        return false;
+    }
+    if (!dataloader) {
+        return false;
+    }
+    status = dataloader->start(mId);
+    if (!status.isOk()) {
+        return false;
+    }
+    return true;
+}
+
+void IncrementalService::DataLoaderStub::destroy() {
+    mDestroyRequested = true;
+    mService.mDataLoaderManager->destroyDataLoader(mId);
+}
+
+binder::Status IncrementalService::DataLoaderStub::onStatusChanged(MountId mountId, int newStatus) {
+    if (mStatus == newStatus) {
+        return binder::Status::ok();
+    }
+
+    if (mListener) {
+        // Give an external listener a chance to act before we destroy something.
+        mListener->onStatusChanged(mountId, newStatus);
+    }
+
     {
-        std::unique_lock l(incrementalService.mLock);
-        const auto& ifs = incrementalService.getIfsLocked(mountId);
+        std::unique_lock l(mService.mLock);
+        const auto& ifs = mService.getIfsLocked(mountId);
         if (!ifs) {
             LOG(WARNING) << "Received data loader status " << int(newStatus)
                          << " for unknown mount " << mountId;
             return binder::Status::ok();
         }
-        ifs->dataLoaderStatus = newStatus;
+        mStatus = newStatus;
 
-        if (newStatus == IDataLoaderStatusListener::DATA_LOADER_DESTROYED) {
-            ifs->dataLoaderStatus = IDataLoaderStatusListener::DATA_LOADER_STOPPED;
-            incrementalService.deleteStorageLocked(*ifs, std::move(l));
+        if (!mDestroyRequested && newStatus == IDataLoaderStatusListener::DATA_LOADER_DESTROYED) {
+            mService.deleteStorageLocked(*ifs, std::move(l));
             return binder::Status::ok();
         }
-
-        startRequested = ifs->dataLoaderStartRequested;
     }
 
     switch (newStatus) {
         case IDataLoaderStatusListener::DATA_LOADER_CREATED: {
-            if (startRequested) {
-                incrementalService.startDataLoader(mountId);
+            if (mStartRequested) {
+                start();
             }
             break;
         }
diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h
index db14a79..27d40f1 100644
--- a/services/incremental/IncrementalService.h
+++ b/services/incremental/IncrementalService.h
@@ -60,7 +60,8 @@
 using TimePoint = std::chrono::time_point<Clock>;
 using Seconds = std::chrono::seconds;
 
-using DataLoaderStatusListener = ::android::sp<::android::content::pm::IDataLoaderStatusListener>;
+using IDataLoaderStatusListener = ::android::content::pm::IDataLoaderStatusListener;
+using DataLoaderStatusListener = ::android::sp<IDataLoaderStatusListener>;
 
 class IncrementalService final {
 public:
@@ -95,7 +96,7 @@
 
     void onDump(int fd);
 
-    std::optional<std::future<void>> onSystemReady();
+    void onSystemReady();
 
     StorageId createStorage(std::string_view mountPoint, DataLoaderParamsParcel&& dataLoaderParams,
                             const DataLoaderStatusListener& dataLoaderStatusListener,
@@ -134,19 +135,6 @@
     bool configureNativeBinaries(StorageId storage, std::string_view apkFullPath,
                                  std::string_view libDirRelativePath, std::string_view abi);
 
-    class IncrementalDataLoaderListener : public android::content::pm::BnDataLoaderStatusListener {
-    public:
-        IncrementalDataLoaderListener(IncrementalService& incrementalService,
-                                      DataLoaderStatusListener externalListener)
-              : incrementalService(incrementalService), externalListener(externalListener) {}
-        // Callbacks interface
-        binder::Status onStatusChanged(MountId mount, int newStatus) final;
-
-    private:
-        IncrementalService& incrementalService;
-        DataLoaderStatusListener externalListener;
-    };
-
     class AppOpsListener : public android::BnAppOpsCallback {
     public:
         AppOpsListener(IncrementalService& incrementalService, std::string packageName) : incrementalService(incrementalService), packageName(std::move(packageName)) {}
@@ -171,6 +159,45 @@
 private:
     static const bool sEnablePerfLogging;
 
+    struct IncFsMount;
+
+    class DataLoaderStub : public android::content::pm::BnDataLoaderStatusListener {
+    public:
+        DataLoaderStub(IncrementalService& service, MountId id, DataLoaderParamsParcel&& params,
+                       FileSystemControlParcel&& control,
+                       const DataLoaderStatusListener* externalListener)
+              : mService(service),
+                mId(id),
+                mParams(std::move(params)),
+                mControl(std::move(control)),
+                mListener(externalListener ? *externalListener : DataLoaderStatusListener()) {}
+        ~DataLoaderStub();
+
+        bool create();
+        bool start();
+        void destroy();
+
+        // accessors
+        MountId id() const { return mId; }
+        const DataLoaderParamsParcel& params() const { return mParams; }
+        int status() const { return mStatus.load(); }
+        bool startRequested() const { return mStartRequested; }
+
+    private:
+        binder::Status onStatusChanged(MountId mount, int newStatus) final;
+
+        IncrementalService& mService;
+        MountId const mId;
+        DataLoaderParamsParcel const mParams;
+        FileSystemControlParcel const mControl;
+        DataLoaderStatusListener const mListener;
+
+        std::atomic<int> mStatus = -1;
+        bool mStartRequested = false;
+        bool mDestroyRequested = false;
+    };
+    using DataLoaderStubPtr = sp<DataLoaderStub>;
+
     struct IncFsMount {
         struct Bind {
             StorageId storage;
@@ -194,10 +221,8 @@
         /*const*/ MountId mountId;
         StorageMap storages;
         BindMap bindPoints;
-        DataLoaderParamsParcel dataLoaderParams;
+        DataLoaderStubPtr dataLoaderStub;
         std::atomic<int> nextStorageDirNo{0};
-        std::atomic<int> dataLoaderStatus = -1;
-        bool dataLoaderStartRequested = false;
         const IncrementalService& incrementalService;
 
         IncFsMount(std::string root, MountId mountId, Control control,
@@ -232,8 +257,8 @@
                            std::string&& source, std::string&& target, BindKind kind,
                            std::unique_lock<std::mutex>& mainLock);
 
-    bool prepareDataLoader(IncFsMount& ifs, const DataLoaderStatusListener* externalListener = nullptr);
-    bool startDataLoader(MountId mountId) const;
+    DataLoaderStubPtr prepareDataLoader(IncFsMount& ifs, DataLoaderParamsParcel&& params,
+                                        const DataLoaderStatusListener* externalListener = nullptr);
 
     BindPathMap::const_iterator findStorageLocked(std::string_view path) const;
     StorageId findStorageId(std::string_view path) const;
@@ -269,7 +294,6 @@
 
     std::atomic_bool mSystemReady = false;
     StorageId mNextId = 0;
-    std::promise<void> mPrepareDataLoaders;
 };
 
 } // namespace android::incremental
diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp
index 18ae4b5..9911319 100644
--- a/services/incremental/test/IncrementalServiceTest.cpp
+++ b/services/incremental/test/IncrementalServiceTest.cpp
@@ -131,6 +131,23 @@
                        binder::Status(int32_t mountId, sp<IDataLoader>* _aidl_return));
     MOCK_CONST_METHOD1(destroyDataLoader, binder::Status(int32_t mountId));
 
+    void initializeDataLoaderSuccess() {
+        ON_CALL(*this, initializeDataLoader(_, _, _, _, _))
+                .WillByDefault(Invoke(this, &MockDataLoaderManager::initializeDataLoaderOk));
+    }
+    void initializeDataLoaderFails() {
+        ON_CALL(*this, initializeDataLoader(_, _, _, _, _))
+                .WillByDefault(Return(
+                        (binder::Status::fromExceptionCode(1, String8("failed to prepare")))));
+    }
+    void getDataLoaderSuccess() {
+        ON_CALL(*this, getDataLoader(_, _))
+                .WillByDefault(Invoke(this, &MockDataLoaderManager::getDataLoaderOk));
+    }
+    void destroyDataLoaderOk() {
+        ON_CALL(*this, destroyDataLoader(_))
+                .WillByDefault(Invoke(this, &MockDataLoaderManager::setDataLoaderStatusDestroyed));
+    }
     binder::Status initializeDataLoaderOk(int32_t mountId, const DataLoaderParamsParcel& params,
                                           const FileSystemControlParcel& control,
                                           const sp<IDataLoaderStatusListener>& listener,
@@ -141,32 +158,22 @@
         *_aidl_return = true;
         return binder::Status::ok();
     }
-
     binder::Status getDataLoaderOk(int32_t mountId, sp<IDataLoader>* _aidl_return) {
         *_aidl_return = mDataLoader;
         return binder::Status::ok();
     }
-
-    void initializeDataLoaderFails() {
-        ON_CALL(*this, initializeDataLoader(_, _, _, _, _))
-                .WillByDefault(Return(
-                        (binder::Status::fromExceptionCode(1, String8("failed to prepare")))));
-    }
-    void initializeDataLoaderSuccess() {
-        ON_CALL(*this, initializeDataLoader(_, _, _, _, _))
-                .WillByDefault(Invoke(this, &MockDataLoaderManager::initializeDataLoaderOk));
-    }
-    void getDataLoaderSuccess() {
-        ON_CALL(*this, getDataLoader(_, _))
-                .WillByDefault(Invoke(this, &MockDataLoaderManager::getDataLoaderOk));
-    }
     void setDataLoaderStatusNotReady() {
         mListener->onStatusChanged(mId, IDataLoaderStatusListener::DATA_LOADER_DESTROYED);
     }
     void setDataLoaderStatusReady() {
         mListener->onStatusChanged(mId, IDataLoaderStatusListener::DATA_LOADER_CREATED);
     }
-
+    binder::Status setDataLoaderStatusDestroyed(int32_t id) {
+        if (mListener) {
+            mListener->onStatusChanged(id, IDataLoaderStatusListener::DATA_LOADER_DESTROYED);
+        }
+        return binder::Status::ok();
+    }
     int32_t setStorageParams(bool enableReadLogs) {
         int32_t result = -1;
         EXPECT_NE(mServiceConnector.get(), nullptr);
@@ -299,6 +306,7 @@
                                                      mRootDir.path);
         mDataLoaderParcel.packageName = "com.test";
         mDataLoaderParcel.arguments = "uri";
+        mDataLoaderManager->destroyDataLoaderOk();
         mIncrementalService->onSystemReady();
     }
 
@@ -346,6 +354,7 @@
 TEST_F(IncrementalServiceTest, testCreateStorageMountIncFsInvalidControlParcel) {
     mVold->mountIncFsInvalidControlParcel();
     EXPECT_CALL(*mDataLoaderManager, initializeDataLoader(_, _, _, _, _)).Times(0);
+    EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_)).Times(0);
     TemporaryDir tempDir;
     int storageId =
             mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {},
@@ -357,7 +366,7 @@
     mVold->mountIncFsSuccess();
     mIncFs->makeFileFails();
     EXPECT_CALL(*mDataLoaderManager, initializeDataLoader(_, _, _, _, _)).Times(0);
-    EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_));
+    EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_)).Times(0);
     EXPECT_CALL(*mVold, unmountIncFs(_));
     TemporaryDir tempDir;
     int storageId =
@@ -371,7 +380,7 @@
     mIncFs->makeFileSuccess();
     mVold->bindMountFails();
     EXPECT_CALL(*mDataLoaderManager, initializeDataLoader(_, _, _, _, _)).Times(0);
-    EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_));
+    EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_)).Times(0);
     EXPECT_CALL(*mVold, unmountIncFs(_));
     TemporaryDir tempDir;
     int storageId =
@@ -385,7 +394,7 @@
     mIncFs->makeFileSuccess();
     mVold->bindMountSuccess();
     mDataLoaderManager->initializeDataLoaderFails();
-    EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_));
+    EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_)).Times(1);
     EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
     TemporaryDir tempDir;
     int storageId =
@@ -399,7 +408,7 @@
     mIncFs->makeFileSuccess();
     mVold->bindMountSuccess();
     mDataLoaderManager->initializeDataLoaderSuccess();
-    EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_));
+    EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_)).Times(1);
     EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
     TemporaryDir tempDir;
     int storageId =
diff --git a/services/net/Android.bp b/services/net/Android.bp
index c54102f..3eba6c4 100644
--- a/services/net/Android.bp
+++ b/services/net/Android.bp
@@ -13,7 +13,7 @@
     ],
     static_libs: [
         "dnsresolver_aidl_interface-V2-java",
-        "netd_aidl_interface-unstable-java",
+        "netd_aidl_interface-V3-java",
         "netlink-client",
         "networkstack-client",
         "net-utils-services-common",
@@ -44,7 +44,7 @@
     ],
     static_libs: [
         "dnsresolver_aidl_interface-V2-java",
-        "netd_aidl_interface-unstable-java",
+        "netd_aidl_interface-V3-java",
         "netlink-client",
         "networkstack-client",
         "net-utils-services-common",
diff --git a/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java
index f4d7b8b..d338b58 100644
--- a/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java
@@ -382,6 +382,7 @@
         doReturn(hasLeases).when(blobMetadata).hasLeases();
         doReturn(blobHandle).when(blobMetadata).getBlobHandle();
         doCallRealMethod().when(blobMetadata).shouldBeDeleted(anyBoolean());
+        doReturn(true).when(blobMetadata).hasLeaseWaitTimeElapsedForAll();
         return blobMetadata;
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/lights/LightsServiceTest.java b/services/tests/servicestests/src/com/android/server/lights/LightsServiceTest.java
index ccbaee4..aa923e2 100644
--- a/services/tests/servicestests/src/com/android/server/lights/LightsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/lights/LightsServiceTest.java
@@ -63,6 +63,16 @@
                 fakeHwLight(105, LightsManager.LIGHT_TYPE_MICROPHONE, 2)
             };
         }
+
+        @Override
+        public int getInterfaceVersion() {
+            return this.VERSION;
+        }
+
+        @Override
+        public String getInterfaceHash() {
+            return this.HASH;
+        }
     };
 
     private static HwLight fakeHwLight(int id, int type, int ordinal) {
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
index 820e61c..9eda718 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
@@ -377,8 +377,7 @@
                 return false;
             }
             final String key = createKey(overlayPackage.packageName, userId);
-            mIdmapFiles.add(key);
-            return true;
+            return mIdmapFiles.add(key);
         }
 
         @Override
diff --git a/services/tests/servicestests/src/com/android/server/stats/pull/SettingsStatsUtilTest.java b/services/tests/servicestests/src/com/android/server/stats/pull/SettingsStatsUtilTest.java
new file mode 100644
index 0000000..cfeadc6
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/stats/pull/SettingsStatsUtilTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.stats.pull;
+
+import static android.os.UserHandle.USER_SYSTEM;
+
+import static com.android.internal.util.FrameworkStatsLog.SETTING_SNAPSHOT;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import android.content.Context;
+import android.provider.DeviceConfig;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:SettingsStatsUtilTest
+ */
+@RunWith(AndroidJUnit4.class)
+public class SettingsStatsUtilTest {
+    private static final String[] KEYS = new String[]{
+            "screen_auto_brightness_adj",
+            "font_scale"
+    };
+    private static final String ENCODED = "ChpzY3JlZW5fYXV0b19icmlnaHRuZXNzX2FkagoKZm9udF9zY2FsZQ";
+    private static final String FLAG = "testflag";
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS,
+                FLAG,
+                "",
+                false /* makeDefault*/);
+        mContext = InstrumentationRegistry.getInstrumentation().getContext();
+    }
+
+    @Test
+    public void getList_emptyString_nullValue() {
+        assertNull(SettingsStatsUtil.getList(FLAG));
+    }
+
+    @Test
+    public void getList_notValidString_nullValue() {
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS, FLAG, "abcd", false);
+
+        assertNull(SettingsStatsUtil.getList(FLAG));
+    }
+
+    @Test
+    public void getList_validString_correctValue() {
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS, FLAG, ENCODED, false);
+
+        assertArrayEquals(KEYS, SettingsStatsUtil.getList(FLAG).element);
+    }
+
+    @Test
+    public void logGlobalSettings_noWhitelist_correctSize() {
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS,
+                "GlobalFeature__boolean_whitelist", "", false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS,
+                "GlobalFeature__integer_whitelist", "", false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS,
+                "GlobalFeature__float_whitelist", "", false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS,
+                "GlobalFeature__string_whitelist", "", false);
+
+        assertEquals(0, SettingsStatsUtil.logGlobalSettings(mContext, SETTING_SNAPSHOT,
+                USER_SYSTEM).size());
+    }
+
+    @Test
+    public void logGlobalSettings_correctSize() {
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS,
+                "GlobalFeature__boolean_whitelist", ENCODED, false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS,
+                "GlobalFeature__integer_whitelist", ENCODED, false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS,
+                "GlobalFeature__float_whitelist", ENCODED, false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS,
+                "GlobalFeature__string_whitelist", ENCODED, false);
+
+        assertEquals(KEYS.length * 4,
+                SettingsStatsUtil.logGlobalSettings(mContext, SETTING_SNAPSHOT,
+                        USER_SYSTEM).size());
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
index 881561f..1f6ba7a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
@@ -238,9 +238,6 @@
         assertTrue(targetActivity.mLaunchTaskBehind);
 
         anotherHomeActivity.moveFocusableActivityToTop("launchAnotherHome");
-        // The current top activity is not the recents so the animation should be canceled.
-        verify(mService.mWindowManager, times(1)).cancelRecentsAnimation(
-                eq(REORDER_KEEP_IN_PLACE), any() /* reason */);
 
         // The test uses mocked RecentsAnimationController so we have to invoke the callback
         // manually to simulate the flow.
@@ -279,10 +276,6 @@
 
         fullscreenStack.moveToFront("Activity start");
 
-        // Ensure that the recents animation was canceled by cancelAnimationSynchronously().
-        verify(mService.mWindowManager, times(1)).cancelRecentsAnimation(
-                eq(REORDER_KEEP_IN_PLACE), any());
-
         // Assume recents animation already started, set a state that cancel recents animation
         // with screenshot.
         doReturn(true).when(mRecentsAnimationController).shouldDeferCancelUntilNextTransition();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index 7a075a2..4a8e8da 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -120,8 +120,8 @@
             IWindow iWindow = mock(IWindow.class);
             doReturn(mock(IBinder.class)).when(iWindow).asBinder();
             window = WindowTestsBase.createWindow(null, TYPE_APPLICATION_STARTING, activity,
-                    "Starting window", 0 /* ownerId */, false /* internalWindows */, wm,
-                    mock(Session.class), iWindow, mPowerManagerWrapper);
+                    "Starting window", 0 /* ownerId */, 0 /* userId*/, false /* internalWindows */,
+                    wm, mock(Session.class), iWindow, mPowerManagerWrapper);
             activity.startingWindow = window;
         }
         if (mRunnableWhenAddingSplashScreen != null) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
index 084216a..fc95556 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
@@ -112,7 +112,7 @@
 
         TestWindowState(WindowManagerService service, Session session, IWindow window,
                 WindowManager.LayoutParams attrs, WindowToken token) {
-            super(service, session, window, token, null, OP_NONE, 0, attrs, 0, 0,
+            super(service, session, window, token, null, OP_NONE, 0, attrs, 0, 0, 0,
                     false /* ownerCanAddInternalSystemWindow */);
         }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 397f73c..e561c13 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -41,6 +41,7 @@
 
 import android.content.Context;
 import android.content.Intent;
+import android.os.UserHandle;
 import android.util.Log;
 import android.view.Display;
 import android.view.DisplayInfo;
@@ -296,12 +297,13 @@
 
     WindowState createWindow(WindowState parent, int type, WindowToken token, String name,
             int ownerId, boolean ownerCanAddInternalSystemWindow) {
-        return createWindow(parent, type, token, name, ownerId, ownerCanAddInternalSystemWindow,
-                mWm, mMockSession, mIWindow, mSystemServicesTestRule.getPowerManagerWrapper());
+        return createWindow(parent, type, token, name, ownerId, UserHandle.getUserId(ownerId),
+                ownerCanAddInternalSystemWindow, mWm, mMockSession, mIWindow,
+                mSystemServicesTestRule.getPowerManagerWrapper());
     }
 
     static WindowState createWindow(WindowState parent, int type, WindowToken token,
-            String name, int ownerId, boolean ownerCanAddInternalSystemWindow,
+            String name, int ownerId, int userId, boolean ownerCanAddInternalSystemWindow,
             WindowManagerService service, Session session, IWindow iWindow,
             WindowState.PowerManagerWrapper powerManagerWrapper) {
         synchronized (service.mGlobalLock) {
@@ -309,8 +311,8 @@
             attrs.setTitle(name);
 
             final WindowState w = new WindowState(service, session, iWindow, token, parent,
-                    OP_NONE,
-                    0, attrs, VISIBLE, ownerId, ownerCanAddInternalSystemWindow,
+                    OP_NONE, 0, attrs, VISIBLE, ownerId, userId,
+                    ownerCanAddInternalSystemWindow,
                     powerManagerWrapper);
             // TODO: Probably better to make this call in the WindowState ctor to avoid errors with
             // adding it to the token...
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
index 76479cb..535d53e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
@@ -29,6 +29,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
 
+import android.content.res.Configuration;
 import android.os.IBinder;
 import android.platform.test.annotations.Presubmit;
 
@@ -134,6 +135,30 @@
         assertEquals(0, token.getWindowsCount());
     }
 
+    @Test
+    public void testClearFixedRotationTransform() {
+        final WindowToken appToken = mAppWindow.mToken;
+        final WindowToken wallpaperToken = mWallpaperWindow.mToken;
+        final Configuration config = new Configuration(mDisplayContent.getConfiguration());
+        final int originalRotation = config.windowConfiguration.getRotation();
+        final int targetRotation = (originalRotation + 1) % 4;
+
+        config.windowConfiguration.setRotation(targetRotation);
+        appToken.applyFixedRotationTransform(mDisplayInfo, mDisplayContent.mDisplayFrames, config);
+        wallpaperToken.linkFixedRotationTransform(appToken);
+
+        // The window tokens should apply the rotation by the transformation.
+        assertEquals(targetRotation, appToken.getWindowConfiguration().getRotation());
+        assertEquals(targetRotation, wallpaperToken.getWindowConfiguration().getRotation());
+
+        // The display doesn't rotate, the transformation will be canceled.
+        mAppWindow.mToken.clearFixedRotationTransform(null /* applyDisplayRotation */);
+
+        // The window tokens should restore to the original rotation.
+        assertEquals(originalRotation, appToken.getWindowConfiguration().getRotation());
+        assertEquals(originalRotation, wallpaperToken.getWindowConfiguration().getRotation());
+    }
+
     /**
      * Test that {@link WindowToken} constructor parameters is set with expectation.
      */
diff --git a/telecomm/java/android/telecom/Conference.java b/telecomm/java/android/telecom/Conference.java
index bce06e4..4e14fd3 100644
--- a/telecomm/java/android/telecom/Conference.java
+++ b/telecomm/java/android/telecom/Conference.java
@@ -1092,16 +1092,16 @@
      * This is applicable in two cases:
      * <ol>
      *     <li>When {@link #setConferenceState(boolean)} is used to mark a conference as
-     *     temporarily "not a conference"; we need to present the correct address in the in-call
-     *     UI.</li>
+     *     temporarily "not a conference"; we need to present the correct address presentation in
+     *     the in-call UI.</li>
      *     <li>When the conference is not hosted on the current device, we need to know the address
-     *     information for the purpose of showing the original address to the user, as well as for
-     *     logging to the call log.</li>
+     *     presentation information for the purpose of showing the original address to the user, as
+     *     well as for logging to the call log.</li>
      * </ol>
-     * @return The address of the conference, or {@code null} if not applicable.
+     * @return The address presentation of the conference.
      * @hide
      */
-    public final int getAddressPresentation() {
+    public final @TelecomManager.Presentation int getAddressPresentation() {
         return mAddressPresentation;
     }
 
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 0d66013..73296986 100755
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -1865,25 +1865,23 @@
         mConferenceById.put(callId, conference);
         mIdByConference.put(conference, callId);
         conference.addListener(mConferenceListener);
-        ParcelableConference parcelableConference = new ParcelableConference(
-                request.getAccountHandle(),
-                conference.getState(),
-                conference.getConnectionCapabilities(),
-                conference.getConnectionProperties(),
-                Collections.<String>emptyList(), //connectionIds
-                conference.getVideoProvider() == null ?
-                        null : conference.getVideoProvider().getInterface(),
-                conference.getVideoState(),
-                conference.getConnectTimeMillis(),
-                conference.getConnectionStartElapsedRealtimeMillis(),
-                conference.getStatusHints(),
-                conference.getExtras(),
-                conference.getAddress(),
-                conference.getAddressPresentation(),
-                conference.getCallerDisplayName(),
-                conference.getCallerDisplayNamePresentation(),
-                conference.getDisconnectCause(),
-                conference.isRingbackRequested());
+        ParcelableConference parcelableConference = new ParcelableConference.Builder(
+                request.getAccountHandle(), conference.getState())
+                .setConnectionCapabilities(conference.getConnectionCapabilities())
+                .setConnectionProperties(conference.getConnectionProperties())
+                .setVideoAttributes(conference.getVideoProvider() == null
+                                ? null : conference.getVideoProvider().getInterface(),
+                        conference.getVideoState())
+                .setConnectTimeMillis(conference.getConnectTimeMillis(),
+                        conference.getConnectionStartElapsedRealtimeMillis())
+                .setStatusHints(conference.getStatusHints())
+                .setExtras(conference.getExtras())
+                .setAddress(conference.getAddress(), conference.getAddressPresentation())
+                .setCallerDisplayName(conference.getCallerDisplayName(),
+                        conference.getCallerDisplayNamePresentation())
+                .setDisconnectCause(conference.getDisconnectCause())
+                .setRingbackRequested(conference.isRingbackRequested())
+                .build();
         if (conference.getState() != Connection.STATE_DISCONNECTED) {
             conference.setTelecomCallId(callId);
             mAdapter.setVideoProvider(callId, conference.getVideoProvider());
@@ -2484,23 +2482,25 @@
                 }
             }
             conference.setTelecomCallId(id);
-            ParcelableConference parcelableConference = new ParcelableConference(
-                    conference.getPhoneAccountHandle(),
-                    conference.getState(),
-                    conference.getConnectionCapabilities(),
-                    conference.getConnectionProperties(),
-                    connectionIds,
-                    conference.getVideoProvider() == null ?
-                            null : conference.getVideoProvider().getInterface(),
-                    conference.getVideoState(),
-                    conference.getConnectTimeMillis(),
-                    conference.getConnectionStartElapsedRealtimeMillis(),
-                    conference.getStatusHints(),
-                    conference.getExtras(),
-                    conference.getAddress(),
-                    conference.getAddressPresentation(),
-                    conference.getCallerDisplayName(),
-                    conference.getCallerDisplayNamePresentation());
+            ParcelableConference parcelableConference = new ParcelableConference.Builder(
+                    conference.getPhoneAccountHandle(), conference.getState())
+                    .setConnectionCapabilities(conference.getConnectionCapabilities())
+                    .setConnectionProperties(conference.getConnectionProperties())
+                    .setConnectionIds(connectionIds)
+                    .setVideoAttributes(conference.getVideoProvider() == null
+                                    ? null : conference.getVideoProvider().getInterface(),
+                            conference.getVideoState())
+                    .setConnectTimeMillis(conference.getConnectTimeMillis(),
+                            conference.getConnectionStartElapsedRealtimeMillis())
+                    .setStatusHints(conference.getStatusHints())
+                    .setExtras(conference.getExtras())
+                    .setAddress(conference.getAddress(), conference.getAddressPresentation())
+                    .setCallerDisplayName(conference.getCallerDisplayName(),
+                            conference.getCallerDisplayNamePresentation())
+                    .setDisconnectCause(conference.getDisconnectCause())
+                    .setRingbackRequested(conference.isRingbackRequested())
+                    .setCallDirection(conference.getCallDirection())
+                    .build();
 
             mAdapter.addConferenceCall(id, parcelableConference);
             mAdapter.setVideoProvider(id, conference.getVideoProvider());
diff --git a/telecomm/java/android/telecom/ParcelableConference.java b/telecomm/java/android/telecom/ParcelableConference.java
index 90b69a3..1f8aafb 100644
--- a/telecomm/java/android/telecom/ParcelableConference.java
+++ b/telecomm/java/android/telecom/ParcelableConference.java
@@ -22,6 +22,7 @@
 import android.os.Parcelable;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 import com.android.internal.telecom.IVideoProvider;
@@ -32,25 +33,130 @@
  */
 public final class ParcelableConference implements Parcelable {
 
-    private PhoneAccountHandle mPhoneAccount;
-    private int mState;
-    private int mConnectionCapabilities;
-    private int mConnectionProperties;
-    private List<String> mConnectionIds;
-    private long mConnectTimeMillis = Conference.CONNECT_TIME_NOT_SPECIFIED;
+    public static final class Builder {
+        private final PhoneAccountHandle mPhoneAccount;
+        private final int mState;
+        private int mConnectionCapabilities;
+        private int mConnectionProperties;
+        private List<String> mConnectionIds = Collections.emptyList();
+        private long mConnectTimeMillis = Conference.CONNECT_TIME_NOT_SPECIFIED;
+        private IVideoProvider mVideoProvider;
+        private int mVideoState = VideoProfile.STATE_AUDIO_ONLY;
+        private StatusHints mStatusHints;
+        private Bundle mExtras;
+        private long mConnectElapsedTimeMillis = Conference.CONNECT_TIME_NOT_SPECIFIED;
+        private Uri mAddress;
+        private int mAddressPresentation = TelecomManager.PRESENTATION_UNKNOWN;
+        private String mCallerDisplayName;
+        private int mCallerDisplayNamePresentation = TelecomManager.PRESENTATION_UNKNOWN;;
+        private DisconnectCause mDisconnectCause;
+        private boolean mRingbackRequested;
+        private int mCallDirection = Call.Details.DIRECTION_UNKNOWN;
+
+        public Builder(
+                PhoneAccountHandle phoneAccount,
+                int state) {
+            mPhoneAccount = phoneAccount;
+            mState = state;
+        }
+
+        public Builder setDisconnectCause(DisconnectCause cause) {
+            mDisconnectCause = cause;
+            return this;
+        }
+
+        public Builder setRingbackRequested(boolean requested) {
+            mRingbackRequested = requested;
+            return this;
+        }
+
+        public Builder setCallerDisplayName(String callerDisplayName,
+                @TelecomManager.Presentation int callerDisplayNamePresentation) {
+            mCallerDisplayName = callerDisplayName;
+            mCallerDisplayNamePresentation = callerDisplayNamePresentation;
+            return this;
+        }
+
+        public Builder setAddress(Uri address,
+                @TelecomManager.Presentation int addressPresentation) {
+            mAddress = address;
+            mAddressPresentation = addressPresentation;
+            return this;
+        }
+
+        public Builder setExtras(Bundle extras) {
+            mExtras = extras;
+            return this;
+        }
+
+        public Builder setStatusHints(StatusHints hints) {
+            mStatusHints = hints;
+            return this;
+        }
+
+        public Builder setConnectTimeMillis(long connectTimeMillis, long connectElapsedTimeMillis) {
+            mConnectTimeMillis = connectTimeMillis;
+            mConnectElapsedTimeMillis = connectElapsedTimeMillis;
+            return this;
+        }
+
+        public Builder setVideoAttributes(IVideoProvider provider,
+                @VideoProfile.VideoState int videoState) {
+            mVideoProvider = provider;
+            mVideoState = videoState;
+            return this;
+        }
+
+        public Builder setConnectionIds(List<String> connectionIds) {
+            mConnectionIds = connectionIds;
+            return this;
+        }
+
+        public Builder setConnectionProperties(int properties) {
+            mConnectionProperties = properties;
+            return this;
+        }
+
+        public Builder setConnectionCapabilities(int capabilities) {
+            mConnectionCapabilities = capabilities;
+            return this;
+        }
+
+        public Builder setCallDirection(int callDirection) {
+            mCallDirection = callDirection;
+            return this;
+        }
+
+        public ParcelableConference build() {
+            return new ParcelableConference(mPhoneAccount, mState, mConnectionCapabilities,
+                    mConnectionProperties, mConnectionIds, mVideoProvider, mVideoState,
+                    mConnectTimeMillis, mConnectElapsedTimeMillis, mStatusHints, mExtras, mAddress,
+                    mAddressPresentation, mCallerDisplayName, mCallerDisplayNamePresentation,
+                    mDisconnectCause, mRingbackRequested, mCallDirection);
+        }
+    }
+
+
+    private final PhoneAccountHandle mPhoneAccount;
+    private final int mState;
+    private final int mConnectionCapabilities;
+    private final int mConnectionProperties;
+    private final List<String> mConnectionIds;
+    private final long mConnectTimeMillis;
     private final IVideoProvider mVideoProvider;
     private final int mVideoState;
-    private StatusHints mStatusHints;
-    private Bundle mExtras;
-    private long mConnectElapsedTimeMillis = Conference.CONNECT_TIME_NOT_SPECIFIED;
+    private final StatusHints mStatusHints;
+    private final Bundle mExtras;
+    private final long mConnectElapsedTimeMillis;
     private final Uri mAddress;
     private final int mAddressPresentation;
     private final String mCallerDisplayName;
     private final int mCallerDisplayNamePresentation;
-    private DisconnectCause mDisconnectCause;
-    private boolean mRingbackRequested;
+    private final DisconnectCause mDisconnectCause;
+    private final boolean mRingbackRequested;
+    private final int mCallDirection;
 
-    public ParcelableConference(
+    private ParcelableConference(
             PhoneAccountHandle phoneAccount,
             int state,
             int connectionCapabilities,
@@ -67,31 +173,8 @@
             String callerDisplayName,
             int callerDisplayNamePresentation,
             DisconnectCause disconnectCause,
-            boolean ringbackRequested) {
-        this(phoneAccount, state, connectionCapabilities, connectionProperties, connectionIds,
-                videoProvider, videoState, connectTimeMillis, connectElapsedTimeMillis,
-                statusHints, extras, address, addressPresentation, callerDisplayName,
-                callerDisplayNamePresentation);
-        mDisconnectCause = disconnectCause;
-        mRingbackRequested = ringbackRequested;
-    }
-
-    public ParcelableConference(
-            PhoneAccountHandle phoneAccount,
-            int state,
-            int connectionCapabilities,
-            int connectionProperties,
-            List<String> connectionIds,
-            IVideoProvider videoProvider,
-            int videoState,
-            long connectTimeMillis,
-            long connectElapsedTimeMillis,
-            StatusHints statusHints,
-            Bundle extras,
-            Uri address,
-            int addressPresentation,
-            String callerDisplayName,
-            int callerDisplayNamePresentation) {
+            boolean ringbackRequested,
+            int callDirection) {
         mPhoneAccount = phoneAccount;
         mState = state;
         mConnectionCapabilities = connectionCapabilities;
@@ -107,8 +190,9 @@
         mAddressPresentation = addressPresentation;
         mCallerDisplayName = callerDisplayName;
         mCallerDisplayNamePresentation = callerDisplayNamePresentation;
-        mDisconnectCause = null;
-        mRingbackRequested = false;
+        mDisconnectCause = disconnectCause;
+        mRingbackRequested = ringbackRequested;
+        mCallDirection = callDirection;
     }
 
     @Override
@@ -134,6 +218,8 @@
                 .append(mRingbackRequested)
                 .append(", disconnectCause: ")
                 .append(mDisconnectCause)
+                .append(", callDirection: ")
+                .append(mCallDirection)
                 .toString();
     }
 
@@ -192,10 +278,15 @@
     public boolean isRingbackRequested() {
         return mRingbackRequested;
     }
+
     public int getHandlePresentation() {
         return mAddressPresentation;
     }
 
+    public int getCallDirection() {
+        return mCallDirection;
+    }
+
     public static final @android.annotation.NonNull Parcelable.Creator<ParcelableConference> CREATOR =
             new Parcelable.Creator<ParcelableConference> () {
         @Override
@@ -220,12 +311,13 @@
             int callerDisplayNamePresentation = source.readInt();
             DisconnectCause disconnectCause = source.readParcelable(classLoader);
             boolean isRingbackRequested = source.readInt() == 1;
+            int callDirection = source.readInt();
 
             return new ParcelableConference(phoneAccount, state, capabilities, properties,
                     connectionIds, videoCallProvider, videoState, connectTimeMillis,
                     connectElapsedTimeMillis, statusHints, extras, address, addressPresentation,
                     callerDisplayName, callerDisplayNamePresentation, disconnectCause,
-                    isRingbackRequested);
+                    isRingbackRequested, callDirection);
         }
 
         @Override
@@ -261,5 +353,6 @@
         destination.writeInt(mCallerDisplayNamePresentation);
         destination.writeParcelable(mDisconnectCause, 0);
         destination.writeInt(mRingbackRequested ? 1 : 0);
+        destination.writeInt(mCallDirection);
     }
 }
diff --git a/wifi/jarjar-rules.txt b/wifi/jarjar-rules.txt
index cf11f43..f852311 100644
--- a/wifi/jarjar-rules.txt
+++ b/wifi/jarjar-rules.txt
@@ -1,6 +1,13 @@
 # used by wifi-service
+# TODO (b/153596226): Find a solution for networkstack's AIDL parcelables & interfaces.
+# Parcelable class names are serialized in the wire, so renaming them
+# will result in the class not being found for any parcelable received/sent from the
+# wifi-service jar.
+
+# Note: This rule is needed to ensure the rule below does not rename a Parcelable (see TODO above).
 rule android.net.DhcpResultsParcelable* @0
 rule android.net.DhcpResults* com.android.server.x.wifi.net.DhcpResults@1
+# Note: This rule is needed to ensure the rule below does not rename a Parcelable (see TODO above).
 rule android.net.InterfaceConfigurationParcel* @0
 rule android.net.InterfaceConfiguration* com.android.server.x.wifi.net.InterfaceConfiguration@1
 rule android.net.IpMemoryStore* com.android.server.x.wifi.net.IpMemoryStore@1
@@ -10,11 +17,18 @@
 rule android.net.ip.IpClientCallbacks* com.android.server.x.wifi.net.ip.IpClientCallbacks@1
 rule android.net.ip.IpClientManager* com.android.server.x.wifi.net.ip.IpClientManager@1
 rule android.net.ip.IpClientUtil* com.android.server.x.wifi.net.ip.IpClientUtil@1
+rule android.net.ipmemorystore.OnBlobRetrievedListener* com.android.server.x.wifi.net.ipmemorystore.OnBlobRetrievedListener@1
+rule android.net.ipmemorystore.OnStatusListener* com.android.server.x.wifi.net.ipmemorystore.OnStatusListener@1
+# Note: This rule is needed to ensure the rule below does not rename a Parcelable (see TODO above).
+rule android.net.ipmemorystore.StatusParcelable* @0
+rule android.net.ipmemorystore.Status* com.android.server.x.wifi.net.ipmemorystore.Status@1
+rule android.net.networkstack.ModuleNetworkStackClient* com.android.server.x.wifi.net.networkstack.ModuleNetworkStackClient@1
+rule android.net.networkstack.NetworkStackClientBase* com.android.server.x.wifi.net.networkstack.NetworkStackClientBase@1
 rule android.net.shared.InetAddressUtils* com.android.server.x.wifi.net.shared.InetAddressUtils@1
 rule android.net.shared.InitialConfiguration* com.android.server.x.wifi.net.shared.InitialConfiguration@1
 rule android.net.shared.IpConfigurationParcelableUtil* com.android.server.x.wifi.net.shared.IpConfigurationParcelableUtil@1
+rule android.net.shared.Layer2Information* com.android.server.x.wifi.net.shared.Layer2Information@1
 rule android.net.shared.LinkPropertiesParcelableUtil* com.android.server.x.wifi.net.shared.LinkPropertiesParcelableUtil@1
-rule android.net.shared.ParcelableUtil* com.android.server.x.wifi.net.shared.ParcelableUtil@1
 rule android.net.shared.NetdUtils* com.android.server.x.wifi.net.shared.NetdUtils@1
 rule android.net.shared.NetworkMonitorUtils* com.android.server.x.wifi.net.shared.NetworkMonitorUtils@1
 rule android.net.shared.ParcelableUtil* com.android.server.x.wifi.net.shared.ParcelableUtil@1
@@ -28,6 +42,8 @@
 rule android.net.util.NetUtils* com.android.server.x.wifi.net.util.NetUtils@1
 rule android.net.util.IpUtils* com.android.server.x.wifi.net.util.IpUtils@1
 
+rule androidx.annotation.** com.android.server.x.wifi.androidx.annotation.@1
+
 # We don't jar-jar the entire package because, we still use some classes (like
 # AsyncChannel in com.android.internal.util) from these packages which are not
 # inside our jar (currently in framework.jar, but will be in wifisdk.jar in the future).
@@ -54,8 +70,16 @@
 # Use our statically linked PlatformProperties library
 rule android.sysprop.** com.android.server.x.wifi.sysprop.@1
 # Use our statically linked HIDL stubs
-rule android.hardware.** com.android.server.x.wifi.hardware.@1
+# Note: android.hardware.wifi.** is used by various wifi feature flags. This unfortunately is also the namespace
+# used by vendor HAL stubs. So, this rule is intentionally weird to try and filter the vendor HAL stubs only.
+rule android.hardware.wifi.V** com.android.server.x.wifi.hardware.wifi.V@1
+rule android.hardware.wifi.supplicant.** com.android.server.x.wifi.hardware.wifi.supplicant.@1
+rule android.hardware.wifi.hostapd.** com.android.server.x.wifi.hardware.wifi.hostapd.@1
 rule android.hidl.** com.android.server.x.wifi.hidl.@1
+# Use our statically linked ksoap2
+rule org.ksoap2.** com.android.server.x.wifi.ksoap2.@1
+# Use our statically linked nanohttpd
+rule fi.iki.elonen.** com.android.server.x.wifi.elonen.@1
 
 # used by both framework-wifi and wifi-service
 rule android.content.pm.BaseParceledListSlice* android.x.net.wifi.util.BaseParceledListSlice@1
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index f1be8b2..6c8dc00 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -179,6 +179,8 @@
     /**
      * Reason code if one or more of the network suggestions added already exists in platform's
      * database.
+     * Note: this code will not be returned with Android 11 as in-place modification is allowed,
+     * please check {@link #addNetworkSuggestions(List)}.
      * @see WifiNetworkSuggestion#equals(Object)
      */
     public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_DUPLICATE = 3;
@@ -186,6 +188,8 @@
     /**
      * Reason code if the number of network suggestions provided by the app crosses the max
      * threshold set per app.
+     * The framework will reject all suggestions provided by {@link #addNetworkSuggestions(List)} if
+     * the total size exceeds the limit.
      * @see #getMaxNumberOfNetworkSuggestionsPerApp()
      */
     public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_EXCEEDS_MAX_PER_APP = 4;
@@ -193,21 +197,27 @@
     /**
      * Reason code if one or more of the network suggestions removed does not exist in platform's
      * database.
+     * The framework won't remove any suggestions if one or more of suggestions provided
+     * by {@link #removeNetworkSuggestions(List)} does not exist in database.
+     * @see WifiNetworkSuggestion#equals(Object)
      */
     public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_REMOVE_INVALID = 5;
 
     /**
      * Reason code if one or more of the network suggestions added is not allowed.
-     *
+     * The framework will reject all suggestions provided by {@link #addNetworkSuggestions(List)}
+     * if one or more of them is not allowed.
      * This error may be caused by suggestion is using SIM-based encryption method, but calling app
      * is not carrier privileged.
      */
     public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_NOT_ALLOWED = 6;
 
     /**
-     * Reason code if one or more of the network suggestions added is invalid.
-     *
-     * Please user {@link WifiNetworkSuggestion.Builder} to create network suggestions.
+     * Reason code if one or more of the network suggestions added is invalid. Framework will reject
+     * all the suggestions in the list.
+     * The framework will reject all suggestions provided by {@link #addNetworkSuggestions(List)}
+     * if one or more of them is invalid.
+     * Please use {@link WifiNetworkSuggestion.Builder} to create network suggestions.
      */
     public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_INVALID = 7;
 
@@ -1887,7 +1897,7 @@
      * <li> If user reset network settings, all added suggestions will be discarded. Apps can use
      * {@link #getNetworkSuggestions()} to check if their suggestions are in the device.</li>
      * <li> In-place modification of existing suggestions are allowed.
-     * <li>If the provided suggestions includes any previously provided suggestions by the app,
+     * <li> If the provided suggestions include any previously provided suggestions by the app,
      * previous suggestions will be updated.</li>
      * <li>If one of the provided suggestions marks a previously unmetered suggestion as metered and
      * the device is currently connected to that suggested network, then the device will disconnect