Merge "Import translations. DO NOT MERGE"
diff --git a/Android.bp b/Android.bp
index 2318f7b..c899179 100644
--- a/Android.bp
+++ b/Android.bp
@@ -551,17 +551,25 @@
 
 java_library {
     name: "framework-annotation-proc",
-    srcs: [":framework-all-sources"],
+    srcs: [
+        ":framework-all-sources",
+        "core/java/**/*.logtags",
+    ],
+    sdk_version: "core_platform",
     libs: [
         "app-compat-annotations",
+        "ext",
+        "icing-java-proto-lite",
         "unsupportedappusage",
     ],
+
     installable: false,
     plugins: [
         "unsupportedappusage-annotation-processor",
         "compat-changeid-annotation-processor",
     ],
     static_libs: [
+        "framework-internal-utils",
         "exoplayer2-extractor",
         "android.hardware.wifi-V1.0-java-constants",
     ]
diff --git a/apex/blobstore/framework/java/android/app/blob/BlobHandle.java b/apex/blobstore/framework/java/android/app/blob/BlobHandle.java
index ee0ee98..f110b36 100644
--- a/apex/blobstore/framework/java/android/app/blob/BlobHandle.java
+++ b/apex/blobstore/framework/java/android/app/blob/BlobHandle.java
@@ -214,12 +214,16 @@
     }
 
     /** @hide */
-    public void dump(IndentingPrintWriter fout) {
-        fout.println("algo: " + algorithm);
-        fout.println("digest: " + Base64.encodeToString(digest, Base64.NO_WRAP));
-        fout.println("label: " + label);
-        fout.println("expiryMs: " + expiryTimeMillis);
-        fout.println("tag: " + tag);
+    public void dump(IndentingPrintWriter fout, boolean dumpFull) {
+        if (dumpFull) {
+            fout.println("algo: " + algorithm);
+            fout.println("digest: " + (dumpFull ? encodeDigest() : safeDigest()));
+            fout.println("label: " + label);
+            fout.println("expiryMs: " + expiryTimeMillis);
+            fout.println("tag: " + tag);
+        } else {
+            fout.println(toString());
+        }
     }
 
     /** @hide */
@@ -233,6 +237,26 @@
         Preconditions.checkArgument(tag.length() <= LIMIT_BLOB_TAG_LENGTH, "tag too long");
     }
 
+    @Override
+    public String toString() {
+        return "BlobHandle {"
+                + "algo:" + algorithm + ","
+                + "digest:" + safeDigest() + ","
+                + "label:" + label + ","
+                + "expiryMs:" + expiryTimeMillis + ","
+                + "tag:" + tag
+                + "}";
+    }
+
+    private String safeDigest() {
+        final String digestStr = encodeDigest();
+        return digestStr.substring(0, 2) + ".." + digestStr.substring(digestStr.length() - 2);
+    }
+
+    private String encodeDigest() {
+        return Base64.encodeToString(digest, Base64.NO_WRAP);
+    }
+
     public static final @NonNull Creator<BlobHandle> CREATOR = new Creator<BlobHandle>() {
         @Override
         public @NonNull BlobHandle createFromParcel(@NonNull Parcel source) {
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 7052d60..aba3e8c 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
@@ -48,6 +48,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.XmlUtils;
+import com.android.server.blob.BlobStoreManagerService.DumpArgs;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -240,10 +241,10 @@
         return revocableFd.getRevocableFileDescriptor();
     }
 
-    void dump(IndentingPrintWriter fout) {
+    void dump(IndentingPrintWriter fout, DumpArgs dumpArgs) {
         fout.println("blobHandle:");
         fout.increaseIndent();
-        blobHandle.dump(fout);
+        blobHandle.dump(fout, dumpArgs.shouldDumpFull());
         fout.decreaseIndent();
 
         fout.println("Committers:");
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 215e9c1..13f095e 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
@@ -40,6 +40,7 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.app.blob.BlobHandle;
 import android.app.blob.IBlobStoreManager;
 import android.app.blob.IBlobStoreSession;
@@ -69,6 +70,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.IndentingPrintWriter;
@@ -121,6 +123,9 @@
 
     private PackageManagerInternal mPackageManagerInternal;
 
+    private final Runnable mSaveBlobsInfoRunnable = this::writeBlobsInfo;
+    private final Runnable mSaveSessionsRunnable = this::writeBlobSessions;
+
     public BlobStoreManagerService(Context context) {
         this(context, new Injector());
     }
@@ -533,9 +538,9 @@
     }
 
     private void writeBlobsInfoAsync() {
-        mHandler.post(PooledLambda.obtainRunnable(
-                BlobStoreManagerService::writeBlobsInfo,
-                BlobStoreManagerService.this).recycleOnUse());
+        if (!mHandler.hasCallbacks(mSaveBlobsInfoRunnable)) {
+            mHandler.post(mSaveBlobsInfoRunnable);
+        }
     }
 
     private void writeBlobSessions() {
@@ -549,9 +554,9 @@
     }
 
     private void writeBlobSessionsAsync() {
-        mHandler.post(PooledLambda.obtainRunnable(
-                BlobStoreManagerService::writeBlobSessions,
-                BlobStoreManagerService.this).recycleOnUse());
+        if (!mHandler.hasCallbacks(mSaveSessionsRunnable)) {
+            mHandler.post(mSaveSessionsRunnable);
+        }
     }
 
     private int getPackageUid(String packageName, int userId) {
@@ -597,6 +602,7 @@
         return new AtomicFile(file, "blobs_index" /* commitLogTag */);
     }
 
+    @VisibleForTesting
     void handlePackageRemoved(String packageName, int uid) {
         synchronized (mBlobsLock) {
             // Clean up any pending sessions
@@ -659,6 +665,80 @@
         }
     }
 
+    void runClearAllSessions(@UserIdInt int userId) {
+        synchronized (mBlobsLock) {
+            if (userId == UserHandle.USER_ALL) {
+                mSessions.clear();
+            } else {
+                mSessions.remove(userId);
+            }
+            writeBlobSessionsAsync();
+        }
+    }
+
+    void runClearAllBlobs(@UserIdInt int userId) {
+        synchronized (mBlobsLock) {
+            if (userId == UserHandle.USER_ALL) {
+                mBlobsMap.clear();
+            } else {
+                mBlobsMap.remove(userId);
+            }
+            writeBlobsInfoAsync();
+        }
+    }
+
+    @GuardedBy("mBlobsLock")
+    private void dumpSessionsLocked(IndentingPrintWriter fout, DumpArgs dumpArgs) {
+        for (int i = 0, userCount = mSessions.size(); i < userCount; ++i) {
+            final int userId = mSessions.keyAt(i);
+            if (!dumpArgs.shouldDumpUser(userId)) {
+                continue;
+            }
+            final LongSparseArray<BlobStoreSession> userSessions = mSessions.valueAt(i);
+            fout.println("List of sessions in user #"
+                    + userId + " (" + userSessions.size() + "):");
+            fout.increaseIndent();
+            for (int j = 0, sessionsCount = userSessions.size(); j < sessionsCount; ++j) {
+                final long sessionId = userSessions.keyAt(j);
+                final BlobStoreSession session = userSessions.valueAt(j);
+                if (!dumpArgs.shouldDumpSession(session.getOwnerPackageName(),
+                        session.getOwnerUid(), session.getSessionId())) {
+                    continue;
+                }
+                fout.println("Session #" + sessionId);
+                fout.increaseIndent();
+                session.dump(fout, dumpArgs);
+                fout.decreaseIndent();
+            }
+            fout.decreaseIndent();
+        }
+    }
+
+    @GuardedBy("mBlobsLock")
+    private void dumpBlobsLocked(IndentingPrintWriter fout, DumpArgs dumpArgs) {
+        for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) {
+            final int userId = mBlobsMap.keyAt(i);
+            if (!dumpArgs.shouldDumpUser(userId)) {
+                continue;
+            }
+            final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i);
+            fout.println("List of blobs in user #"
+                    + userId + " (" + userBlobs.size() + "):");
+            fout.increaseIndent();
+            for (int j = 0, blobsCount = userBlobs.size(); j < blobsCount; ++j) {
+                final BlobMetadata blobMetadata = userBlobs.valueAt(j);
+                if (!dumpArgs.shouldDumpBlob(blobMetadata.blobId)) {
+                    continue;
+                }
+                fout.println("Blob #" + blobMetadata.blobId);
+                fout.increaseIndent();
+                blobMetadata.dump(fout, dumpArgs);
+                fout.decreaseIndent();
+            }
+            fout.decreaseIndent();
+        }
+    }
+
     private class PackageChangedReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -808,48 +888,158 @@
         public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer,
                 @Nullable String[] args) {
             // TODO: add proto-based version of this.
-            if (!DumpUtils.checkDumpPermission(mContext, TAG, writer)) return;
+            if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, writer)) return;
+
+            final DumpArgs dumpArgs = DumpArgs.parse(args);
 
             final IndentingPrintWriter fout = new IndentingPrintWriter(writer, "    ");
             synchronized (mBlobsLock) {
                 fout.println("mCurrentMaxSessionId: " + mCurrentMaxSessionId);
                 fout.println();
-                for (int i = 0, userCount = mSessions.size(); i < userCount; ++i) {
-                    final int userId = mSessions.keyAt(i);
-                    final LongSparseArray<BlobStoreSession> userSessions = mSessions.valueAt(i);
-                    fout.println("List of sessions in user #"
-                            + userId + " (" + userSessions.size() + "):");
-                    fout.increaseIndent();
-                    for (int j = 0, sessionsCount = userSessions.size(); j < sessionsCount; ++j) {
-                        final long sessionId = userSessions.keyAt(j);
-                        final BlobStoreSession session = userSessions.valueAt(j);
-                        fout.println("Session #" + sessionId);
-                        fout.increaseIndent();
-                        session.dump(fout);
-                        fout.decreaseIndent();
-                    }
-                    fout.decreaseIndent();
+
+                if (dumpArgs.shouldDumpSessions()) {
+                    dumpSessionsLocked(fout, dumpArgs);
+                    fout.println();
                 }
-
-                fout.print("\n\n");
-
-                for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) {
-                    final int userId = mBlobsMap.keyAt(i);
-                    final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i);
-                    fout.println("List of blobs in user #"
-                            + userId + " (" + userBlobs.size() + "):");
-                    fout.increaseIndent();
-                    for (int j = 0, blobsCount = userBlobs.size(); j < blobsCount; ++j) {
-                        final BlobMetadata blobMetadata = userBlobs.valueAt(j);
-                        fout.println("Blob #" + blobMetadata.blobId);
-                        fout.increaseIndent();
-                        blobMetadata.dump(fout);
-                        fout.decreaseIndent();
-                    }
-                    fout.decreaseIndent();
+                if (dumpArgs.shouldDumpBlobs()) {
+                    dumpBlobsLocked(fout, dumpArgs);
+                    fout.println();
                 }
             }
         }
+
+        @Override
+        public int handleShellCommand(@NonNull ParcelFileDescriptor in,
+                @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err,
+                @NonNull String[] args) {
+            return (new BlobStoreManagerShellCommand(BlobStoreManagerService.this)).exec(this,
+                    in.getFileDescriptor(), out.getFileDescriptor(), err.getFileDescriptor(), args);
+        }
+    }
+
+    static final class DumpArgs {
+        private boolean mDumpFull;
+        private final ArrayList<String> mDumpPackages = new ArrayList<>();
+        private final ArrayList<Integer> mDumpUids = new ArrayList<>();
+        private final ArrayList<Integer> mDumpUserIds = new ArrayList<>();
+        private final ArrayList<Long> mDumpBlobIds = new ArrayList<>();
+        private boolean mDumpOnlySelectedSections;
+        private boolean mDumpSessions;
+        private boolean mDumpBlobs;
+
+        public boolean shouldDumpSession(String packageName, int uid, long blobId) {
+            if (!CollectionUtils.isEmpty(mDumpPackages)
+                    && mDumpPackages.indexOf(packageName) < 0) {
+                return false;
+            }
+            if (!CollectionUtils.isEmpty(mDumpUids)
+                    && mDumpUids.indexOf(uid) < 0) {
+                return false;
+            }
+            if (!CollectionUtils.isEmpty(mDumpBlobIds)
+                    && mDumpBlobIds.indexOf(blobId) < 0) {
+                return false;
+            }
+            return true;
+        }
+
+        public boolean shouldDumpSessions() {
+            if (!mDumpOnlySelectedSections) {
+                return true;
+            }
+            return mDumpSessions;
+        }
+
+        public boolean shouldDumpBlobs() {
+            if (!mDumpOnlySelectedSections) {
+                return true;
+            }
+            return mDumpBlobs;
+        }
+
+        public boolean shouldDumpBlob(long blobId) {
+            return CollectionUtils.isEmpty(mDumpBlobIds)
+                    || mDumpBlobIds.indexOf(blobId) >= 0;
+        }
+
+        public boolean shouldDumpFull() {
+            return mDumpFull;
+        }
+
+        public boolean shouldDumpUser(int userId) {
+            return CollectionUtils.isEmpty(mDumpUserIds)
+                    || mDumpUserIds.indexOf(userId) >= 0;
+        }
+
+        private DumpArgs() {}
+
+        public static DumpArgs parse(String[] args) {
+            final DumpArgs dumpArgs = new DumpArgs();
+            if (args == null) {
+                return dumpArgs;
+            }
+
+            for (int i = 0; i < args.length; ++i) {
+                final String opt = args[i];
+                if ("--full".equals(opt) || "-f".equals(opt)) {
+                    final int callingUid = Binder.getCallingUid();
+                    if (callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID) {
+                        dumpArgs.mDumpFull = true;
+                    }
+                } else if ("--sessions".equals(opt)) {
+                    dumpArgs.mDumpOnlySelectedSections = true;
+                    dumpArgs.mDumpSessions = true;
+                } else if ("--blobs".equals(opt)) {
+                    dumpArgs.mDumpOnlySelectedSections = true;
+                    dumpArgs.mDumpBlobs = true;
+                } else if ("--package".equals(opt) || "-p".equals(opt)) {
+                    dumpArgs.mDumpPackages.add(getStringArgRequired(args, ++i, "packageName"));
+                } else if ("--uid".equals(opt) || "-u".equals(opt)) {
+                    dumpArgs.mDumpUids.add(getIntArgRequired(args, ++i, "uid"));
+                } else if ("--user".equals(opt)) {
+                    dumpArgs.mDumpUserIds.add(getIntArgRequired(args, ++i, "userId"));
+                } else if ("--blob".equals(opt) || "-b".equals(opt)) {
+                    dumpArgs.mDumpBlobIds.add(getLongArgRequired(args, ++i, "blobId"));
+                } else {
+                    // Everything else is assumed to be blob ids.
+                    dumpArgs.mDumpBlobIds.add(getLongArgRequired(args, i, "blobId"));
+                }
+            }
+            return dumpArgs;
+        }
+
+        private static String getStringArgRequired(String[] args, int index, String argName) {
+            if (index >= args.length) {
+                throw new IllegalArgumentException("Missing " + argName);
+            }
+            return args[index];
+        }
+
+        private static int getIntArgRequired(String[] args, int index, String argName) {
+            if (index >= args.length) {
+                throw new IllegalArgumentException("Missing " + argName);
+            }
+            final int value;
+            try {
+                value = Integer.parseInt(args[index]);
+            } catch (NumberFormatException e) {
+                throw new IllegalArgumentException("Invalid " + argName + ": " + args[index]);
+            }
+            return value;
+        }
+
+        private static long getLongArgRequired(String[] args, int index, String argName) {
+            if (index >= args.length) {
+                throw new IllegalArgumentException("Missing " + argName);
+            }
+            final long value;
+            try {
+                value = Long.parseLong(args[index]);
+            } catch (NumberFormatException e) {
+                throw new IllegalArgumentException("Invalid " + argName + ": " + args[index]);
+            }
+            return value;
+        }
     }
 
     @VisibleForTesting
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerShellCommand.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerShellCommand.java
new file mode 100644
index 0000000..3ac30f8
--- /dev/null
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerShellCommand.java
@@ -0,0 +1,111 @@
+/*
+ * 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.
+ */
+package com.android.server.blob;
+
+import android.os.ShellCommand;
+import android.os.UserHandle;
+
+import java.io.PrintWriter;
+
+class BlobStoreManagerShellCommand extends ShellCommand {
+
+    private final BlobStoreManagerService mService;
+
+    BlobStoreManagerShellCommand(BlobStoreManagerService blobStoreManagerService) {
+        mService = blobStoreManagerService;
+    }
+
+    @Override
+    public int onCommand(String cmd) {
+        if (cmd == null) {
+            return handleDefaultCommands(null);
+        }
+        final PrintWriter pw = getOutPrintWriter();
+        switch (cmd) {
+            case "clear-all-sessions":
+                return runClearAllSessions(pw);
+            case "clear-all-blobs":
+                return runClearAllBlobs(pw);
+            default:
+                return handleDefaultCommands(cmd);
+        }
+    }
+
+    private int runClearAllSessions(PrintWriter pw) {
+        final ParsedArgs args = new ParsedArgs();
+        args.userId = UserHandle.USER_ALL;
+
+        if (parseOptions(pw, args) < 0) {
+            return -1;
+        }
+
+        mService.runClearAllSessions(args.userId);
+        return 0;
+    }
+
+    private int runClearAllBlobs(PrintWriter pw) {
+        final ParsedArgs args = new ParsedArgs();
+        args.userId = UserHandle.USER_ALL;
+
+        if (parseOptions(pw, args) < 0) {
+            return -1;
+        }
+
+        mService.runClearAllBlobs(args.userId);
+        return 0;
+    }
+
+    @Override
+    public void onHelp() {
+        final PrintWriter pw = getOutPrintWriter();
+        pw.println("BlobStore service (blob_store) commands:");
+        pw.println("help");
+        pw.println("    Print this help text.");
+        pw.println();
+        pw.println("clear-all-sessions [-u | --user USER_ID]");
+        pw.println("    Remove all sessions.");
+        pw.println("    Options:");
+        pw.println("      -u or --user: specify which user's sessions to be removed;");
+        pw.println("                    If not specified, sessions in all users are removed.");
+        pw.println();
+        pw.println("clear-all-blobs [-u | --user USER_ID]");
+        pw.println("    Remove all blobs.");
+        pw.println("    Options:");
+        pw.println("      -u or --user: specify which user's blobs to be removed;");
+        pw.println("                    If not specified, blobs in all users are removed.");
+        pw.println();
+    }
+
+    private int parseOptions(PrintWriter pw, ParsedArgs args) {
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "-u":
+                case "--user":
+                    args.userId = Integer.parseInt(getNextArgRequired());
+                    break;
+                default:
+                    pw.println("Error: unknown option '" + opt + "'");
+                    return -1;
+            }
+        }
+        return 0;
+    }
+
+    private static class ParsedArgs {
+        public int userId;
+    }
+}
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java
index b457396..54a2997 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java
@@ -51,6 +51,7 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.XmlUtils;
+import com.android.server.blob.BlobStoreManagerService.DumpArgs;
 import com.android.server.blob.BlobStoreManagerService.SessionStateChangeListener;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -453,7 +454,7 @@
         }
     }
 
-    void dump(IndentingPrintWriter fout) {
+    void dump(IndentingPrintWriter fout, DumpArgs dumpArgs) {
         synchronized (mSessionLock) {
             fout.println("state: " + stateToString(mState));
             fout.println("ownerUid: " + mOwnerUid);
@@ -461,7 +462,7 @@
 
             fout.println("blobHandle:");
             fout.increaseIndent();
-            mBlobHandle.dump(fout);
+            mBlobHandle.dump(fout, dumpArgs.shouldDumpFull());
             fout.decreaseIndent();
 
             fout.println("accessMode:");
diff --git a/api/current.txt b/api/current.txt
index 9f1b8c6..5ac58d0 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2875,9 +2875,17 @@
     method public boolean takeScreenshot(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.graphics.Bitmap>);
     field public static final int GESTURE_2_FINGER_DOUBLE_TAP = 20; // 0x14
     field public static final int GESTURE_2_FINGER_SINGLE_TAP = 19; // 0x13
+    field public static final int GESTURE_2_FINGER_SWIPE_DOWN = 26; // 0x1a
+    field public static final int GESTURE_2_FINGER_SWIPE_LEFT = 27; // 0x1b
+    field public static final int GESTURE_2_FINGER_SWIPE_RIGHT = 28; // 0x1c
+    field public static final int GESTURE_2_FINGER_SWIPE_UP = 25; // 0x19
     field public static final int GESTURE_2_FINGER_TRIPLE_TAP = 21; // 0x15
     field public static final int GESTURE_3_FINGER_DOUBLE_TAP = 23; // 0x17
     field public static final int GESTURE_3_FINGER_SINGLE_TAP = 22; // 0x16
+    field public static final int GESTURE_3_FINGER_SWIPE_DOWN = 30; // 0x1e
+    field public static final int GESTURE_3_FINGER_SWIPE_LEFT = 31; // 0x1f
+    field public static final int GESTURE_3_FINGER_SWIPE_RIGHT = 32; // 0x20
+    field public static final int GESTURE_3_FINGER_SWIPE_UP = 29; // 0x1d
     field public static final int GESTURE_3_FINGER_TRIPLE_TAP = 24; // 0x18
     field public static final int GESTURE_DOUBLE_TAP = 17; // 0x11
     field public static final int GESTURE_DOUBLE_TAP_AND_HOLD = 18; // 0x12
@@ -26729,6 +26737,7 @@
     method public int getVolume();
     method public int getVolumeHandling();
     method public int getVolumeMax();
+    method public boolean isSystemRoute();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field public static final int CONNECTION_STATE_CONNECTED = 2; // 0x2
     field public static final int CONNECTION_STATE_CONNECTING = 1; // 0x1
diff --git a/api/module-lib-current.txt b/api/module-lib-current.txt
index 1a2cb74..d11801b 100644
--- a/api/module-lib-current.txt
+++ b/api/module-lib-current.txt
@@ -6,7 +6,7 @@
     method public void addDebugInfo(@NonNull java.util.List<java.lang.String>);
     method public int describeContents();
     method @NonNull public java.util.List<java.lang.String> getDebugInfo();
-    method public int getPhoneId();
+    method public int getSlotIndex();
     method @Nullable public android.os.TimestampedValue<java.lang.Long> getUtcTime();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.timedetector.PhoneTimeSuggestion> CREATOR;
@@ -34,8 +34,8 @@
     method public int describeContents();
     method @NonNull public java.util.List<java.lang.String> getDebugInfo();
     method public int getMatchType();
-    method public int getPhoneId();
     method public int getQuality();
+    method public int getSlotIndex();
     method @Nullable public String getZoneId();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.timezonedetector.PhoneTimeZoneSuggestion> CREATOR;
diff --git a/api/system-current.txt b/api/system-current.txt
index daad703..ff2d308 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -9169,7 +9169,12 @@
     method @WorkerThread public void allocateBytes(java.io.FileDescriptor, long, @RequiresPermission int) throws java.io.IOException;
     method @WorkerThread public long getAllocatableBytes(@NonNull java.util.UUID, @RequiresPermission int) throws java.io.IOException;
     method public static boolean hasIsolatedStorage();
+    method public void updateExternalStorageFileQuotaType(@NonNull java.io.File, int) throws java.io.IOException;
     field @RequiresPermission(android.Manifest.permission.ALLOCATE_AGGRESSIVE) public static final int FLAG_ALLOCATE_AGGRESSIVE = 1; // 0x1
+    field public static final int QUOTA_TYPE_MEDIA_AUDIO = 2; // 0x2
+    field public static final int QUOTA_TYPE_MEDIA_IMAGE = 1; // 0x1
+    field public static final int QUOTA_TYPE_MEDIA_NONE = 0; // 0x0
+    field public static final int QUOTA_TYPE_MEDIA_VIDEO = 3; // 0x3
   }
 
   public final class StorageVolume implements android.os.Parcelable {
@@ -10107,6 +10112,7 @@
     ctor public AugmentedAutofillService();
     method protected final void dump(java.io.FileDescriptor, java.io.PrintWriter, String[]);
     method protected void dump(@NonNull java.io.PrintWriter, @NonNull String[]);
+    method @Nullable public final android.service.autofill.FillEventHistory getFillEventHistory();
     method public void onConnected();
     method public void onDisconnected();
     method public void onFillRequest(@NonNull android.service.autofill.augmented.FillRequest, @NonNull android.os.CancellationSignal, @NonNull android.service.autofill.augmented.FillController, @NonNull android.service.autofill.augmented.FillCallback);
@@ -10136,6 +10142,7 @@
   public static final class FillResponse.Builder {
     ctor public FillResponse.Builder();
     method @NonNull public android.service.autofill.augmented.FillResponse build();
+    method @NonNull public android.service.autofill.augmented.FillResponse.Builder setClientState(@Nullable android.os.Bundle);
     method @NonNull public android.service.autofill.augmented.FillResponse.Builder setFillWindow(@Nullable android.service.autofill.augmented.FillWindow);
     method @NonNull public android.service.autofill.augmented.FillResponse.Builder setInlineSuggestions(@Nullable java.util.List<android.service.autofill.Dataset>);
   }
diff --git a/api/test-current.txt b/api/test-current.txt
index e604806..fda3ab4 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -3104,6 +3104,7 @@
     ctor public AugmentedAutofillService();
     method protected final void dump(java.io.FileDescriptor, java.io.PrintWriter, String[]);
     method protected void dump(@NonNull java.io.PrintWriter, @NonNull String[]);
+    method @Nullable public final android.service.autofill.FillEventHistory getFillEventHistory();
     method public void onConnected();
     method public void onDisconnected();
     method public void onFillRequest(@NonNull android.service.autofill.augmented.FillRequest, @NonNull android.os.CancellationSignal, @NonNull android.service.autofill.augmented.FillController, @NonNull android.service.autofill.augmented.FillCallback);
@@ -3133,6 +3134,7 @@
   public static final class FillResponse.Builder {
     ctor public FillResponse.Builder();
     method @NonNull public android.service.autofill.augmented.FillResponse build();
+    method @NonNull public android.service.autofill.augmented.FillResponse.Builder setClientState(@Nullable android.os.Bundle);
     method @NonNull public android.service.autofill.augmented.FillResponse.Builder setFillWindow(@Nullable android.service.autofill.augmented.FillWindow);
     method @NonNull public android.service.autofill.augmented.FillResponse.Builder setInlineSuggestions(@Nullable java.util.List<android.service.autofill.Dataset>);
   }
diff --git a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
index 47fc7e1..9cf1de9 100644
--- a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
+++ b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
@@ -19,9 +19,17 @@
 
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SINGLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_RIGHT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_UP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_TRIPLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SINGLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_RIGHT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_UP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_TRIPLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP_AND_HOLD;
@@ -89,7 +97,15 @@
             GESTURE_SWIPE_RIGHT,
             GESTURE_SWIPE_RIGHT_AND_UP,
             GESTURE_SWIPE_RIGHT_AND_LEFT,
-            GESTURE_SWIPE_RIGHT_AND_DOWN
+            GESTURE_SWIPE_RIGHT_AND_DOWN,
+            GESTURE_2_FINGER_SWIPE_DOWN,
+            GESTURE_2_FINGER_SWIPE_LEFT,
+            GESTURE_2_FINGER_SWIPE_RIGHT,
+            GESTURE_2_FINGER_SWIPE_UP,
+            GESTURE_3_FINGER_SWIPE_DOWN,
+            GESTURE_3_FINGER_SWIPE_LEFT,
+            GESTURE_3_FINGER_SWIPE_RIGHT,
+            GESTURE_3_FINGER_SWIPE_UP
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface GestureId {}
@@ -167,6 +183,14 @@
             case GESTURE_SWIPE_UP_AND_LEFT: return "GESTURE_SWIPE_UP_AND_LEFT";
             case GESTURE_SWIPE_UP_AND_DOWN: return "GESTURE_SWIPE_UP_AND_DOWN";
             case GESTURE_SWIPE_UP_AND_RIGHT: return "GESTURE_SWIPE_UP_AND_RIGHT";
+            case GESTURE_2_FINGER_SWIPE_DOWN: return "GESTURE_2_FINGER_SWIPE_DOWN";
+            case GESTURE_2_FINGER_SWIPE_LEFT: return "GESTURE_2_FINGER_SWIPE_LEFT";
+            case GESTURE_2_FINGER_SWIPE_RIGHT: return "GESTURE_2_FINGER_SWIPE_RIGHT";
+            case GESTURE_2_FINGER_SWIPE_UP: return "GESTURE_2_FINGER_SWIPE_UP";
+            case GESTURE_3_FINGER_SWIPE_DOWN: return "GESTURE_3_FINGER_SWIPE_DOWN";
+            case GESTURE_3_FINGER_SWIPE_LEFT: return "GESTURE_3_FINGER_SWIPE_LEFT";
+            case GESTURE_3_FINGER_SWIPE_RIGHT: return "GESTURE_3_FINGER_SWIPE_RIGHT";
+            case GESTURE_3_FINGER_SWIPE_UP: return "GESTURE_3_FINGER_SWIPE_UP";
             default: return Integer.toHexString(eventType);
         }
     }
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 27cd285..2165fb3 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -349,6 +349,46 @@
     public static final int GESTURE_3_FINGER_TRIPLE_TAP = 24;
 
     /**
+     * The user has performed a two-finger swipe up gesture on the touch screen.
+     */
+    public static final int GESTURE_2_FINGER_SWIPE_UP = 25;
+
+    /**
+     * The user has performed a two-finger swipe down gesture on the touch screen.
+     */
+    public static final int GESTURE_2_FINGER_SWIPE_DOWN = 26;
+
+    /**
+     * The user has performed a two-finger swipe left gesture on the touch screen.
+     */
+    public static final int GESTURE_2_FINGER_SWIPE_LEFT = 27;
+
+    /**
+     * The user has performed a two-finger swipe right gesture on the touch screen.
+     */
+    public static final int GESTURE_2_FINGER_SWIPE_RIGHT = 28;
+
+    /**
+     * The user has performed a three-finger swipe up gesture on the touch screen.
+     */
+    public static final int GESTURE_3_FINGER_SWIPE_UP = 29;
+
+    /**
+     * The user has performed a three-finger swipe down gesture on the touch screen.
+     */
+    public static final int GESTURE_3_FINGER_SWIPE_DOWN = 30;
+
+    /**
+     * The user has performed a three-finger swipe left gesture on the touch screen.
+     */
+    public static final int GESTURE_3_FINGER_SWIPE_LEFT = 31;
+
+    /**
+     * The user has performed a three-finger swipe right gesture on the touch screen.
+     */
+    public static final int GESTURE_3_FINGER_SWIPE_RIGHT = 32;
+
+    /**
      * The {@link Intent} that must be declared as handled by the service.
      */
     public static final String SERVICE_INTERFACE =
diff --git a/core/java/android/app/AppCompatCallbacks.java b/core/java/android/app/AppCompatCallbacks.java
index 19d158d..28a21f7 100644
--- a/core/java/android/app/AppCompatCallbacks.java
+++ b/core/java/android/app/AppCompatCallbacks.java
@@ -18,7 +18,6 @@
 
 import android.compat.Compatibility;
 import android.os.Process;
-import android.util.StatsLog;
 
 import com.android.internal.compat.ChangeReporter;
 
@@ -46,20 +45,20 @@
         mDisabledChanges = Arrays.copyOf(disabledChanges, disabledChanges.length);
         Arrays.sort(mDisabledChanges);
         mChangeReporter = new ChangeReporter(
-                StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__SOURCE__APP_PROCESS);
+                ChangeReporter.SOURCE_APP_PROCESS);
     }
 
     protected void reportChange(long changeId) {
-        reportChange(changeId, StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__LOGGED);
+        reportChange(changeId, ChangeReporter.STATE_LOGGED);
     }
 
     protected boolean isChangeEnabled(long changeId) {
         if (Arrays.binarySearch(mDisabledChanges, changeId) < 0) {
             // Not present in the disabled array
-            reportChange(changeId, StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__ENABLED);
+            reportChange(changeId, ChangeReporter.STATE_ENABLED);
             return true;
         }
-        reportChange(changeId, StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__DISABLED);
+        reportChange(changeId, ChangeReporter.STATE_DISABLED);
         return false;
     }
 
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index c09aa1f..71cb4a4 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -618,36 +618,22 @@
         return hasSystemFeature(name, 0);
     }
 
-    private boolean hasSystemFeatureUncached(String name, int version) {
-        try {
-            return mPM.hasSystemFeature(name, version);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    // Make this cache relatively large.  There are many system features and
-    // none are ever invalidated.  MPTS tests suggests that the cache should
-    // hold at least 150 entries.
-    private static final int SYS_FEATURE_CACHE_SIZE = 256;
-    private static final String CACHE_KEY_SYS_FEATURE_PROPERTY = "cache_key.has_system_feature";
-
-    private class SystemFeatureQuery {
+    private class HasSystemFeatureQuery {
         public final String name;
         public final int version;
-        public SystemFeatureQuery(String n, int v) {
+        public HasSystemFeatureQuery(String n, int v) {
             name = n;
             version = v;
         }
         @Override
         public String toString() {
-            return String.format("SystemFeatureQuery(name=\"%s\", version=%d)",
+            return String.format("HasSystemFeatureQuery(name=\"%s\", version=%d)",
                     name, version);
         }
         @Override
         public boolean equals(Object o) {
-            if (o instanceof SystemFeatureQuery) {
-                SystemFeatureQuery r = (SystemFeatureQuery) o;
+            if (o instanceof HasSystemFeatureQuery) {
+                HasSystemFeatureQuery r = (HasSystemFeatureQuery) o;
                 return Objects.equals(name, r.name) &&  version == r.version;
             } else {
                 return false;
@@ -655,33 +641,41 @@
         }
         @Override
         public int hashCode() {
-            return Objects.hashCode(name) + version;
+            return Objects.hashCode(name) * 13 + version;
         }
     }
 
-    private final PropertyInvalidatedCache<SystemFeatureQuery, Boolean> mSysFeatureCache =
-            new PropertyInvalidatedCache<SystemFeatureQuery, Boolean>(
-                SYS_FEATURE_CACHE_SIZE,
-                CACHE_KEY_SYS_FEATURE_PROPERTY) {
+    // Make this cache relatively large.  There are many system features and
+    // none are ever invalidated.  MPTS tests suggests that the cache should
+    // hold at least 150 entries.
+    private final static PropertyInvalidatedCache<HasSystemFeatureQuery, Boolean>
+            mHasSystemFeatureCache =
+            new PropertyInvalidatedCache<HasSystemFeatureQuery, Boolean>(
+                256, "cache_key.has_system_feature") {
                 @Override
-                protected Boolean recompute(SystemFeatureQuery query) {
-                    return hasSystemFeatureUncached(query.name, query.version);
+                protected Boolean recompute(HasSystemFeatureQuery query) {
+                    try {
+                        return ActivityThread.currentActivityThread().getPackageManager().
+                            hasSystemFeature(query.name, query.version);
+                    } catch (RemoteException e) {
+                        throw e.rethrowFromSystemServer();
+                    }
                 }
             };
 
     @Override
     public boolean hasSystemFeature(String name, int version) {
-        return mSysFeatureCache.query(new SystemFeatureQuery(name, version)).booleanValue();
+        return mHasSystemFeatureCache.query(new HasSystemFeatureQuery(name, version));
     }
 
     /** @hide */
-    public void disableSysFeatureCache() {
-        mSysFeatureCache.disableLocal();
+    public void disableHasSystemFeatureCache() {
+        mHasSystemFeatureCache.disableLocal();
     }
 
     /** @hide */
-    public static void invalidateSysFeatureCache() {
-        PropertyInvalidatedCache.invalidateCache(CACHE_KEY_SYS_FEATURE_PROPERTY);
+    public static void invalidateHasSystemFeatureCache() {
+        mHasSystemFeatureCache.invalidateCache();
     }
 
     @Override
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 04d3e39..57cd894 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -750,7 +750,7 @@
             if (type != null) {
                 dirs = Environment.buildPaths(dirs, type);
             }
-            return ensureExternalDirsExistOrFilter(dirs);
+            return ensureExternalDirsExistOrFilter(dirs, true /* tryCreateInProcess */);
         }
     }
 
@@ -765,7 +765,7 @@
     public File[] getObbDirs() {
         synchronized (mSync) {
             File[] dirs = Environment.buildExternalStorageAppObbDirs(getPackageName());
-            return ensureExternalDirsExistOrFilter(dirs);
+            return ensureExternalDirsExistOrFilter(dirs, true /* tryCreateInProcess */);
         }
     }
 
@@ -809,7 +809,10 @@
     public File[] getExternalCacheDirs() {
         synchronized (mSync) {
             File[] dirs = Environment.buildExternalStorageAppCacheDirs(getPackageName());
-            return ensureExternalDirsExistOrFilter(dirs);
+            // We don't try to create cache directories in-process, because they need special
+            // setup for accurate quota tracking. This ensures the cache dirs are always
+            // created through StorageManagerService.
+            return ensureExternalDirsExistOrFilter(dirs, false /* tryCreateInProcess */);
         }
     }
 
@@ -817,7 +820,7 @@
     public File[] getExternalMediaDirs() {
         synchronized (mSync) {
             File[] dirs = Environment.buildExternalStorageAppMediaDirs(getPackageName());
-            return ensureExternalDirsExistOrFilter(dirs);
+            return ensureExternalDirsExistOrFilter(dirs, true /* tryCreateInProcess */);
         }
     }
 
@@ -2804,24 +2807,24 @@
      * Ensure that given directories exist, trying to create them if missing. If
      * unable to create, they are filtered by replacing with {@code null}.
      */
-    private File[] ensureExternalDirsExistOrFilter(File[] dirs) {
+    private File[] ensureExternalDirsExistOrFilter(File[] dirs, boolean tryCreateInProcess) {
         final StorageManager sm = getSystemService(StorageManager.class);
         final File[] result = new File[dirs.length];
         for (int i = 0; i < dirs.length; i++) {
             File dir = dirs[i];
             if (!dir.exists()) {
-                if (!dir.mkdirs()) {
-                    // recheck existence in case of cross-process race
-                    if (!dir.exists()) {
-                        // Failing to mkdir() may be okay, since we might not have
-                        // enough permissions; ask vold to create on our behalf.
-                        try {
+                try {
+                    if (!tryCreateInProcess || !dir.mkdirs()) {
+                        // recheck existence in case of cross-process race
+                        if (!dir.exists()) {
+                            // Failing to mkdir() may be okay, since we might not have
+                            // enough permissions; ask vold to create on our behalf.
                             sm.mkdirs(dir);
-                        } catch (Exception e) {
-                            Log.w(TAG, "Failed to ensure " + dir + ": " + e);
-                            dir = null;
                         }
                     }
+                } catch (Exception e) {
+                    Log.w(TAG, "Failed to ensure " + dir + ": " + e);
+                    dir = null;
                 }
             }
             result[i] = dir;
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index f71d78b..3676a9b 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -328,7 +328,7 @@
      * modified by the user and the only way of resetting the device is via factory reset.
      *
      * <p>From version {@link android.os.Build.VERSION_CODES#Q}, the admin app can choose
-     * whether to set up a fully managed device or a work profile. For the admin app to support
+     * whether to set up a fully managed device or a managed profile. For the admin app to support
      * this, it must have an activity with intent filter {@link #ACTION_GET_PROVISIONING_MODE} and
      * another one with intent filter {@link #ACTION_ADMIN_POLICY_COMPLIANCE}. For example:
      * <pre>
@@ -1781,12 +1781,13 @@
     /**
      * Grants access to selection of KeyChain certificates on behalf of requesting apps.
      * Once granted the app will start receiving
-     * DelegatedAdminReceiver.onChoosePrivateKeyAlias. The caller (PO/DO) will
+     * {@link DelegatedAdminReceiver#onChoosePrivateKeyAlias}. The caller (PO/DO) will
      * no longer receive {@link DeviceAdminReceiver#onChoosePrivateKeyAlias}.
      * There can be at most one app that has this delegation.
      * If another app already had delegated certificate selection access,
      * it will lose the delegation when a new app is delegated.
-     *
+     * <p> The delegaetd app can also call {@link #grantKeyPairToApp} and
+     * {@link #revokeKeyPairFromApp} to directly grant KeyCain keys to other apps.
      * <p> Can be granted by Device Owner or Profile Owner.
      */
     public static final String DELEGATION_CERT_SELECTION = "delegation-cert-selection";
@@ -4313,7 +4314,7 @@
      * additionally call this method on the parent instance.
      * Calling this method on the parent {@link DevicePolicyManager} instance would wipe the
      * entire device, while calling it on the current profile instance would relinquish the device
-     * for personal use, removing the work profile and all policies set by the profile owner.
+     * for personal use, removing the managed profile and all policies set by the profile owner.
      *
      * @param flags Bit mask of additional options: currently supported flags are
      *            {@link #WIPE_EXTERNAL_STORAGE}, {@link #WIPE_RESET_PROTECTION_DATA},
@@ -4339,7 +4340,7 @@
      * additionally call this method on the parent instance.
      * Calling this method on the parent {@link DevicePolicyManager} instance would wipe the
      * entire device, while calling it on the current profile instance would relinquish the device
-     * for personal use, removing the work profile and all policies set by the profile owner.
+     * for personal use, removing the managed profile and all policies set by the profile owner.
      *
      * @param flags Bit mask of additional options: currently supported flags are
      *            {@link #WIPE_EXTERNAL_STORAGE}, {@link #WIPE_RESET_PROTECTION_DATA} and
@@ -11584,12 +11585,14 @@
      * #setCrossProfilePackages(ComponentName, Set)}.</li>
      * <li>The default package names set by the OEM that are allowed to request user consent for
      * cross-profile communication without being explicitly enabled by the admin, via
-     * {@link com.android.internal.R.array#cross_profile_apps}</li>
+     * {@link com.android.internal.R.array#cross_profile_apps} and
+     * {@link com.android.internal.R.array#vendor_cross_profile_apps}.</li>
      * </ul>
      *
      * @return the combined set of whitelisted package names set via
-     * {@link #setCrossProfilePackages(ComponentName, Set)} and
-     * {@link com.android.internal.R.array#cross_profile_apps}
+     * {@link #setCrossProfilePackages(ComponentName, Set)},
+     * {@link com.android.internal.R.array#cross_profile_apps},
+     * and {@link com.android.internal.R.array#vendor_cross_profile_apps}.
      *
      * @hide
      */
@@ -11599,7 +11602,7 @@
             permission.INTERACT_ACROSS_PROFILES
     })
     public @NonNull Set<String> getAllCrossProfilePackages() {
-        throwIfParentInstance("getDefaultCrossProfilePackages");
+        throwIfParentInstance("getAllCrossProfilePackages");
         if (mService != null) {
             try {
                 return new ArraySet<>(mService.getAllCrossProfilePackages());
@@ -11611,6 +11614,26 @@
     }
 
     /**
+     * Returns the default package names set by the OEM that are allowed to request user consent for
+     * cross-profile communication without being explicitly enabled by the admin, via
+     * {@link com.android.internal.R.array#cross_profile_apps} and
+     * {@link com.android.internal.R.array#vendor_cross_profile_apps}.
+     *
+     * @hide
+     */
+    public @NonNull Set<String> getDefaultCrossProfilePackages() {
+        throwIfParentInstance("getDefaultCrossProfilePackages");
+        if (mService != null) {
+            try {
+                return new ArraySet<>(mService.getDefaultCrossProfilePackages());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return Collections.emptySet();
+    }
+
+    /**
      * Returns whether the device is being used as a managed kiosk. These requirements are as
      * follows:
      * <ul>
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 7fd0ae4..d2672eb 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -457,6 +457,7 @@
     List<String> getCrossProfilePackages(in ComponentName admin);
 
     List<String> getAllCrossProfilePackages();
+    List<String> getDefaultCrossProfilePackages();
 
     boolean isManagedKiosk();
     boolean isUnattendedManagedKiosk();
diff --git a/core/java/android/app/timedetector/PhoneTimeSuggestion.java b/core/java/android/app/timedetector/PhoneTimeSuggestion.java
index bd649f8..eab8838 100644
--- a/core/java/android/app/timedetector/PhoneTimeSuggestion.java
+++ b/core/java/android/app/timedetector/PhoneTimeSuggestion.java
@@ -57,19 +57,19 @@
                 }
             };
 
-    private final int mPhoneId;
+    private final int mSlotIndex;
     @Nullable private final TimestampedValue<Long> mUtcTime;
     @Nullable private ArrayList<String> mDebugInfo;
 
     private PhoneTimeSuggestion(Builder builder) {
-        mPhoneId = builder.mPhoneId;
+        mSlotIndex = builder.mSlotIndex;
         mUtcTime = builder.mUtcTime;
         mDebugInfo = builder.mDebugInfo != null ? new ArrayList<>(builder.mDebugInfo) : null;
     }
 
     private static PhoneTimeSuggestion createFromParcel(Parcel in) {
-        int phoneId = in.readInt();
-        PhoneTimeSuggestion suggestion = new PhoneTimeSuggestion.Builder(phoneId)
+        int slotIndex = in.readInt();
+        PhoneTimeSuggestion suggestion = new PhoneTimeSuggestion.Builder(slotIndex)
                 .setUtcTime(in.readParcelable(null /* classLoader */))
                 .build();
         @SuppressWarnings("unchecked")
@@ -87,17 +87,17 @@
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeInt(mPhoneId);
+        dest.writeInt(mSlotIndex);
         dest.writeParcelable(mUtcTime, 0);
         dest.writeList(mDebugInfo);
     }
 
     /**
-     * Returns an identifier for the source of this suggestion. When a device has several "phones",
-     * i.e. sim slots or equivalent, it is used to identify which one.
+     * Returns an identifier for the source of this suggestion. When a device has several sim slots
+     * or equivalent, it is used to identify which one the suggestion is from.
      */
-    public int getPhoneId() {
-        return mPhoneId;
+    public int getSlotIndex() {
+        return mSlotIndex;
     }
 
     /**
@@ -152,19 +152,19 @@
             return false;
         }
         PhoneTimeSuggestion that = (PhoneTimeSuggestion) o;
-        return mPhoneId == that.mPhoneId
+        return mSlotIndex == that.mSlotIndex
                 && Objects.equals(mUtcTime, that.mUtcTime);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mPhoneId, mUtcTime);
+        return Objects.hash(mSlotIndex, mUtcTime);
     }
 
     @Override
     public String toString() {
         return "PhoneTimeSuggestion{"
-                + "mPhoneId='" + mPhoneId + '\''
+                + "mSlotIndex='" + mSlotIndex + '\''
                 + ", mUtcTime=" + mUtcTime
                 + ", mDebugInfo=" + mDebugInfo
                 + '}';
@@ -177,13 +177,13 @@
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final class Builder {
-        private final int mPhoneId;
+        private final int mSlotIndex;
         @Nullable private TimestampedValue<Long> mUtcTime;
         @Nullable private List<String> mDebugInfo;
 
-        /** Creates a builder with the specified {@code phoneId}. */
-        public Builder(int phoneId) {
-            mPhoneId = phoneId;
+        /** Creates a builder with the specified {@code slotIndex}. */
+        public Builder(int slotIndex) {
+            mSlotIndex = slotIndex;
         }
 
         /** Returns the builder for call chaining. */
diff --git a/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java b/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java
index d71ffcb..ebaf951 100644
--- a/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java
+++ b/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java
@@ -66,12 +66,12 @@
 
     /**
      * Creates an empty time zone suggestion, i.e. one that will cancel previous suggestions with
-     * the same {@code phoneId}.
+     * the same {@code slotIndex}.
      */
     @NonNull
     public static PhoneTimeZoneSuggestion createEmptySuggestion(
-            int phoneId, @NonNull String debugInfo) {
-        return new Builder(phoneId).addDebugInfo(debugInfo).build();
+            int slotIndex, @NonNull String debugInfo) {
+        return new Builder(slotIndex).addDebugInfo(debugInfo).build();
     }
 
     /** @hide */
@@ -135,7 +135,7 @@
      * The ID of the phone this suggestion is associated with. For multiple-sim devices this
      * helps to establish source so filtering / stickiness can be implemented.
      */
-    private final int mPhoneId;
+    private final int mSlotIndex;
 
     /**
      * The suggestion. {@code null} means there is no current suggestion and any previous suggestion
@@ -165,7 +165,7 @@
     private List<String> mDebugInfo;
 
     private PhoneTimeZoneSuggestion(Builder builder) {
-        mPhoneId = builder.mPhoneId;
+        mSlotIndex = builder.mSlotIndex;
         mZoneId = builder.mZoneId;
         mMatchType = builder.mMatchType;
         mQuality = builder.mQuality;
@@ -175,8 +175,8 @@
     @SuppressWarnings("unchecked")
     private static PhoneTimeZoneSuggestion createFromParcel(Parcel in) {
         // Use the Builder so we get validation during build().
-        int phoneId = in.readInt();
-        PhoneTimeZoneSuggestion suggestion = new Builder(phoneId)
+        int slotIndex = in.readInt();
+        PhoneTimeZoneSuggestion suggestion = new Builder(slotIndex)
                 .setZoneId(in.readString())
                 .setMatchType(in.readInt())
                 .setQuality(in.readInt())
@@ -190,7 +190,7 @@
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeInt(mPhoneId);
+        dest.writeInt(mSlotIndex);
         dest.writeString(mZoneId);
         dest.writeInt(mMatchType);
         dest.writeInt(mQuality);
@@ -203,11 +203,11 @@
     }
 
     /**
-     * Returns an identifier for the source of this suggestion. When a device has several "phones",
-     * i.e. sim slots or equivalent, it is used to identify which one.
+     * Returns an identifier for the source of this suggestion. When a device has several sim slots
+     * or equivalent, it is used to identify which one the suggestion is from.
      */
-    public int getPhoneId() {
-        return mPhoneId;
+    public int getSlotIndex() {
+        return mSlotIndex;
     }
 
     /**
@@ -282,7 +282,7 @@
             return false;
         }
         PhoneTimeZoneSuggestion that = (PhoneTimeZoneSuggestion) o;
-        return mPhoneId == that.mPhoneId
+        return mSlotIndex == that.mSlotIndex
                 && mMatchType == that.mMatchType
                 && mQuality == that.mQuality
                 && Objects.equals(mZoneId, that.mZoneId);
@@ -290,13 +290,13 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mPhoneId, mZoneId, mMatchType, mQuality);
+        return Objects.hash(mSlotIndex, mZoneId, mMatchType, mQuality);
     }
 
     @Override
     public String toString() {
         return "PhoneTimeZoneSuggestion{"
-                + "mPhoneId=" + mPhoneId
+                + "mSlotIndex=" + mSlotIndex
                 + ", mZoneId='" + mZoneId + '\''
                 + ", mMatchType=" + mMatchType
                 + ", mQuality=" + mQuality
@@ -311,14 +311,14 @@
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final class Builder {
-        private final int mPhoneId;
+        private final int mSlotIndex;
         @Nullable private String mZoneId;
         @MatchType private int mMatchType;
         @Quality private int mQuality;
         @Nullable private List<String> mDebugInfo;
 
-        public Builder(int phoneId) {
-            mPhoneId = phoneId;
+        public Builder(int slotIndex) {
+            mSlotIndex = slotIndex;
         }
 
         /**
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index a385771..7615b87c 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -91,7 +91,7 @@
     private boolean mIdle = true;
 
     /** map request IDs to callback/request data */
-    private final SparseArray<CaptureCallbackHolder> mCaptureCallbackMap =
+    private SparseArray<CaptureCallbackHolder> mCaptureCallbackMap =
             new SparseArray<CaptureCallbackHolder>();
 
     private int mRepeatingRequestId = REQUEST_ID_NONE;
@@ -123,7 +123,7 @@
      * An object tracking received frame numbers.
      * Updated when receiving callbacks from ICameraDeviceCallbacks.
      */
-    private final FrameNumberTracker mFrameNumberTracker = new FrameNumberTracker();
+    private FrameNumberTracker mFrameNumberTracker = new FrameNumberTracker();
 
     private CameraCaptureSessionCore mCurrentSession;
     private int mNextSessionId = 0;
@@ -892,6 +892,7 @@
         HashSet<Integer> offlineStreamIds = new HashSet<Integer>();
         SparseArray<OutputConfiguration> offlineConfiguredOutputs =
                 new SparseArray<OutputConfiguration>();
+        CameraOfflineSession ret;
 
         synchronized(mInterfaceLock) {
             if (mOfflineSessionImpl != null) {
@@ -919,15 +920,20 @@
 
                 offlineStreamIds.add(streamId);
             }
+            stopRepeating();
 
             mOfflineSessionImpl = new CameraOfflineSessionImpl(mCameraId,
                     mCharacteristics, executor, listener, offlineConfiguredOutputs,
-                    mConfiguredInput, mFrameNumberTracker, mCaptureCallbackMap,
+                    mConfiguredInput, mConfiguredOutputs, mFrameNumberTracker, mCaptureCallbackMap,
                     mRequestLastFrameNumbersList);
+            ret = mOfflineSessionImpl;
 
             mOfflineSwitchService = Executors.newSingleThreadExecutor();
             mConfiguredOutputs.clear();
             mConfiguredInput = new SimpleEntry<Integer, InputConfiguration>(REQUEST_ID_NONE, null);
+            mIdle = true;
+            mCaptureCallbackMap = new SparseArray<CaptureCallbackHolder>();
+            mFrameNumberTracker = new FrameNumberTracker();
 
             mCurrentSession.closeWithoutDraining();
             mCurrentSession = null;
@@ -949,11 +955,13 @@
                     mOfflineSessionImpl.setRemoteSession(remoteOfflineSession);
                 } catch (CameraAccessException e) {
                     mOfflineSessionImpl.notifyFailedSwitch();
+                } finally {
+                    mOfflineSessionImpl = null;
                 }
             }
         });
 
-        return mOfflineSessionImpl;
+        return ret;
     }
 
     public boolean supportsOfflineProcessing(Surface surface) {
diff --git a/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java
index 1db377a..1d9d644 100644
--- a/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java
@@ -63,6 +63,7 @@
     private SimpleEntry<Integer, InputConfiguration> mOfflineInput =
             new SimpleEntry<>(REQUEST_ID_NONE, null);
     private SparseArray<OutputConfiguration> mOfflineOutputs = new SparseArray<>();
+    private SparseArray<OutputConfiguration> mConfiguredOutputs = new SparseArray<>();
 
     final Object mInterfaceLock = new Object(); // access from this class and Session only!
 
@@ -96,6 +97,7 @@
             Executor offlineExecutor, CameraOfflineSessionCallback offlineCallback,
             SparseArray<OutputConfiguration> offlineOutputs,
             SimpleEntry<Integer, InputConfiguration> offlineInput,
+            SparseArray<OutputConfiguration> configuredOutputs,
             FrameNumberTracker frameNumberTracker, SparseArray<CaptureCallbackHolder> callbackMap,
             List<RequestLastFrameNumbersHolder> frameNumberList) {
         if ((cameraId == null) || (characteristics == null)) {
@@ -117,6 +119,7 @@
         mOfflineRequestLastFrameNumbersList.addAll(frameNumberList);
         mFrameNumberTracker = frameNumberTracker;
         mCaptureCallbackMap = callbackMap;
+        mConfiguredOutputs = configuredOutputs;
         mOfflineOutputs = offlineOutputs;
         mOfflineInput = offlineInput;
         mOfflineExecutor = checkNotNull(offlineExecutor, "offline executor must not be null");
@@ -137,9 +140,6 @@
         @Override
         public void onDeviceError(final int errorCode, CaptureResultExtras resultExtras) {
             synchronized(mInterfaceLock) {
-                if (mRemoteSession == null) {
-                    return; // Camera already closed
-                }
 
                 switch (errorCode) {
                     case CameraDeviceCallbacks.ERROR_CAMERA_REQUEST:
@@ -177,6 +177,11 @@
         @Override
         public void onDeviceIdle() {
             synchronized(mInterfaceLock) {
+                if (mRemoteSession == null) {
+                    Log.v(TAG, "Ignoring idle state notifications during offline switches");
+                    return;
+                }
+
                 Runnable idleDispatch = new Runnable() {
                     @Override
                     public void run() {
@@ -203,8 +208,6 @@
             final CaptureCallbackHolder holder;
 
             synchronized(mInterfaceLock) {
-                if (mRemoteSession == null) return; // Camera already closed
-
                 // Get the callback for this frame ID, if there is one
                 holder = CameraOfflineSessionImpl.this.mCaptureCallbackMap.get(requestId);
 
@@ -269,8 +272,6 @@
             long frameNumber = resultExtras.getFrameNumber();
 
             synchronized(mInterfaceLock) {
-                if (mRemoteSession == null) return; // Camera already closed
-
                 // TODO: Handle CameraCharacteristics access from CaptureResult correctly.
                 result.set(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE,
                         mCharacteristics.get(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE));
@@ -445,8 +446,12 @@
             if (errorCode == ERROR_CAMERA_BUFFER) {
                 // Because 1 stream id could map to multiple surfaces, we need to specify both
                 // streamId and surfaceId.
-                OutputConfiguration config = mOfflineOutputs.get(
-                        resultExtras.getErrorStreamId());
+                OutputConfiguration config;
+                if ((mRemoteSession == null) && !isClosed()) {
+                    config = mConfiguredOutputs.get(resultExtras.getErrorStreamId());
+                } else {
+                    config = mOfflineOutputs.get(resultExtras.getErrorStreamId());
+                }
                 if (config == null) {
                     Log.v(TAG, String.format(
                             "Stream %d has been removed. Skipping buffer lost callback",
@@ -538,11 +543,6 @@
             final Executor executor;
             final CameraCaptureSession.CaptureCallback callback;
             synchronized(mInterfaceLock) {
-                if (mRemoteSession == null) {
-                    Log.w(TAG, "Camera closed while checking sequences");
-                    return;
-                }
-
                 int index = mCaptureCallbackMap.indexOfKey(requestId);
                 holder = (index >= 0) ?
                         mCaptureCallbackMap.valueAt(index) : null;
@@ -575,7 +575,7 @@
             }
 
             // Call onCaptureSequenceCompleted
-            if ((sequenceCompleted) && (callback != null) && (executor == null)) {
+            if ((sequenceCompleted) && (callback != null) && (executor != null)) {
                 Runnable resultDispatch = new Runnable() {
                     @Override
                     public void run() {
@@ -592,7 +592,12 @@
                 } finally {
                     Binder.restoreCallingIdentity(ident);
                 }
+
+                if (mCaptureCallbackMap.size() == 0) {
+                    getCallbacks().onDeviceIdle();
+                }
             }
+
         }
     }
 
@@ -686,9 +691,7 @@
             Runnable closeDispatch = new Runnable() {
                 @Override
                 public void run() {
-                    if (!isClosed()) {
-                        mOfflineCallback.onClosed(CameraOfflineSessionImpl.this);
-                    }
+                    mOfflineCallback.onClosed(CameraOfflineSessionImpl.this);
                 }
             };
 
diff --git a/core/java/android/hardware/display/AmbientDisplayConfiguration.java b/core/java/android/hardware/display/AmbientDisplayConfiguration.java
index 3e995b6..ece5c28 100644
--- a/core/java/android/hardware/display/AmbientDisplayConfiguration.java
+++ b/core/java/android/hardware/display/AmbientDisplayConfiguration.java
@@ -194,6 +194,11 @@
         return !TextUtils.isEmpty(ambientDisplayComponent());
     }
 
+    /** {@hide} */
+    public boolean dozeSuppressed(int user) {
+        return boolSettingDefaultOff(Settings.Secure.SUPPRESS_DOZE, user);
+    }
+
     private boolean alwaysOnDisplayAvailable() {
         return mContext.getResources().getBoolean(R.bool.config_dozeAlwaysOnDisplayAvailable);
     }
diff --git a/core/java/android/net/ConnectivityDiagnosticsManager.java b/core/java/android/net/ConnectivityDiagnosticsManager.java
index b13e4b7..140363c 100644
--- a/core/java/android/net/ConnectivityDiagnosticsManager.java
+++ b/core/java/android/net/ConnectivityDiagnosticsManager.java
@@ -25,13 +25,16 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.PersistableBundle;
+import android.os.RemoteException;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Map;
 import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executor;
 
 /**
@@ -57,6 +60,11 @@
  * </ul>
  */
 public class ConnectivityDiagnosticsManager {
+    /** @hide */
+    @VisibleForTesting
+    public static final Map<ConnectivityDiagnosticsCallback, ConnectivityDiagnosticsBinder>
+            sCallbacks = new ConcurrentHashMap<>();
+
     private final Context mContext;
     private final IConnectivityManager mService;
 
@@ -631,8 +639,9 @@
     /**
      * Registers a ConnectivityDiagnosticsCallback with the System.
      *
-     * <p>Only apps that offer network connectivity to the user are allowed to register callbacks.
-     * This includes:
+     * <p>Only apps that offer network connectivity to the user should be registering callbacks.
+     * These are the only apps whose callbacks will be invoked by the system. Apps considered to
+     * meet these conditions include:
      *
      * <ul>
      *   <li>Carrier apps with active subscriptions
@@ -640,15 +649,14 @@
      *   <li>WiFi Suggesters
      * </ul>
      *
-     * <p>Callbacks will be limited to receiving notifications for networks over which apps provide
-     * connectivity.
+     * <p>Callbacks registered by apps not meeting the above criteria will not be invoked.
      *
      * <p>If a registering app loses its relevant permissions, any callbacks it registered will
      * silently stop receiving callbacks.
      *
-     * <p>Each register() call <b>MUST</b> use a unique ConnectivityDiagnosticsCallback instance. If
-     * a single instance is registered with multiple NetworkRequests, an IllegalArgumentException
-     * will be thrown.
+     * <p>Each register() call <b>MUST</b> use a ConnectivityDiagnosticsCallback instance that is
+     * not currently registered. If a ConnectivityDiagnosticsCallback instance is registered with
+     * multiple NetworkRequests, an IllegalArgumentException will be thrown.
      *
      * @param request The NetworkRequest that will be used to match with Networks for which
      *     callbacks will be fired
@@ -657,15 +665,21 @@
      *     System
      * @throws IllegalArgumentException if the same callback instance is registered with multiple
      *     NetworkRequests
-     * @throws SecurityException if the caller does not have appropriate permissions to register a
-     *     callback
      */
     public void registerConnectivityDiagnosticsCallback(
             @NonNull NetworkRequest request,
             @NonNull Executor e,
             @NonNull ConnectivityDiagnosticsCallback callback) {
-        // TODO(b/143187964): implement ConnectivityDiagnostics functionality
-        throw new UnsupportedOperationException("registerCallback() not supported yet");
+        final ConnectivityDiagnosticsBinder binder = new ConnectivityDiagnosticsBinder(callback, e);
+        if (sCallbacks.putIfAbsent(callback, binder) != null) {
+            throw new IllegalArgumentException("Callback is currently registered");
+        }
+
+        try {
+            mService.registerConnectivityDiagnosticsCallback(binder, request);
+        } catch (RemoteException exception) {
+            exception.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -678,7 +692,15 @@
      */
     public void unregisterConnectivityDiagnosticsCallback(
             @NonNull ConnectivityDiagnosticsCallback callback) {
-        // TODO(b/143187964): implement ConnectivityDiagnostics functionality
-        throw new UnsupportedOperationException("registerCallback() not supported yet");
+        // unconditionally removing from sCallbacks prevents race conditions here, since remove() is
+        // atomic.
+        final ConnectivityDiagnosticsBinder binder = sCallbacks.remove(callback);
+        if (binder == null) return;
+
+        try {
+            mService.unregisterConnectivityDiagnosticsCallback(binder);
+        } catch (RemoteException exception) {
+            exception.rethrowFromSystemServer();
+        }
     }
 }
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 3ea64f1..bb1dafc 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -2309,6 +2309,121 @@
     private static final String XATTR_CACHE_GROUP = "user.cache_group";
     private static final String XATTR_CACHE_TOMBSTONE = "user.cache_tombstone";
 
+
+   // Matches AID_MEDIA_RW in android_filesystem_config.h
+    private static final int QUOTA_PROJECT_ID_MEDIA_NONE = 1023;
+
+    // Matches AID_MEDIA_IMAGE in android_filesystem_config.h
+    private static final int QUOTA_PROJECT_ID_MEDIA_IMAGE = 1057;
+
+    // Matches AID_MEDIA_AUDIO in android_filesystem_config.h
+    private static final int QUOTA_PROJECT_ID_MEDIA_AUDIO = 1055;
+
+    // Matches AID_MEDIA_VIDEO in android_filesystem_config.h
+    private static final int QUOTA_PROJECT_ID_MEDIA_VIDEO = 1056;
+
+    /**
+     * Constant for use with
+     * {@link #updateExternalStorageFileQuotaType(String, int)} (String, int)}, to indicate the file
+     * is not a media file.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int QUOTA_TYPE_MEDIA_NONE = 0;
+
+    /**
+     * Constant for use with
+     * {@link #updateExternalStorageFileQuotaType(String, int)} (String, int)}, to indicate the file
+     * is an image file.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int QUOTA_TYPE_MEDIA_IMAGE = 1;
+
+    /**
+     * Constant for use with
+     * {@link #updateExternalStorageFileQuotaType(String, int)} (String, int)}, to indicate the file
+     * is an audio file.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int QUOTA_TYPE_MEDIA_AUDIO = 2;
+
+    /**
+     * Constant for use with
+     * {@link #updateExternalStorageFileQuotaType(String, int)} (String, int)}, to indicate the file
+     * is a video file.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int QUOTA_TYPE_MEDIA_VIDEO = 3;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "QUOTA_TYPE_" }, value = {
+            QUOTA_TYPE_MEDIA_NONE,
+            QUOTA_TYPE_MEDIA_AUDIO,
+            QUOTA_TYPE_MEDIA_VIDEO,
+            QUOTA_TYPE_MEDIA_IMAGE,
+    })
+    public @interface QuotaType {}
+
+    private static native boolean setQuotaProjectId(String path, long projectId);
+
+    /**
+     * Let StorageManager know that the quota type for a file on external storage should
+     * be updated. Android tracks quotas for various media types. Consequently, this should be
+     * called on first creation of a new file on external storage, and whenever the
+     * media type of the file is updated later.
+     *
+     * This API doesn't require any special permissions, though typical implementations
+     * will require being called from an SELinux domain that allows setting file attributes
+     * related to quota (eg the GID or project ID).
+     *
+     * The default platform user of this API is the MediaProvider process, which is
+     * responsible for managing all of external storage.
+     *
+     * @param path the path to the file for which we should update the quota type
+     * @param quotaType the quota type of the file; this is based on the
+     *                  {@code QuotaType} constants, eg
+     *                  {@code StorageManager.QUOTA_TYPE_MEDIA_AUDIO}
+     *
+     * @throws IllegalArgumentException if {@code quotaType} does not correspond to a valid
+     *                                  quota type.
+     * @throws IOException              if the quota type could not be updated.
+     *
+     * @hide
+     */
+    @SystemApi
+    public void updateExternalStorageFileQuotaType(@NonNull File path,
+            @QuotaType int quotaType) throws IOException {
+        long projectId;
+        final String filePath = path.getCanonicalPath();
+        switch (quotaType) {
+            case QUOTA_TYPE_MEDIA_NONE:
+                projectId = QUOTA_PROJECT_ID_MEDIA_NONE;
+                break;
+            case QUOTA_TYPE_MEDIA_AUDIO:
+                projectId = QUOTA_PROJECT_ID_MEDIA_AUDIO;
+                break;
+            case QUOTA_TYPE_MEDIA_VIDEO:
+                projectId = QUOTA_PROJECT_ID_MEDIA_VIDEO;
+                break;
+            case QUOTA_TYPE_MEDIA_IMAGE:
+                projectId = QUOTA_PROJECT_ID_MEDIA_IMAGE;
+                break;
+            default:
+                throw new IllegalArgumentException("Invalid quota type: " + quotaType);
+        }
+        if (!setQuotaProjectId(filePath, projectId)) {
+            throw new IOException("Failed to update quota type for " + filePath);
+        }
+    }
+
     /** {@hide} */
     private static void setCacheBehavior(File path, String name, boolean enabled)
             throws IOException {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 605c585..b62142c 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2417,6 +2417,10 @@
     public static class NameValueTable implements BaseColumns {
         public static final String NAME = "name";
         public static final String VALUE = "value";
+        // A flag indicating whether the current value of a setting should be preserved during
+        // restore.
+        /** @hide */
+        public static final String IS_PRESERVED_IN_RESTORE = "is_preserved_in_restore";
 
         protected static boolean putString(ContentResolver resolver, Uri uri,
                 String name, String value) {
diff --git a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
index 6334d9d..368c94c 100644
--- a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
+++ b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
@@ -31,6 +31,7 @@
 import android.content.Intent;
 import android.graphics.Rect;
 import android.os.Build;
+import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.IBinder;
@@ -39,6 +40,7 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.service.autofill.Dataset;
+import android.service.autofill.FillEventHistory;
 import android.service.autofill.augmented.PresentationParams.SystemPopupPresentationParams;
 import android.util.Log;
 import android.util.Pair;
@@ -319,6 +321,31 @@
         pw.print(getClass().getName()); pw.println(": nothing to dump");
     }
 
+    /**
+     * Gets the inline augmented autofill events that happened after the last
+     * {@link #onFillRequest(FillRequest, CancellationSignal, FillController, FillCallback)} call.
+     *
+     * <p>The history is not persisted over reboots, and it's cleared every time the service
+     * replies to a
+     * {@link #onFillRequest(FillRequest, CancellationSignal, FillController, FillCallback)}
+     * by calling {@link FillCallback#onSuccess(FillResponse)}. Hence, the service should call
+     * {@link #getFillEventHistory() before finishing the {@link FillCallback}.
+     *
+     * <p>Also note that the events from the dropdown suggestion UI is not stored in the history
+     * since the service owns the UI.
+     *
+     * @return The history or {@code null} if there are no events.
+     */
+    @Nullable public final FillEventHistory getFillEventHistory() {
+        final AutofillManager afm = getSystemService(AutofillManager.class);
+
+        if (afm == null) {
+            return null;
+        } else {
+            return afm.getFillEventHistory();
+        }
+    }
+
     /** @hide */
     static final class AutofillProxy {
 
@@ -487,9 +514,10 @@
             }
         }
 
-        public void onInlineSuggestionsDataReady(@NonNull List<Dataset> inlineSuggestionsData) {
+        public void onInlineSuggestionsDataReady(@NonNull List<Dataset> inlineSuggestionsData,
+                @Nullable Bundle clientState) {
             try {
-                mCallback.onSuccess(inlineSuggestionsData.toArray(new Dataset[]{}));
+                mCallback.onSuccess(inlineSuggestionsData.toArray(new Dataset[]{}), clientState);
             } catch (RemoteException e) {
                 Log.e(TAG, "Error calling back with the inline suggestions data: " + e);
             }
@@ -511,7 +539,8 @@
                         }
                     }
                     try {
-                        mCallback.onSuccess(/* mInlineSuggestionsData= */null);
+                        mCallback.onSuccess(/* inlineSuggestionsData= */null, /* clientState=*/
+                                null);
                     } catch (RemoteException e) {
                         Log.e(TAG, "Error reporting success: " + e);
                     }
diff --git a/core/java/android/service/autofill/augmented/FillCallback.java b/core/java/android/service/autofill/augmented/FillCallback.java
index b767799..d0ffd7b 100644
--- a/core/java/android/service/autofill/augmented/FillCallback.java
+++ b/core/java/android/service/autofill/augmented/FillCallback.java
@@ -60,7 +60,7 @@
 
         List<Dataset> inlineSuggestions = response.getInlineSuggestions();
         if (inlineSuggestions != null && !inlineSuggestions.isEmpty()) {
-            mProxy.onInlineSuggestionsDataReady(inlineSuggestions);
+            mProxy.onInlineSuggestionsDataReady(inlineSuggestions, response.getClientState());
             return;
         }
 
diff --git a/core/java/android/service/autofill/augmented/FillResponse.java b/core/java/android/service/autofill/augmented/FillResponse.java
index e8e6ff0..68ba63a 100644
--- a/core/java/android/service/autofill/augmented/FillResponse.java
+++ b/core/java/android/service/autofill/augmented/FillResponse.java
@@ -19,6 +19,7 @@
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
+import android.os.Bundle;
 import android.service.autofill.Dataset;
 
 import com.android.internal.util.DataClass;
@@ -50,6 +51,13 @@
     @DataClass.PluralOf("inlineSuggestion")
     private @Nullable List<Dataset> mInlineSuggestions;
 
+    /**
+     * The client state that {@link AugmentedAutofillService} implementation can put anything in to
+     * identify the request and the response when calling
+     * {@link AugmentedAutofillService#getFillEventHistory()}.
+     */
+    private @Nullable Bundle mClientState;
+
     private static FillWindow defaultFillWindow() {
         return null;
     }
@@ -58,6 +66,10 @@
         return null;
     }
 
+    private static Bundle defaultClientState() {
+        return null;
+    }
+
 
     /** @hide */
     abstract static class BaseBuilder {
@@ -82,9 +94,11 @@
     @DataClass.Generated.Member
     /* package-private */ FillResponse(
             @Nullable FillWindow fillWindow,
-            @Nullable List<Dataset> inlineSuggestions) {
+            @Nullable List<Dataset> inlineSuggestions,
+            @Nullable Bundle clientState) {
         this.mFillWindow = fillWindow;
         this.mInlineSuggestions = inlineSuggestions;
+        this.mClientState = clientState;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -111,6 +125,18 @@
     }
 
     /**
+     * The client state that {@link AugmentedAutofillService} implementation can put anything in to
+     * identify the request and the response when calling
+     * {@link AugmentedAutofillService#getFillEventHistory()}.
+     *
+     * @hide
+     */
+    @DataClass.Generated.Member
+    public @Nullable Bundle getClientState() {
+        return mClientState;
+    }
+
+    /**
      * A builder for {@link FillResponse}
      */
     @SuppressWarnings("WeakerAccess")
@@ -119,6 +145,7 @@
 
         private @Nullable FillWindow mFillWindow;
         private @Nullable List<Dataset> mInlineSuggestions;
+        private @Nullable Bundle mClientState;
 
         private long mBuilderFieldsSet = 0L;
 
@@ -157,10 +184,23 @@
             return this;
         }
 
+        /**
+         * The client state that {@link AugmentedAutofillService} implementation can put anything in to
+         * identify the request and the response when calling
+         * {@link AugmentedAutofillService#getFillEventHistory()}.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setClientState(@Nullable Bundle value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x4;
+            mClientState = value;
+            return this;
+        }
+
         /** Builds the instance. This builder should not be touched after calling this! */
         public @NonNull FillResponse build() {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x4; // Mark builder used
+            mBuilderFieldsSet |= 0x8; // Mark builder used
 
             if ((mBuilderFieldsSet & 0x1) == 0) {
                 mFillWindow = defaultFillWindow();
@@ -168,14 +208,18 @@
             if ((mBuilderFieldsSet & 0x2) == 0) {
                 mInlineSuggestions = defaultInlineSuggestions();
             }
+            if ((mBuilderFieldsSet & 0x4) == 0) {
+                mClientState = defaultClientState();
+            }
             FillResponse o = new FillResponse(
                     mFillWindow,
-                    mInlineSuggestions);
+                    mInlineSuggestions,
+                    mClientState);
             return o;
         }
 
         private void checkNotUsed() {
-            if ((mBuilderFieldsSet & 0x4) != 0) {
+            if ((mBuilderFieldsSet & 0x8) != 0) {
                 throw new IllegalStateException(
                         "This Builder should not be reused. Use a new Builder instance instead");
             }
@@ -183,10 +227,10 @@
     }
 
     @DataClass.Generated(
-            time = 1577476012370L,
+            time = 1580335256422L,
             codegenVersion = "1.0.14",
             sourceFile = "frameworks/base/core/java/android/service/autofill/augmented/FillResponse.java",
-            inputSignatures = "private @android.annotation.Nullable android.service.autofill.augmented.FillWindow mFillWindow\nprivate @com.android.internal.util.DataClass.PluralOf(\"inlineSuggestion\") @android.annotation.Nullable java.util.List<android.service.autofill.Dataset> mInlineSuggestions\nprivate static  android.service.autofill.augmented.FillWindow defaultFillWindow()\nprivate static  java.util.List<android.service.autofill.Dataset> defaultInlineSuggestions()\nclass FillResponse extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genBuilder=true, genHiddenGetters=true)\nabstract  android.service.autofill.augmented.FillResponse.Builder addInlineSuggestion(android.service.autofill.Dataset)\nclass BaseBuilder extends java.lang.Object implements []")
+            inputSignatures = "private @android.annotation.Nullable android.service.autofill.augmented.FillWindow mFillWindow\nprivate @com.android.internal.util.DataClass.PluralOf(\"inlineSuggestion\") @android.annotation.Nullable java.util.List<android.service.autofill.Dataset> mInlineSuggestions\nprivate @android.annotation.Nullable android.os.Bundle mClientState\nprivate static  android.service.autofill.augmented.FillWindow defaultFillWindow()\nprivate static  java.util.List<android.service.autofill.Dataset> defaultInlineSuggestions()\nprivate static  android.os.Bundle defaultClientState()\nclass FillResponse extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genBuilder=true, genHiddenGetters=true)\nabstract  android.service.autofill.augmented.FillResponse.Builder addInlineSuggestion(android.service.autofill.Dataset)\nclass BaseBuilder extends java.lang.Object implements []")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/service/autofill/augmented/IFillCallback.aidl b/core/java/android/service/autofill/augmented/IFillCallback.aidl
index 3ccb311..31e77f35 100644
--- a/core/java/android/service/autofill/augmented/IFillCallback.aidl
+++ b/core/java/android/service/autofill/augmented/IFillCallback.aidl
@@ -16,6 +16,7 @@
 
 package android.service.autofill.augmented;
 
+import android.os.Bundle;
 import android.os.ICancellationSignal;
 
 import android.service.autofill.Dataset;
@@ -27,7 +28,7 @@
  */
 interface IFillCallback {
     void onCancellable(in ICancellationSignal cancellation);
-    void onSuccess(in @nullable Dataset[] mInlineSuggestionsData);
+    void onSuccess(in @nullable Dataset[] inlineSuggestionsData, in @nullable Bundle clientState);
     boolean isCompleted();
     void cancel();
 }
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 06fccaf8..5fdac81 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -47,6 +47,9 @@
     /** @hide */
     public static final String BACKUP_NO_KV_DATA_CHANGE_CALLS =
             "backup_enable_no_data_notification_calls";
+    /** @hide */
+    public static final String SETTINGS_DO_NOT_RESTORE_PRESERVED =
+            "settings_do_not_restore_preserved";
 
     private static final Map<String, String> DEFAULT_FLAGS;
 
@@ -68,6 +71,11 @@
 
         // Disabled until backup transports support it.
         DEFAULT_FLAGS.put(BACKUP_NO_KV_DATA_CHANGE_CALLS, "false");
+        // Disabled by default until b/148278926 is resolved. This flags guards a feature
+        // introduced in R and will be removed in the next release (b/148367230).
+        DEFAULT_FLAGS.put(SETTINGS_DO_NOT_RESTORE_PRESERVED, "false");
+
+        DEFAULT_FLAGS.put("settings_tether_all_in_one", "false");
     }
 
     /**
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index dad7696..0653b06 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -16,8 +16,6 @@
 
 package android.view;
 
-import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_HIDDEN;
-import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
 import static android.view.InsetsState.ISIDE_BOTTOM;
 import static android.view.InsetsState.ISIDE_FLOATING;
 import static android.view.InsetsState.ISIDE_LEFT;
@@ -188,7 +186,7 @@
 
     @Override
     public void finish(boolean shown) {
-        if (mCancelled) {
+        if (mCancelled || mFinished) {
             return;
         }
         setInsetsAndAlpha(shown ? mShownInsets : mHiddenInsets, 1f /* alpha */, 1f /* fraction */);
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 2785d21..22d6f37 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -70,7 +70,7 @@
     private static final int ANIMATION_DURATION_HIDE_MS = 340;
     private static final int PENDING_CONTROL_TIMEOUT_MS = 2000;
 
-    static final Interpolator INTERPOLATOR = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
+    public static final Interpolator INTERPOLATOR = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
 
     /**
      * Layout mode during insets animation: The views should be laid out as if the changing inset
@@ -78,7 +78,7 @@
      * be called as if the changing insets types are shown, which will result in the views being
      * laid out as if the insets are fully shown.
      */
-    static final int LAYOUT_INSETS_DURING_ANIMATION_SHOWN = 0;
+    public static final int LAYOUT_INSETS_DURING_ANIMATION_SHOWN = 0;
 
     /**
      * Layout mode during insets animation: The views should be laid out as if the changing inset
@@ -86,7 +86,7 @@
      * be called as if the changing insets types are hidden, which will result in the views being
      * laid out as if the insets are fully hidden.
      */
-    static final int LAYOUT_INSETS_DURING_ANIMATION_HIDDEN = 1;
+    public static final int LAYOUT_INSETS_DURING_ANIMATION_HIDDEN = 1;
 
     /**
      * Determines the behavior of how the views should be laid out during an insets animation that
@@ -149,7 +149,7 @@
         @Override
         public void set(WindowInsetsAnimationController controller, Insets value) {
             controller.setInsetsAndAlpha(
-                    value, 1f /* alpha */, (((DefaultAnimationControlListener)
+                    value, 1f /* alpha */, (((InternalAnimationControlListener)
                             ((InsetsAnimationControlImpl) controller).getListener())
                                     .getRawFraction()));
         }
@@ -166,7 +166,7 @@
         private ObjectAnimator mAnimator;
         protected boolean mShow;
 
-        InternalAnimationControlListener(boolean show) {
+        public InternalAnimationControlListener(boolean show) {
             mShow = show;
         }
 
@@ -214,7 +214,10 @@
             return (float) mAnimator.getCurrentPlayTime() / mAnimator.getDuration();
         }
 
-        protected long getDurationMs() {
+        /**
+         * To get the animation duration in MS.
+         */
+        public long getDurationMs() {
             if (mAnimator != null) {
                 return mAnimator.getDuration();
             }
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index 77bc25b..d4961ea 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -193,6 +193,15 @@
         dest.writeBoolean(mVisible);
     }
 
+    @Override
+    public String toString() {
+        return "InsetsSource: {"
+                + "mType=" + InsetsState.typeToString(mType)
+                + ", mFrame=" + mFrame.toShortString()
+                + ", mVisible" + mVisible
+                + "}";
+    }
+
     public static final @android.annotation.NonNull Creator<InsetsSource> CREATOR = new Creator<InsetsSource>() {
 
         public InsetsSource createFromParcel(Parcel in) {
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index bd19799..8648682 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -21,7 +21,6 @@
 import static android.view.ViewRootImpl.NEW_INSETS_MODE_IME;
 import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE;
 import static android.view.ViewRootImpl.sNewInsetsMode;
-import static android.view.WindowInsets.Type.IME;
 import static android.view.WindowInsets.Type.MANDATORY_SYSTEM_GESTURES;
 import static android.view.WindowInsets.Type.SIZE;
 import static android.view.WindowInsets.Type.SYSTEM_GESTURES;
@@ -43,7 +42,6 @@
 import android.util.SparseIntArray;
 import android.view.WindowInsets.Type;
 import android.view.WindowInsets.Type.InsetsType;
-import android.view.WindowManager.LayoutParams;
 import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
 
 import java.io.PrintWriter;
@@ -366,7 +364,12 @@
         return result;
     }
 
-    static @Type.InsetsType int toPublicType(@InternalInsetsType int type) {
+    /**
+     * Converting a internal type to the public type.
+     * @param type internal insets type, {@code InternalInsetsType}.
+     * @return public insets type, {@code Type.InsetsType}.
+     */
+    public static @Type.InsetsType int toPublicType(@InternalInsetsType int type) {
         switch (type) {
             case ITYPE_STATUS_BAR:
                 return Type.STATUS_BARS;
@@ -510,5 +513,13 @@
             mSources.put(source.getType(), source);
         }
     }
+
+    @Override
+    public String toString() {
+        return "InsetsState: {"
+                + "mDisplayFrame=" + mDisplayFrame
+                + ", mSources=" + mSources
+                + "}";
+    }
 }
 
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 051534c..bbb7513 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -359,7 +359,7 @@
                 mMultiProfilePagerAdapter.getPersonalListAdapter());
         mPersonalPackageMonitor.register(
                 this, getMainLooper(), getPersonalProfileUserHandle(), false);
-        if (hasWorkProfile()) {
+        if (hasWorkProfile() && ENABLE_TABBED_VIEW) {
             mWorkPackageMonitor = createPackageMonitor(
                     mMultiProfilePagerAdapter.getWorkListAdapter());
             mWorkPackageMonitor.register(this, getMainLooper(), getWorkProfileUserHandle(), false);
@@ -725,7 +725,7 @@
         if (!mRegistered) {
             mPersonalPackageMonitor.register(this, getMainLooper(),
                     getPersonalProfileUserHandle(), false);
-            if (hasWorkProfile()) {
+            if (hasWorkProfile() && ENABLE_TABBED_VIEW) {
                 if (mWorkPackageMonitor == null) {
                     mWorkPackageMonitor = createPackageMonitor(
                             mMultiProfilePagerAdapter.getWorkListAdapter());
@@ -1155,7 +1155,9 @@
     }
 
     private void safelyStartActivityInternal(TargetInfo cti) {
-        mPersonalPackageMonitor.unregister();
+        if (mPersonalPackageMonitor != null) {
+            mPersonalPackageMonitor.unregister();
+        }
         if (mWorkPackageMonitor != null) {
             mWorkPackageMonitor.unregister();
         }
diff --git a/core/java/com/android/internal/compat/ChangeReporter.java b/core/java/com/android/internal/compat/ChangeReporter.java
index e0eb9af..5e886a6 100644
--- a/core/java/com/android/internal/compat/ChangeReporter.java
+++ b/core/java/com/android/internal/compat/ChangeReporter.java
@@ -16,13 +16,16 @@
 
 package com.android.internal.compat;
 
+import android.annotation.IntDef;
 import android.util.Log;
 import android.util.Slog;
-import android.util.StatsLog;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
@@ -42,7 +45,7 @@
         long mChangeId;
         int mState;
 
-        ChangeReport(long changeId, int state) {
+        ChangeReport(long changeId, @State int state) {
             mChangeId = changeId;
             mState = state;
         }
@@ -69,7 +72,7 @@
     // When true will of every time to debug (logcat).
     private boolean mDebugLogAll;
 
-    public ChangeReporter(int source) {
+    public ChangeReporter(@Source int source) {
         mSource = source;
         mReportedChanges =  new HashMap<>();
         mDebugLogAll = false;
@@ -85,8 +88,8 @@
      */
     public void reportChange(int uid, long changeId, int state) {
         if (shouldWriteToStatsLog(uid, changeId, state)) {
-            StatsLog.write(StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED, uid, changeId,
-                    state, mSource);
+            FrameworkStatsLog.write(FrameworkStatsLog.APP_COMPATIBILITY_CHANGE_REPORTED, uid,
+                    changeId, state, mSource);
         }
         if (shouldWriteToDebug(uid, changeId, state)) {
             debugLog(uid, changeId, state);
@@ -110,7 +113,7 @@
 
 
     /**
-     * Returns whether the next report should be logged to statsLog.
+     * Returns whether the next report should be logged to FrameworkStatsLog.
      *
      * @param uid      affected by the change
      * @param changeId the reported change id
@@ -174,7 +177,7 @@
     private void debugLog(int uid, long changeId, int state) {
         String message = String.format("Compat change id reported: %d; UID %d; state: %s", changeId,
                 uid, stateToString(state));
-        if (mSource == StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__SOURCE__SYSTEM_SERVER) {
+        if (mSource == SOURCE_SYSTEM_SERVER) {
             Slog.d(TAG, message);
         } else {
             Log.d(TAG, message);
@@ -183,21 +186,56 @@
     }
 
     /**
-     * Transforms StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE enum to a string.
+     * Transforms {@link #ChangeReporter.State} enum to a string.
      *
      * @param state to transform
      * @return a string representing the state
      */
-    private static String stateToString(int state) {
+    private static String stateToString(@State int state) {
         switch (state) {
-            case StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__LOGGED:
+            case STATE_LOGGED:
                 return "LOGGED";
-            case StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__ENABLED:
+            case STATE_ENABLED:
                 return "ENABLED";
-            case StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__DISABLED:
+            case STATE_DISABLED:
                 return "DISABLED";
             default:
                 return "UNKNOWN";
         }
     }
+
+    /** These values should be kept in sync with those in atoms.proto */
+    public static final int STATE_UNKNOWN_STATE =
+                    FrameworkStatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__UNKNOWN_STATE;
+    public static final int STATE_ENABLED =
+                    FrameworkStatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__ENABLED;
+    public static final int STATE_DISABLED =
+                    FrameworkStatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__DISABLED;
+    public static final int STATE_LOGGED =
+                    FrameworkStatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__LOGGED;
+    public static final int SOURCE_UNKNOWN_SOURCE =
+                    FrameworkStatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__SOURCE__UNKNOWN_SOURCE;
+    public static final int SOURCE_APP_PROCESS =
+                    FrameworkStatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__SOURCE__APP_PROCESS;
+    public static final int SOURCE_SYSTEM_SERVER =
+                    FrameworkStatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__SOURCE__SYSTEM_SERVER;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = { "STATE_" }, value = {
+            STATE_UNKNOWN_STATE,
+            STATE_ENABLED,
+            STATE_DISABLED,
+            STATE_LOGGED
+    })
+    public @interface State {
+    }
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = { "SOURCE_" }, value = {
+            SOURCE_UNKNOWN_SOURCE,
+            SOURCE_APP_PROCESS,
+            SOURCE_SYSTEM_SERVER
+    })
+    public @interface Source {
+    }
 }
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index c04efa3..cec68df 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -137,6 +137,7 @@
                 "android_os_Parcel.cpp",
                 "android_os_SELinux.cpp",
                 "android_os_SharedMemory.cpp",
+                "android_os_storage_StorageManager.cpp",
                 "android_os_Trace.cpp",
                 "android_os_UEventObserver.cpp",
                 "android_os_VintfObject.cpp",
@@ -268,6 +269,7 @@
                 "libnativewindow",
                 "libdl",
                 "libdl_android",
+                "libstats_jni",
                 "libstatslog",
                 "server_configurable_flags",
             ],
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 924cd81..481be24 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -108,6 +108,7 @@
 extern int register_android_app_admin_SecurityLog(JNIEnv* env);
 extern int register_android_content_AssetManager(JNIEnv* env);
 extern int register_android_util_EventLog(JNIEnv* env);
+extern int register_android_util_StatsLog(JNIEnv* env);
 extern int register_android_util_StatsLogInternal(JNIEnv* env);
 extern int register_android_util_Log(JNIEnv* env);
 extern int register_android_util_MemoryIntArray(JNIEnv* env);
@@ -143,6 +144,7 @@
 extern int register_android_os_SELinux(JNIEnv* env);
 extern int register_android_os_VintfObject(JNIEnv *env);
 extern int register_android_os_VintfRuntimeInfo(JNIEnv *env);
+extern int register_android_os_storage_StorageManager(JNIEnv* env);
 extern int register_android_os_SystemProperties(JNIEnv *env);
 extern int register_android_os_SystemClock(JNIEnv* env);
 extern int register_android_os_Trace(JNIEnv* env);
@@ -1445,6 +1447,7 @@
     REG_JNI(register_android_util_EventLog),
     REG_JNI(register_android_util_Log),
     REG_JNI(register_android_util_MemoryIntArray),
+    REG_JNI(register_android_util_StatsLog),
     REG_JNI(register_android_util_StatsLogInternal),
     REG_JNI(register_android_app_admin_SecurityLog),
     REG_JNI(register_android_content_AssetManager),
@@ -1466,6 +1469,7 @@
     REG_JNI(register_android_os_HwParcel),
     REG_JNI(register_android_os_HwRemoteBinder),
     REG_JNI(register_android_os_NativeHandle),
+    REG_JNI(register_android_os_storage_StorageManager),
     REG_JNI(register_android_os_VintfObject),
     REG_JNI(register_android_os_VintfRuntimeInfo),
     REG_JNI(register_android_service_DataLoaderService),
diff --git a/core/jni/android_os_storage_StorageManager.cpp b/core/jni/android_os_storage_StorageManager.cpp
new file mode 100644
index 0000000..aee6733
--- /dev/null
+++ b/core/jni/android_os_storage_StorageManager.cpp
@@ -0,0 +1,77 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "StorageManager"
+#include <android-base/logging.h>
+#include <android-base/unique_fd.h>
+#include <fcntl.h>
+#include <linux/fs.h>
+
+#include <nativehelper/JNIHelp.h>
+#include "core_jni_helpers.h"
+
+namespace android {
+
+jboolean android_os_storage_StorageManager_setQuotaProjectId(JNIEnv* env, jobject self,
+                                                             jstring path, jlong projectId) {
+    struct fsxattr fsx;
+    ScopedUtfChars utf_chars_path(env, path);
+
+    if (projectId > UINT32_MAX) {
+        LOG(ERROR) << "Invalid project id: " << projectId;
+        return JNI_FALSE;
+    }
+
+    android::base::unique_fd fd(
+            TEMP_FAILURE_RETRY(open(utf_chars_path.c_str(), O_RDONLY | O_CLOEXEC)));
+    if (fd == -1) {
+        PLOG(ERROR) << "Failed to open " << utf_chars_path.c_str() << " to set project id.";
+        return JNI_FALSE;
+    }
+
+    int ret = ioctl(fd, FS_IOC_FSGETXATTR, &fsx);
+    if (ret == -1) {
+        PLOG(ERROR) << "Failed to get extended attributes for " << utf_chars_path.c_str()
+                    << " to get project id.";
+        return JNI_FALSE;
+    }
+
+    fsx.fsx_projid = projectId;
+    ret = ioctl(fd, FS_IOC_FSSETXATTR, &fsx);
+    if (ret == -1) {
+        PLOG(ERROR) << "Failed to set extended attributes for " << utf_chars_path.c_str()
+                    << " to set project id.";
+        return JNI_FALSE;
+    }
+
+    return JNI_TRUE;
+}
+
+// ----------------------------------------------------------------------------
+
+static const JNINativeMethod gStorageManagerMethods[] = {
+        {"setQuotaProjectId", "(Ljava/lang/String;J)Z",
+         (void*)android_os_storage_StorageManager_setQuotaProjectId},
+};
+
+const char* const kStorageManagerPathName = "android/os/storage/StorageManager";
+
+int register_android_os_storage_StorageManager(JNIEnv* env) {
+    return RegisterMethodsOrDie(env, kStorageManagerPathName, gStorageManagerMethods,
+                                NELEM(gStorageManagerMethods));
+}
+
+}; // namespace android
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index c0743e5..7c8f62c 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -150,9 +150,9 @@
     // Will be removed soon.
     optional PinnedStackControllerProto pinned_stack_controller = 5 [deprecated=true];
     /* non app windows */
-    repeated WindowTokenProto above_app_windows = 6;
-    repeated WindowTokenProto below_app_windows = 7;
-    repeated WindowTokenProto ime_windows = 8;
+    repeated WindowTokenProto above_app_windows = 6 [deprecated=true];
+    repeated WindowTokenProto below_app_windows = 7 [deprecated=true];
+    repeated WindowTokenProto ime_windows = 8 [deprecated=true];
     optional int32 dpi = 9;
     optional .android.view.DisplayInfoProto display_info = 10;
     optional int32 rotation = 11;
@@ -165,8 +165,33 @@
     repeated IdentifierProto closing_apps = 18;
     repeated IdentifierProto changing_apps = 19;
     repeated WindowTokenProto overlay_windows = 20;
+    optional DisplayAreaProto root_display_area = 21;
 }
 
+/* represents DisplayArea object */
+message DisplayAreaProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    optional WindowContainerProto window_container = 1;
+    optional string name = 2 [ (.android.privacy).dest = DEST_EXPLICIT ];
+    repeated DisplayAreaChildProto children = 3;
+}
+
+/* represents a generic child of a DisplayArea */
+message DisplayAreaChildProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    /* At most one of the following should be present: */
+
+    /* represents a DisplayArea child */
+    optional DisplayAreaProto display_area = 1;
+    /* represents a WindowToken child */
+    optional WindowTokenProto window = 2;
+    /* represents an unknown child - the class name is recorded */
+    repeated string unknown = 3;
+}
+
+
 /* represents DisplayFrames */
 message DisplayFramesProto {
     option (.android.msg_privacy).dest = DEST_AUTOMATIC;
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index cfed805..059bc44 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1781,8 +1781,11 @@
         <attr name="crossProfile" format="boolean" />
     </declare-styleable>
 
-    <!-- The <code>feature</code> tag declares a feature. A feature is a part of an app. E.g.
-    photo sharing app might include a direct messaging component.
+    <!-- The <code>feature</code> tag declares a feature. A feature is a logical part of an app.
+    E.g. photo sharing app might include a direct messaging component. To tag certain code as
+    belonging to a feature, use a context created via
+    {@link android.content.Context#createFeatureContext(String)} for any interaction with the
+    system.
 
     <p>This appears as a child tag of the root {@link #AndroidManifest manifest} tag.
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 21128e3..21d1d3c 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1266,6 +1266,7 @@
   <java-symbol type="array" name="vendor_disallowed_apps_managed_profile" />
   <java-symbol type="array" name="vendor_disallowed_apps_managed_device" />
   <java-symbol type="array" name="cross_profile_apps" />
+  <java-symbol type="array" name="vendor_cross_profile_apps" />
 
   <java-symbol type="drawable" name="default_wallpaper" />
   <java-symbol type="drawable" name="default_lock_wallpaper" />
diff --git a/core/res/res/values/vendor_cross_profile_apps.xml b/core/res/res/values/vendor_cross_profile_apps.xml
new file mode 100644
index 0000000..32839cd
--- /dev/null
+++ b/core/res/res/values/vendor_cross_profile_apps.xml
@@ -0,0 +1,24 @@
+<?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.
+  -->
+<resources>
+    <!--
+    A collection of apps that have been pre-approved for cross-profile communication.
+    These will not require admin consent, but will still require user consent during provisioning.
+    -->
+    <string-array translatable="false" name="vendor_cross_profile_apps">
+    </string-array>
+</resources>
diff --git a/core/tests/PlatformCompatFramework/src/com/android/internal/compat/ChangeReporterTest.java b/core/tests/PlatformCompatFramework/src/com/android/internal/compat/ChangeReporterTest.java
index 09bbe37..a052543 100644
--- a/core/tests/PlatformCompatFramework/src/com/android/internal/compat/ChangeReporterTest.java
+++ b/core/tests/PlatformCompatFramework/src/com/android/internal/compat/ChangeReporterTest.java
@@ -24,10 +24,10 @@
 public class ChangeReporterTest {
     @Test
     public void testStatsLogOnce() {
-        ChangeReporter reporter = new ChangeReporter(0);
+        ChangeReporter reporter = new ChangeReporter(ChangeReporter.SOURCE_UNKNOWN_SOURCE);
         int myUid = 1022, otherUid = 1023;
         long myChangeId = 500L, otherChangeId = 600L;
-        int myState = 1, otherState = 2;
+        int myState = ChangeReporter.STATE_ENABLED, otherState = ChangeReporter.STATE_DISABLED;
 
         assertTrue(reporter.shouldWriteToStatsLog(myUid, myChangeId, myState));
         reporter.reportChange(myUid, myChangeId, myState);
@@ -42,10 +42,10 @@
 
     @Test
     public void testStatsLogAfterReset() {
-        ChangeReporter reporter = new ChangeReporter(0);
+        ChangeReporter reporter = new ChangeReporter(ChangeReporter.SOURCE_UNKNOWN_SOURCE);
         int myUid = 1022;
         long myChangeId = 500L;
-        int myState = 1;
+        int myState = ChangeReporter.STATE_ENABLED;
 
         assertTrue(reporter.shouldWriteToStatsLog(myUid, myChangeId, myState));
         reporter.reportChange(myUid, myChangeId, myState);
@@ -60,10 +60,10 @@
 
     @Test
     public void testDebugLogOnce() {
-        ChangeReporter reporter = new ChangeReporter(0);
+        ChangeReporter reporter = new ChangeReporter(ChangeReporter.SOURCE_UNKNOWN_SOURCE);
         int myUid = 1022, otherUid = 1023;
         long myChangeId = 500L, otherChangeId = 600L;
-        int myState = 1, otherState = 2;
+        int myState = ChangeReporter.STATE_ENABLED, otherState = ChangeReporter.STATE_DISABLED;
 
         assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myState));
         reporter.reportChange(myUid, myChangeId, myState);
@@ -78,10 +78,10 @@
 
     @Test
     public void testDebugLogAfterReset() {
-        ChangeReporter reporter = new ChangeReporter(0);
+        ChangeReporter reporter = new ChangeReporter(ChangeReporter.SOURCE_UNKNOWN_SOURCE);
         int myUid = 1022;
         long myChangeId = 500L;
-        int myState = 1;
+        int myState = ChangeReporter.STATE_ENABLED;
 
         assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myState));
         reporter.reportChange(myUid, myChangeId, myState);
@@ -96,10 +96,10 @@
 
     @Test
     public void testDebugLogWithLogAll() {
-        ChangeReporter reporter = new ChangeReporter(0);
+        ChangeReporter reporter = new ChangeReporter(ChangeReporter.SOURCE_UNKNOWN_SOURCE);
         int myUid = 1022;
         long myChangeId = 500L;
-        int myState = 1;
+        int myState = ChangeReporter.STATE_ENABLED;
 
         assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myState));
         reporter.reportChange(myUid, myChangeId, myState);
diff --git a/core/tests/coretests/src/android/app/timedetector/PhoneTimeSuggestionTest.java b/core/tests/coretests/src/android/app/timedetector/PhoneTimeSuggestionTest.java
index ba29a97..d17b635 100644
--- a/core/tests/coretests/src/android/app/timedetector/PhoneTimeSuggestionTest.java
+++ b/core/tests/coretests/src/android/app/timedetector/PhoneTimeSuggestionTest.java
@@ -27,17 +27,17 @@
 import org.junit.Test;
 
 public class PhoneTimeSuggestionTest {
-    private static final int PHONE_ID = 99999;
+    private static final int SLOT_INDEX = 99999;
 
     @Test
     public void testEquals() {
-        PhoneTimeSuggestion.Builder builder1 = new PhoneTimeSuggestion.Builder(PHONE_ID);
+        PhoneTimeSuggestion.Builder builder1 = new PhoneTimeSuggestion.Builder(SLOT_INDEX);
         {
             PhoneTimeSuggestion one = builder1.build();
             assertEquals(one, one);
         }
 
-        PhoneTimeSuggestion.Builder builder2 = new PhoneTimeSuggestion.Builder(PHONE_ID);
+        PhoneTimeSuggestion.Builder builder2 = new PhoneTimeSuggestion.Builder(SLOT_INDEX);
         {
             PhoneTimeSuggestion one = builder1.build();
             PhoneTimeSuggestion two = builder2.build();
@@ -59,7 +59,7 @@
             assertEquals(two, one);
         }
 
-        PhoneTimeSuggestion.Builder builder3 = new PhoneTimeSuggestion.Builder(PHONE_ID + 1);
+        PhoneTimeSuggestion.Builder builder3 = new PhoneTimeSuggestion.Builder(SLOT_INDEX + 1);
         builder3.setUtcTime(new TimestampedValue<>(1111L, 2222L));
         {
             PhoneTimeSuggestion one = builder1.build();
@@ -80,7 +80,7 @@
 
     @Test
     public void testParcelable() {
-        PhoneTimeSuggestion.Builder builder = new PhoneTimeSuggestion.Builder(PHONE_ID);
+        PhoneTimeSuggestion.Builder builder = new PhoneTimeSuggestion.Builder(SLOT_INDEX);
         assertRoundTripParcelable(builder.build());
 
         builder.setUtcTime(new TimestampedValue<>(1111L, 2222L));
diff --git a/core/tests/coretests/src/android/app/timezonedetector/PhoneTimeZoneSuggestionTest.java b/core/tests/coretests/src/android/app/timezonedetector/PhoneTimeZoneSuggestionTest.java
index 0108a0b..384dbf9 100644
--- a/core/tests/coretests/src/android/app/timezonedetector/PhoneTimeZoneSuggestionTest.java
+++ b/core/tests/coretests/src/android/app/timezonedetector/PhoneTimeZoneSuggestionTest.java
@@ -26,17 +26,17 @@
 import org.junit.Test;
 
 public class PhoneTimeZoneSuggestionTest {
-    private static final int PHONE_ID = 99999;
+    private static final int SLOT_INDEX = 99999;
 
     @Test
     public void testEquals() {
-        PhoneTimeZoneSuggestion.Builder builder1 = new PhoneTimeZoneSuggestion.Builder(PHONE_ID);
+        PhoneTimeZoneSuggestion.Builder builder1 = new PhoneTimeZoneSuggestion.Builder(SLOT_INDEX);
         {
             PhoneTimeZoneSuggestion one = builder1.build();
             assertEquals(one, one);
         }
 
-        PhoneTimeZoneSuggestion.Builder builder2 = new PhoneTimeZoneSuggestion.Builder(PHONE_ID);
+        PhoneTimeZoneSuggestion.Builder builder2 = new PhoneTimeZoneSuggestion.Builder(SLOT_INDEX);
         {
             PhoneTimeZoneSuggestion one = builder1.build();
             PhoneTimeZoneSuggestion two = builder2.build();
@@ -45,7 +45,7 @@
         }
 
         PhoneTimeZoneSuggestion.Builder builder3 =
-                new PhoneTimeZoneSuggestion.Builder(PHONE_ID + 1);
+                new PhoneTimeZoneSuggestion.Builder(SLOT_INDEX + 1);
         {
             PhoneTimeZoneSuggestion one = builder1.build();
             PhoneTimeZoneSuggestion three = builder3.build();
@@ -120,7 +120,7 @@
 
     @Test(expected = RuntimeException.class)
     public void testBuilderValidates_emptyZone_badMatchType() {
-        PhoneTimeZoneSuggestion.Builder builder = new PhoneTimeZoneSuggestion.Builder(PHONE_ID);
+        PhoneTimeZoneSuggestion.Builder builder = new PhoneTimeZoneSuggestion.Builder(SLOT_INDEX);
         // No zone ID, so match type should be left unset.
         builder.setMatchType(PhoneTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET);
         builder.build();
@@ -128,7 +128,7 @@
 
     @Test(expected = RuntimeException.class)
     public void testBuilderValidates_zoneSet_badMatchType() {
-        PhoneTimeZoneSuggestion.Builder builder = new PhoneTimeZoneSuggestion.Builder(PHONE_ID);
+        PhoneTimeZoneSuggestion.Builder builder = new PhoneTimeZoneSuggestion.Builder(SLOT_INDEX);
         builder.setZoneId("Europe/London");
         builder.setQuality(PhoneTimeZoneSuggestion.QUALITY_SINGLE_ZONE);
         builder.build();
@@ -136,7 +136,7 @@
 
     @Test
     public void testParcelable() {
-        PhoneTimeZoneSuggestion.Builder builder = new PhoneTimeZoneSuggestion.Builder(PHONE_ID);
+        PhoneTimeZoneSuggestion.Builder builder = new PhoneTimeZoneSuggestion.Builder(SLOT_INDEX);
         assertRoundTripParcelable(builder.build());
 
         builder.setZoneId("Europe/London");
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 91c5fbe..9da185b 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1183,12 +1183,6 @@
       "group": "WM_DEBUG_APP_TRANSITIONS",
       "at": "com\/android\/server\/wm\/AppTransitionController.java"
     },
-    "292555239": {
-      "message": "ScreenRotation sill animating: mDisplayAnimator: %s\nmEnterBlackFrameAnimator: %s\nmRotateScreenAnimator: %s\nmScreenshotRotationAnimator: %s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_ORIENTATION",
-      "at": "com\/android\/server\/wm\/ScreenRotationAnimation.java"
-    },
     "292904800": {
       "message": "Deferring rotation, animation in progress.",
       "level": "VERBOSE",
@@ -1573,12 +1567,6 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
-    "1004585481": {
-      "message": "%s forcing orientation to %d for display id=%d",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_ORIENTATION",
-      "at": "com\/android\/server\/wm\/DisplayContent.java"
-    },
     "1051545910": {
       "message": "Exit animation finished in %s: remove=%b",
       "level": "VERBOSE",
@@ -1699,6 +1687,12 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "1346895820": {
+      "message": "ScreenRotation still animating: type: %d\nmDisplayAnimator: %s\nmEnterBlackFrameAnimator: %s\nmRotateScreenAnimator: %s\nmScreenshotRotationAnimator: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/ScreenRotationAnimation.java"
+    },
     "1358462645": {
       "message": "Looking for focus: %s, flags=%d, canReceive=%b",
       "level": "VERBOSE",
@@ -1717,6 +1711,12 @@
       "group": "WM_DEBUG_IME",
       "at": "com\/android\/server\/wm\/ImeInsetsSourceProvider.java"
     },
+    "1389009035": {
+      "message": "NonAppWindowContainer cannot set orientation: %s",
+      "level": "WARN",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/DisplayContent.java"
+    },
     "1401700824": {
       "message": "Window drawn win=%s",
       "level": "DEBUG",
@@ -1891,6 +1891,12 @@
       "group": "WM_DEBUG_STARTING_WINDOW",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "1674747211": {
+      "message": "%s forcing orientation to %d for display id=%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/DisplayArea.java"
+    },
     "1677260366": {
       "message": "Finish starting %s: first real window is shown, no animation",
       "level": "VERBOSE",
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index dace091..0e88c75 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -198,6 +198,7 @@
     final List<String> mFeatures;
     @DeviceType
     final int mDeviceType;
+    final boolean mIsSystem;
     final Uri mIconUri;
     final CharSequence mDescription;
     @ConnectionState
@@ -214,6 +215,7 @@
         mName = builder.mName;
         mFeatures = builder.mFeatures;
         mDeviceType = builder.mDeviceType;
+        mIsSystem = builder.mIsSystem;
         mIconUri = builder.mIconUri;
         mDescription = builder.mDescription;
         mConnectionState = builder.mConnectionState;
@@ -230,6 +232,7 @@
         mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
         mFeatures = in.createStringArrayList();
         mDeviceType = in.readInt();
+        mIsSystem = in.readBoolean();
         mIconUri = in.readParcelable(null);
         mDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
         mConnectionState = in.readInt();
@@ -288,6 +291,17 @@
     }
 
     /**
+     * Returns whether the route is a system route or not.
+     * <p>
+     * System routes are media routes directly controlled by the system
+     * such as phone speaker, wired headset, and Bluetooth devices.
+     * </p>
+     */
+    public boolean isSystemRoute() {
+        return mIsSystem;
+    }
+
+    /**
      * Gets the URI of the icon representing this route.
      * <p>
      * This icon will be used in picker UIs if available.
@@ -425,6 +439,7 @@
                 && Objects.equals(mName, other.mName)
                 && Objects.equals(mFeatures, other.mFeatures)
                 && (mDeviceType == other.mDeviceType)
+                && (mIsSystem == other.mIsSystem)
                 && Objects.equals(mIconUri, other.mIconUri)
                 && Objects.equals(mDescription, other.mDescription)
                 && (mConnectionState == other.mConnectionState)
@@ -438,7 +453,7 @@
     @Override
     public int hashCode() {
         // Note: mExtras is not included.
-        return Objects.hash(mId, mName, mFeatures, mDeviceType, mIconUri, mDescription,
+        return Objects.hash(mId, mName, mFeatures, mDeviceType, mIsSystem, mIconUri, mDescription,
                 mConnectionState, mClientPackageName, mVolumeHandling, mVolumeMax, mVolume,
                 mProviderId);
     }
@@ -475,6 +490,7 @@
         TextUtils.writeToParcel(mName, dest, flags);
         dest.writeStringList(mFeatures);
         dest.writeInt(mDeviceType);
+        dest.writeBoolean(mIsSystem);
         dest.writeParcelable(mIconUri, flags);
         TextUtils.writeToParcel(mDescription, dest, flags);
         dest.writeInt(mConnectionState);
@@ -496,6 +512,7 @@
 
         @DeviceType
         int mDeviceType = DEVICE_TYPE_UNKNOWN;
+        boolean mIsSystem;
         Uri mIconUri;
         CharSequence mDescription;
         @ConnectionState
@@ -542,6 +559,7 @@
             mName = routeInfo.mName;
             mFeatures = new ArrayList<>(routeInfo.mFeatures);
             mDeviceType = routeInfo.mDeviceType;
+            mIsSystem = routeInfo.mIsSystem;
             mIconUri = routeInfo.mIconUri;
             mDescription = routeInfo.mDescription;
             mConnectionState = routeInfo.mConnectionState;
@@ -610,6 +628,16 @@
         }
 
         /**
+         * Sets whether the route is a system route or not.
+         * @hide
+         */
+        @NonNull
+        public Builder setSystemRoute(boolean isSystem) {
+            mIsSystem = isSystem;
+            return this;
+        }
+
+        /**
          * Sets the URI of the icon representing this route.
          * <p>
          * This icon will be used in picker UIs if available.
diff --git a/media/java/android/media/MediaRoute2ProviderInfo.java b/media/java/android/media/MediaRoute2ProviderInfo.java
index c9a2ec7..afe002e 100644
--- a/media/java/android/media/MediaRoute2ProviderInfo.java
+++ b/media/java/android/media/MediaRoute2ProviderInfo.java
@@ -180,6 +180,23 @@
         }
 
         /**
+         * Sets whether the provider provides system routes or not
+         */
+        @NonNull
+        public Builder setSystemRouteProvider(boolean isSystem) {
+            int count = mRoutes.size();
+            for (int i = 0; i < count; i++) {
+                MediaRoute2Info route = mRoutes.valueAt(i);
+                if (route.isSystemRoute() != isSystem) {
+                    mRoutes.setValueAt(i, new MediaRoute2Info.Builder(route)
+                            .setSystemRoute(isSystem)
+                            .build());
+                }
+            }
+            return this;
+        }
+
+        /**
          * Adds a route to the provider
          */
         @NonNull
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 7b9a44f..f751a22 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -230,7 +230,11 @@
     /**
      * Gets the unmodifiable list of {@link MediaRoute2Info routes} currently
      * known to the media router.
+     * <p>
+     * {@link MediaRoute2Info#isSystemRoute() System routes} such as phone speaker,
+     * Bluetooth devices are always included in the list.
      * Please note that the list can be changed before callbacks are invoked.
+     * </p>
      *
      * @return the list of routes that contains at least one of the route features in discovery
      * preferences registered by the application
@@ -243,7 +247,8 @@
 
                 List<MediaRoute2Info> filteredRoutes = new ArrayList<>();
                 for (MediaRoute2Info route : mRoutes.values()) {
-                    if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) {
+                    if (route.isSystemRoute()
+                            || route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) {
                         filteredRoutes.add(route);
                     }
                 }
@@ -307,12 +312,18 @@
      * with the given route.
      *
      * @param route the route you want to create a controller with.
+     * @throws IllegalArgumentException if the given route is
+     * {@link MediaRoute2Info#isSystemRoute() system route}
      *
      * @see RoutingControllerCallback#onControllerCreated
      * @see RoutingControllerCallback#onControllerCreationFailed
      */
     public void requestCreateController(@NonNull MediaRoute2Info route) {
         Objects.requireNonNull(route, "route must not be null");
+        if (route.isSystemRoute()) {
+            throw new IllegalArgumentException("Can't create a route controller with "
+                    + "a system route. Use getSystemController().");
+        }
         // TODO: Check the given route exists
 
         final int requestId;
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 61e2f77..662eeb1 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -161,14 +161,15 @@
     public List<MediaRoute2Info> getAvailableRoutes(@NonNull String packageName) {
         Objects.requireNonNull(packageName, "packageName must not be null");
 
+        List<MediaRoute2Info> routes = new ArrayList<>();
+
         List<String> preferredFeatures = mPreferredFeaturesMap.get(packageName);
         if (preferredFeatures == null) {
-            return Collections.emptyList();
+            preferredFeatures = Collections.emptyList();
         }
-        List<MediaRoute2Info> routes = new ArrayList<>();
         synchronized (mRoutesLock) {
             for (MediaRoute2Info route : mRoutes.values()) {
-                if (route.hasAnyFeatures(preferredFeatures)) {
+                if (route.isSystemRoute() || route.hasAnyFeatures(preferredFeatures)) {
                     routes.add(route);
                 }
             }
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
index 4a2044a..16259ab 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
@@ -196,7 +196,14 @@
     public void testRouteFeatures() throws Exception {
         Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_SPECIAL);
 
-        assertEquals(1, routes.size());
+        int routeCount = 0;
+        for (MediaRoute2Info route : routes.values()) {
+            if (!route.isSystemRoute()) {
+                routeCount++;
+            }
+        }
+
+        assertEquals(1, routeCount);
         assertNotNull(routes.get(ROUTE_ID_SPECIAL_FEATURE));
     }
 
diff --git a/native/android/Android.bp b/native/android/Android.bp
index 34ab7a0..0c6f507 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -64,7 +64,6 @@
         "libgui",
         "libharfbuzz_ng",  // Only for including hb.h via minikin
         "libsensor",
-        "libstats_jni",
         "libandroid_runtime",
         "libminikin",
         "libnetd_client",
diff --git a/packages/CarSystemUI/res/values/config.xml b/packages/CarSystemUI/res/values/config.xml
index e2297e4..d2f514c 100644
--- a/packages/CarSystemUI/res/values/config.xml
+++ b/packages/CarSystemUI/res/values/config.xml
@@ -29,6 +29,9 @@
     <bool name="config_enableRightNavigationBar">false</bool>
     <bool name="config_enableBottomNavigationBar">true</bool>
 
+    <!-- Disable normal notification rendering; we handle that ourselves -->
+    <bool name="config_renderNotifications">false</bool>
+
     <!-- Whether heads-up notifications should be shown when shade is open. -->
     <bool name="config_enableHeadsUpNotificationWhenNotificationShadeOpen">true</bool>
 
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
index cf4ee7d..585acfe 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
@@ -22,7 +22,6 @@
 import android.content.Context;
 
 import com.android.systemui.car.CarDeviceProvisionedControllerImpl;
-import com.android.systemui.car.CarNotificationEntryManager;
 import com.android.systemui.car.CarNotificationInterruptionStateProvider;
 import com.android.systemui.dagger.SystemUIRootComponent;
 import com.android.systemui.dock.DockManager;
@@ -73,13 +72,6 @@
         return false;
     }
 
-    /**
-     * Use {@link CarNotificationEntryManager}, which does nothing when adding a notification.
-     */
-    @Binds
-    abstract NotificationEntryManager bindNotificationEntryManager(
-            CarNotificationEntryManager notificationEntryManager);
-
     @Singleton
     @Provides
     static HeadsUpManagerPhone provideHeadsUpManagerPhone(Context context,
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java
deleted file mode 100644
index cfe1c70..0000000
--- a/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * 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.
- * 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.car;
-
-import android.service.notification.NotificationListenerService;
-import android.service.notification.StatusBarNotification;
-
-import com.android.systemui.statusbar.FeatureFlags;
-import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
-import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
-import com.android.systemui.statusbar.notification.logging.NotifLog;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.util.leak.LeakDetector;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-import dagger.Lazy;
-
-/**
- * Car specific notification entry manager that does nothing when adding a notification.
- *
- * <p> This is because system UI notifications are disabled and we have a different implementation.
- * Please see {@link com.android.car.notification}.
- */
-@Singleton
-public class CarNotificationEntryManager extends NotificationEntryManager {
-
-    @Inject
-    public CarNotificationEntryManager(
-            NotifLog notifLog,
-            NotificationGroupManager groupManager,
-            NotificationRankingManager rankingManager,
-            KeyguardEnvironment keyguardEnvironment,
-            FeatureFlags featureFlags,
-            Lazy<NotificationRowBinder> notificationRowBinderLazy,
-            Lazy<NotificationRemoteInputManager> notificationRemoteInputManagerLazy,
-            LeakDetector leakDetector) {
-        super(notifLog, groupManager, rankingManager, keyguardEnvironment, featureFlags,
-                notificationRowBinderLazy, notificationRemoteInputManagerLazy, leakDetector);
-    }
-
-    @Override
-    public void addNotification(
-            StatusBarNotification notification, NotificationListenerService.RankingMap ranking) {
-    }
-}
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 76e9ec6..210dd32 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -87,10 +87,8 @@
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.FlingAnimationUtils;
 import com.android.systemui.statusbar.NavigationBarController;
-import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -103,12 +101,10 @@
 import com.android.systemui.statusbar.notification.BypassHeadsUpNotifier;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationAlertingManager;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
-import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
-import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer;
+import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.phone.AutoHideController;
@@ -124,7 +120,6 @@
 import com.android.systemui.statusbar.phone.LightsOutNotifController;
 import com.android.systemui.statusbar.phone.LockscreenLockIconController;
 import com.android.systemui.statusbar.phone.LockscreenWallpaper;
-import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.ScrimController;
@@ -141,7 +136,6 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
-import com.android.systemui.statusbar.policy.RemoteInputUriController;
 import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.volume.VolumeComponent;
@@ -257,7 +251,7 @@
 
     public CarStatusBar(
             Context context,
-            FeatureFlags featureFlags,
+            NotificationsController notificationsController,
             LightBarController lightBarController,
             AutoHideController autoHideController,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -269,13 +263,11 @@
             HeadsUpManagerPhone headsUpManagerPhone,
             DynamicPrivacyController dynamicPrivacyController,
             BypassHeadsUpNotifier bypassHeadsUpNotifier,
-            Lazy<NotifPipelineInitializer> notifPipelineInitializer,
             FalsingManager falsingManager,
             BroadcastDispatcher broadcastDispatcher,
             RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler,
             NotificationGutsManager notificationGutsManager,
             NotificationLogger notificationLogger,
-            NotificationEntryManager notificationEntryManager,
             NotificationInterruptionStateProvider notificationInterruptionStateProvider,
             NotificationViewHierarchyManager notificationViewHierarchyManager,
             KeyguardViewMediator keyguardViewMediator,
@@ -296,12 +288,10 @@
             VibratorHelper vibratorHelper,
             BubbleController bubbleController,
             NotificationGroupManager groupManager,
-            NotificationGroupAlertTransferHelper groupAlertTransferHelper,
             VisualStabilityManager visualStabilityManager,
             DeviceProvisionedController deviceProvisionedController,
             NavigationBarController navigationBarController,
             Lazy<AssistManager> assistManagerLazy,
-            NotificationListener notificationListener,
             ConfigurationController configurationController,
             NotificationShadeWindowController notificationShadeWindowController,
             LockscreenLockIconController lockscreenLockIconController,
@@ -318,7 +308,6 @@
             Optional<Recents> recents,
             Provider<StatusBarComponent.Builder> statusBarComponentBuilder,
             PluginManager pluginManager,
-            RemoteInputUriController remoteInputUriController,
             Optional<Divider> dividerOptional,
             SuperStatusBarViewFactory superStatusBarViewFactory,
             LightsOutNotifController lightsOutNotifController,
@@ -334,7 +323,6 @@
             KeyguardDismissUtil keyguardDismissUtil,
             ExtensionController extensionController,
             UserInfoControllerImpl userInfoControllerImpl,
-            NotificationRowBinderImpl notificationRowBinder,
             DismissCallbackRegistry dismissCallbackRegistry,
             /* Car Settings injected components. */
             CarServiceProvider carServiceProvider,
@@ -345,7 +333,7 @@
             FlingAnimationUtils.Builder flingAnimationUtilsBuilder) {
         super(
                 context,
-                featureFlags,
+                notificationsController,
                 lightBarController,
                 autoHideController,
                 keyguardUpdateMonitor,
@@ -357,13 +345,11 @@
                 headsUpManagerPhone,
                 dynamicPrivacyController,
                 bypassHeadsUpNotifier,
-                notifPipelineInitializer,
                 falsingManager,
                 broadcastDispatcher,
                 remoteInputQuickSettingsDisabler,
                 notificationGutsManager,
                 notificationLogger,
-                notificationEntryManager,
                 notificationInterruptionStateProvider,
                 notificationViewHierarchyManager,
                 keyguardViewMediator,
@@ -384,12 +370,10 @@
                 vibratorHelper,
                 bubbleController,
                 groupManager,
-                groupAlertTransferHelper,
                 visualStabilityManager,
                 deviceProvisionedController,
                 navigationBarController,
                 assistManagerLazy,
-                notificationListener,
                 configurationController,
                 notificationShadeWindowController,
                 lockscreenLockIconController,
@@ -407,7 +391,6 @@
                 recents,
                 statusBarComponentBuilder,
                 pluginManager,
-                remoteInputUriController,
                 dividerOptional,
                 lightsOutNotifController,
                 statusBarNotificationActivityStarterBuilder,
@@ -422,7 +405,6 @@
                 keyguardDismissUtil,
                 extensionController,
                 userInfoControllerImpl,
-                notificationRowBinder,
                 dismissCallbackRegistry);
         mUserSwitcherController = userSwitcherController;
         mScrimController = scrimController;
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
index 45da822..498bd87 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
@@ -47,10 +47,8 @@
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.FlingAnimationUtils;
 import com.android.systemui.statusbar.NavigationBarController;
-import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -63,12 +61,10 @@
 import com.android.systemui.statusbar.notification.BypassHeadsUpNotifier;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationAlertingManager;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
-import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
-import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer;
+import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.phone.AutoHideController;
@@ -83,7 +79,6 @@
 import com.android.systemui.statusbar.phone.LightsOutNotifController;
 import com.android.systemui.statusbar.phone.LockscreenLockIconController;
 import com.android.systemui.statusbar.phone.LockscreenWallpaper;
-import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.ScrimController;
@@ -99,7 +94,6 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
-import com.android.systemui.statusbar.policy.RemoteInputUriController;
 import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.volume.VolumeComponent;
@@ -127,7 +121,7 @@
     @Singleton
     static CarStatusBar provideStatusBar(
             Context context,
-            FeatureFlags featureFlags,
+            NotificationsController notificationsController,
             LightBarController lightBarController,
             AutoHideController autoHideController,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -139,13 +133,11 @@
             HeadsUpManagerPhone headsUpManagerPhone,
             DynamicPrivacyController dynamicPrivacyController,
             BypassHeadsUpNotifier bypassHeadsUpNotifier,
-            Lazy<NotifPipelineInitializer> notifPipelineInitializer,
             FalsingManager falsingManager,
             BroadcastDispatcher broadcastDispatcher,
             RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler,
             NotificationGutsManager notificationGutsManager,
             NotificationLogger notificationLogger,
-            NotificationEntryManager notificationEntryManager,
             NotificationInterruptionStateProvider notificationInterruptionStateProvider,
             NotificationViewHierarchyManager notificationViewHierarchyManager,
             KeyguardViewMediator keyguardViewMediator,
@@ -166,12 +158,10 @@
             VibratorHelper vibratorHelper,
             BubbleController bubbleController,
             NotificationGroupManager groupManager,
-            NotificationGroupAlertTransferHelper groupAlertTransferHelper,
             VisualStabilityManager visualStabilityManager,
             DeviceProvisionedController deviceProvisionedController,
             NavigationBarController navigationBarController,
             Lazy<AssistManager> assistManagerLazy,
-            NotificationListener notificationListener,
             ConfigurationController configurationController,
             NotificationShadeWindowController notificationShadeWindowController,
             LockscreenLockIconController lockscreenLockIconController,
@@ -188,7 +178,6 @@
             Optional<Recents> recentsOptional,
             Provider<StatusBarComponent.Builder> statusBarComponentBuilder,
             PluginManager pluginManager,
-            RemoteInputUriController remoteInputUriController,
             Optional<Divider> dividerOptional,
             SuperStatusBarViewFactory superStatusBarViewFactory,
             LightsOutNotifController lightsOutNotifController,
@@ -204,7 +193,6 @@
             KeyguardDismissUtil keyguardDismissUtil,
             ExtensionController extensionController,
             UserInfoControllerImpl userInfoControllerImpl,
-            NotificationRowBinderImpl notificationRowBinder,
             DismissCallbackRegistry dismissCallbackRegistry,
             CarServiceProvider carServiceProvider,
             Lazy<PowerManagerHelper> powerManagerHelperLazy,
@@ -214,7 +202,7 @@
             FlingAnimationUtils.Builder flingAnimationUtilsBuilder) {
         return new CarStatusBar(
                 context,
-                featureFlags,
+                notificationsController,
                 lightBarController,
                 autoHideController,
                 keyguardUpdateMonitor,
@@ -226,13 +214,11 @@
                 headsUpManagerPhone,
                 dynamicPrivacyController,
                 bypassHeadsUpNotifier,
-                notifPipelineInitializer,
                 falsingManager,
                 broadcastDispatcher,
                 remoteInputQuickSettingsDisabler,
                 notificationGutsManager,
                 notificationLogger,
-                notificationEntryManager,
                 notificationInterruptionStateProvider,
                 notificationViewHierarchyManager,
                 keyguardViewMediator,
@@ -253,12 +239,10 @@
                 vibratorHelper,
                 bubbleController,
                 groupManager,
-                groupAlertTransferHelper,
                 visualStabilityManager,
                 deviceProvisionedController,
                 navigationBarController,
                 assistManagerLazy,
-                notificationListener,
                 configurationController,
                 notificationShadeWindowController,
                 lockscreenLockIconController,
@@ -275,7 +259,6 @@
                 recentsOptional,
                 statusBarComponentBuilder,
                 pluginManager,
-                remoteInputUriController,
                 dividerOptional,
                 superStatusBarViewFactory,
                 lightsOutNotifController,
@@ -290,7 +273,6 @@
                 keyguardDismissUtil,
                 extensionController,
                 userInfoControllerImpl,
-                notificationRowBinder,
                 dismissCallbackRegistry,
                 carServiceProvider,
                 powerManagerHelperLazy,
diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml
index 2b143e4..b31841d 100644
--- a/packages/SettingsLib/res/values-af/strings.xml
+++ b/packages/SettingsLib/res/values-af/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Gekoppel via <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Beskikbaar via %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Tik om aan te meld"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Gekoppel, geen internet nie"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Daar kan nie by private DNS-bediener ingegaan word nie"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Beperkte verbinding"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Geen internet nie"</string>
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index 2a93e017..4955ad8 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"በ <xliff:g id="NAME">%1$s</xliff:g> በኩል ተገናኝተዋል"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"በ%1$s በኩል የሚገኝ"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"ለመመዝገብ መታ ያድርጉ"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"ተገናኝቷል፣ ምንም በይነመረብ የለም"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"የግል ዲኤንኤስ አገልጋይ ሊደረስበት አይችልም"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"የተገደበ ግንኙነት"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"ምንም በይነመረብ የለም"</string>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index 5103345..7b26be4 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"تم الاتصال عبر <xliff:g id="NAME">%1$s</xliff:g>."</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"‏متوفرة عبر %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"انقر للاشتراك."</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"متصلة ولكن بلا إنترنت"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"لا يمكن الوصول إلى خادم أسماء نظام نطاقات خاص"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"اتصال محدود"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"لا يتوفر اتصال إنترنت."</string>
diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml
index fa26b9b..dcebe5b 100644
--- a/packages/SettingsLib/res/values-as/strings.xml
+++ b/packages/SettingsLib/res/values-as/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g>ৰ জৰিয়তে সংযুক্ত"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"%1$sৰ মাধ্যমেৰে উপলব্ধ"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"ছাইন আপ কৰিবলৈ টিপক"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"সংযোজিত, ইণ্টাৰনেট নাই"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"ব্যক্তিগত DNS ছাৰ্ভাৰ এক্সেছ কৰিব নোৱাৰি"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"ইণ্টাৰনেট সংযোগ সীমিত"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"ইণ্টাৰনেট সংযোগ নাই"</string>
diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml
index 2e80fdc..7f3db37 100644
--- a/packages/SettingsLib/res/values-az/strings.xml
+++ b/packages/SettingsLib/res/values-az/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> ilə qoşulub"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s vasitəsilə əlçatandır"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Qeydiyyatdan keçmək üçün klikləyin"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Qoşuludur, internet yoxdur"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Özəl DNS serverinə giriş mümkün deyil"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Məhdud bağlantı"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"İnternet yoxdur"</string>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
index ae5c936..b789cb0 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Povezano preko: <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Dostupna je preko pristupne tačke %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Dodirnite da biste se registrovali"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Veza je uspostavljena, nema interneta"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Pristup privatnom DNS serveru nije uspeo"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Ograničena veza"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nema interneta"</string>
diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml
index ad201d0..ce191eb 100644
--- a/packages/SettingsLib/res/values-be/strings.xml
+++ b/packages/SettingsLib/res/values-be/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Падключана праз праграму \"<xliff:g id="NAME">%1$s</xliff:g>\""</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Даступна праз %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Націсніце, каб зарэгістравацца"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Падключана, без доступу да інтэрнэту"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Не ўдалося атрымаць доступ да прыватнага DNS-сервера"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Абмежаваныя магчымасці падключэння"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Не падключана да інтэрнэту"</string>
diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml
index 94f78ad..3e6f77d 100644
--- a/packages/SettingsLib/res/values-bg/strings.xml
+++ b/packages/SettingsLib/res/values-bg/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Установена е връзка през <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Мрежата е достъпна през „%1$s“"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Докоснете, за да се регистрирате"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Установена е връзка – няма достъп до интернет"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Не може да се осъществи достъп до частния DNS сървър"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Ограничена връзка"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Няма връзка с интернет"</string>
diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml
index 51719160..7f6938a 100644
--- a/packages/SettingsLib/res/values-bn/strings.xml
+++ b/packages/SettingsLib/res/values-bn/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g>-এর মাধ্যমে কানেক্ট করা আছে"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s এর মাধ্যমে উপলব্ধ"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"সাইন-আপ করতে ট্যাপ করুন"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"কানেক্ট, ইন্টারনেট নেই"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"ব্যক্তিগত ডিএনএস সার্ভার অ্যাক্সেস করা যাবে না"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"সীমিত কানেকশন"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"ইন্টারনেট কানেকশন নেই"</string>
diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml
index f74bcee..0563abd 100644
--- a/packages/SettingsLib/res/values-bs/strings.xml
+++ b/packages/SettingsLib/res/values-bs/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Povezano preko <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Dostupan preko %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Dodirnite za prijavu"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Povezano, nema interneta"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Nije moguće pristupiti privatnom DNS serveru"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Ograničena veza"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nema internetske veze"</string>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index 1b23ecf..71b2c5d 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Connectat mitjançant <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Disponible mitjançant %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Toca per registrar-te"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Connectada, sense Internet"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"No es pot accedir al servidor DNS privat"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Connexió limitada"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Sense connexió a Internet"</string>
diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml
index 22603cc..a381772 100644
--- a/packages/SettingsLib/res/values-cs/strings.xml
+++ b/packages/SettingsLib/res/values-cs/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Připojeno přes <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Dostupné prostřednictvím %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Klepnutím se zaregistrujete"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Připojeno, není k dispozici internet"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Nelze získat přístup k soukromému serveru DNS"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Omezené připojení"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nejste připojeni k internetu"</string>
diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml
index bef1855..f6e8576 100644
--- a/packages/SettingsLib/res/values-da/strings.xml
+++ b/packages/SettingsLib/res/values-da/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Forbundet via <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Tilgængelig via %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Tryk for at registrere dig"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Tilsluttet – intet internet"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Der er ikke adgang til den private DNS-server"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Begrænset forbindelse"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Intet internet"</string>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index c8c97bd..99a0910 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Verbunden über <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Verfügbar über %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Zum Anmelden tippen"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Verbunden, kein Internet"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Auf den privaten DNS-Server kann nicht zugegriffen werden"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Eingeschränkte Verbindung"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Kein Internet"</string>
diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml
index 371075c..d37ea44 100644
--- a/packages/SettingsLib/res/values-el/strings.xml
+++ b/packages/SettingsLib/res/values-el/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Συνδέθηκε μέσω <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Διαθέσιμο μέσω %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Πατήστε για εγγραφή"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Συνδέθηκε, χωρίς σύνδεση στο διαδίκτυο"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Δεν είναι δυνατή η πρόσβαση στον ιδιωτικό διακομιστή DNS."</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Περιορισμένη σύνδεση"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Δεν υπάρχει σύνδεση στο διαδίκτυο"</string>
diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml
index b314d17..429cd3e 100644
--- a/packages/SettingsLib/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/res/values-en-rAU/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Connected via <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Available via %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Tap to sign up"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Connected, no Internet"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Private DNS server cannot be accessed"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Limited connection"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"No Internet"</string>
diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml
index b314d17..429cd3e 100644
--- a/packages/SettingsLib/res/values-en-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-en-rCA/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Connected via <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Available via %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Tap to sign up"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Connected, no Internet"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Private DNS server cannot be accessed"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Limited connection"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"No Internet"</string>
diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml
index b314d17..429cd3e 100644
--- a/packages/SettingsLib/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/res/values-en-rGB/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Connected via <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Available via %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Tap to sign up"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Connected, no Internet"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Private DNS server cannot be accessed"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Limited connection"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"No Internet"</string>
diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml
index b314d17..429cd3e 100644
--- a/packages/SettingsLib/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-en-rIN/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Connected via <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Available via %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Tap to sign up"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Connected, no Internet"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Private DNS server cannot be accessed"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Limited connection"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"No Internet"</string>
diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml
index 95944dc..1aa6cdb 100644
--- a/packages/SettingsLib/res/values-en-rXC/strings.xml
+++ b/packages/SettingsLib/res/values-en-rXC/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‎‎‎‏‎‎‎‎‎‏‎‏‎‎‏‎‎‎‏‎‏‎‏‏‏‎‎‏‎‎‏‎‏‎‎‏‏‎‎‏‏‎‏‎‎‎‎‎‎‏‏‏‎‏‏‎‏‎‎‎‎Connected via ‎‏‎‎‏‏‎<xliff:g id="NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‎‏‏‏‏‏‎‏‎‎‎‎‎‏‏‏‎‏‎‏‎‎‏‎‎‏‎‏‎‏‎‏‎‎‏‎‏‏‎‏‏‏‎‎‏‏‎‏‏‎‎‎‎‎‏‎‎‎‏‎‎Available via %1$s‎‏‎‎‏‎"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‏‎‏‎‎‏‎‏‎‏‎‏‏‎‏‏‏‏‏‏‎‏‎‏‏‏‎‏‏‎‏‏‎‏‏‏‏‏‎‎‏‏‏‏‎‎‎‏‏‎‏‏‏‏‏‎‏‎‏‏‎Tap to sign up‎‏‎‎‏‎"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‎‎‏‎‎‎‎‏‏‎‏‏‎‎‏‏‎‎‏‏‏‏‏‏‎‎‏‎‏‏‎‎‎‎‏‎‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‏‎‏‏‎Connected, no internet‎‏‎‎‏‎"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‎‏‏‏‎‎‎‏‎‎‏‎‎‏‎‎‏‏‎‏‎‎‎‎‏‎‏‎‎‏‏‎‎‏‎‎‎‎‏‏‎‏‏‎‎‎‏‏‎‏‎‎‎‎‎‎‎‏‏‎Private DNS server cannot be accessed‎‏‎‎‏‎"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‎‎‎‎‎‏‏‏‎‎‎‏‎‎‏‎‏‏‎‏‏‎‎‏‎‎‏‎‏‏‎‎‏‎‏‏‎‏‎‏‏‎‎‎‏‎‎‎‏‏‏‎‎‏‎‎‏‎‏‎‎Limited connection‎‏‎‎‏‎"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‎‏‎‎‏‎‏‏‏‏‎‎‎‎‎‏‎‎‏‏‏‎‏‏‏‎‏‎‎‎‏‏‎‎‎‎‎‏‎‎‏‎‎‏‏‎‎‏‎‎‏‏‎‏‏‏‏‎‎‎‎No internet‎‏‎‎‏‎"</string>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index c6dfdd3..7d28a32 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Conexión a través de <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Disponible a través de %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Presiona para registrarte"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Conectado pero sin conexión a Internet"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"No se puede acceder al servidor DNS privado"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Conexión limitada"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Sin Internet"</string>
diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml
index ba4a9ff..b35696f5 100644
--- a/packages/SettingsLib/res/values-es/strings.xml
+++ b/packages/SettingsLib/res/values-es/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Conectado a través de <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Disponible a través de %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Toca para registrarte"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Conexión sin Internet"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"No se ha podido acceder al servidor DNS privado"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Conexión limitada"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Sin Internet"</string>
diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml
index df3b792..b6c112b 100644
--- a/packages/SettingsLib/res/values-et/strings.xml
+++ b/packages/SettingsLib/res/values-et/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Ühendatud võrgu <xliff:g id="NAME">%1$s</xliff:g> kaudu"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Saadaval üksuse %1$s kaudu"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Puudutage registreerumiseks"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Ühendatud, Interneti-ühendus puudub"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Privaatsele DNS-serverile ei pääse juurde"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Piiratud ühendus"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Interneti-ühendus puudub"</string>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index fcb320f..4fd9add 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> bidez konektatuta"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s bidez erabilgarri"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Sakatu erregistratzeko"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Konektatuta; ezin da atzitu Internet"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Ezin da atzitu DNS zerbitzari pribatua"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Konexio mugatua"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Ez dago Interneteko konexiorik"</string>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index 261a438..32a98be 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"متصل شده ازطریق <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"‏در دسترس از طریق %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"برای ثبت‌نام ضربه بزنید"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"متصل، بدون اینترنت"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"‏سرور DNS خصوصی قابل دسترسی نیست"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"اتصال محدود"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"عدم دسترسی به اینترنت"</string>
diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml
index 4ccf430..84c5372 100644
--- a/packages/SettingsLib/res/values-fi/strings.xml
+++ b/packages/SettingsLib/res/values-fi/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Yhdistetty (<xliff:g id="NAME">%1$s</xliff:g>)"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Käytettävissä seuraavan kautta: %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Rekisteröidy napauttamalla"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Yhdistetty, ei internetyhteyttä"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Ei pääsyä yksityiselle DNS-palvelimelle"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Rajallinen yhteys"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Ei internetyhteyttä"</string>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index 84a9797..995eab6 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Connecté sur le réseau <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Accessible par %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Toucher pour vous connecter"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Connecté, aucun accès à Internet"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Impossible d\'accéder au serveur DNS privé"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Connexion limitée"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Aucune connexion Internet"</string>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index 030a7f9..2afacab 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Connecté via <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Disponible via %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Appuyez ici pour vous connecter"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Connecté, aucun accès à Internet"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Impossible d\'accéder au serveur DNS privé"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Connexion limitée"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Aucun accès à Internet"</string>
@@ -57,7 +58,7 @@
     <string name="osu_sign_up_complete" msgid="7640183358878916847">"Inscription terminée. Connexion…"</string>
     <string name="speed_label_very_slow" msgid="8526005255731597666">"Très lente"</string>
     <string name="speed_label_slow" msgid="6069917670665664161">"Lente"</string>
-    <string name="speed_label_okay" msgid="1253594383880810424">"Correct"</string>
+    <string name="speed_label_okay" msgid="1253594383880810424">"Correcte"</string>
     <string name="speed_label_medium" msgid="9078405312828606976">"Moyenne"</string>
     <string name="speed_label_fast" msgid="2677719134596044051">"Élevée"</string>
     <string name="speed_label_very_fast" msgid="8215718029533182439">"Très rapide"</string>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index af033cf..a106e60 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Wifi conectada a través de <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Dispoñible a través de %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Toca para rexistrarte"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Conexión sen Internet"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Non se puido acceder ao servidor DNS privado"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Pouca conexión"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Non hai conexión a Internet"</string>
@@ -316,7 +317,7 @@
     <string name="usb_audio_disable_routing" msgid="3367656923544254975">"Desactivar encamiñamento audio USB"</string>
     <string name="usb_audio_disable_routing_summary" msgid="8768242894849534699">"Desactiva o encamiñamento automático a periféricos de audio USB"</string>
     <string name="debug_layout" msgid="1659216803043339741">"Mostrar límites de deseño"</string>
-    <string name="debug_layout_summary" msgid="8825829038287321978">"Mostra os límites dos clips, as marxes, etc."</string>
+    <string name="debug_layout_summary" msgid="8825829038287321978">"Mostra os límites dos clips, as marxes etc."</string>
     <string name="force_rtl_layout_all_locales" msgid="8690762598501599796">"Forzar dirección do deseño RTL"</string>
     <string name="force_rtl_layout_all_locales_summary" msgid="6663016859517239880">"Forza a dirección de pantalla a RTL (dereita a esquerda) para todas as configuración rexionais"</string>
     <string name="force_msaa" msgid="4081288296137775550">"Forzar MSAA 4x"</string>
diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml
index 87fd876..5f2d5cd 100644
--- a/packages/SettingsLib/res/values-gu/strings.xml
+++ b/packages/SettingsLib/res/values-gu/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> દ્વારા કનેક્ટ થયેલ"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s દ્વારા ઉપલબ્ધ"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"સાઇન અપ કરવા માટે ટૅપ કરો"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"કનેક્ટ કર્યું, કોઈ ઇન્ટરનેટ નથી"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"ખાનગી DNS સર્વર ઍક્સેસ કરી શકાતા નથી"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"મર્યાદિત કનેક્શન"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"ઇન્ટરનેટ ઍક્સેસ નથી"</string>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index 02b5c96..d60eead 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> के ज़रिए कनेक्ट किया गया"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s के द्वारा उपलब्ध"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"साइन अप करने के लिए टैप करें"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"कनेक्ट हो गया है, लेकिन इंटरनेट नहीं है"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"निजी डीएनएस सर्वर को ऐक्सेस नहीं किया जा सकता"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"सीमित कनेक्शन"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"इंटरनेट कनेक्शन नहीं है"</string>
diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml
index 598cfe2..28e3460 100644
--- a/packages/SettingsLib/res/values-hr/strings.xml
+++ b/packages/SettingsLib/res/values-hr/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Povezan putem mreže <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Dostupno putem %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Dodirnite da biste se registrirali"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Povezano, bez interneta"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Nije moguće pristupiti privatnom DNS poslužitelju"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Ograničena veza"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nema interneta"</string>
diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml
index d970c73..22e03a7 100644
--- a/packages/SettingsLib/res/values-hu/strings.xml
+++ b/packages/SettingsLib/res/values-hu/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Kapcsolódva a következőn keresztül: <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Elérhető a következőn keresztül: %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Koppintson a regisztrációhoz"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Csatlakozva, nincs internet-hozzáférés"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"A privát DNS-kiszolgálóhoz nem lehet hozzáférni"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Korlátozott kapcsolat"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nincs internetkapcsolat"</string>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index 9086934..ecb615a 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Միացված է <xliff:g id="NAME">%1$s</xliff:g>-ի միջոցով"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Հասանելի է %1$s-ի միջոցով"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Հպեք՝ գրանցվելու համար"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Միացված է, սակայն ինտերնետ կապ չկա"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Մասնավոր DNS սերվերն անհասանելի է"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Սահմանափակ կապ"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Ինտերնետ կապ չկա"</string>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index 3c1504c..cfbda04 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Tersambung melalui <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Tersedia melalui %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Ketuk untuk mendaftar"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Tersambung, tidak ada internet"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Server DNS pribadi tidak dapat diakses"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Koneksi terbatas"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Tidak ada internet"</string>
diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml
index 438e900..ba4c009 100644
--- a/packages/SettingsLib/res/values-is/strings.xml
+++ b/packages/SettingsLib/res/values-is/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Tenging í gegnum <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Í boði í gegnum %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Ýttu til að skrá þig"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Tengt, enginn netaðgangur"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Ekki næst í DNS-einkaþjón"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Takmörkuð tenging"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Engin nettenging"</string>
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index 114b33b..50e5777 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Connesso tramite <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Disponibile tramite %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Tocca per registrarti"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Connesso, senza Internet"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Non è possibile accedere al server DNS privato"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Connessione limitata"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nessuna connessione a Internet"</string>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index 62085a8..9f1e457 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"מחוברת באמצעות <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"‏זמינה דרך %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"יש להקיש כדי להירשם"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"מחובר. אין אינטרנט"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"‏לא ניתן לגשת לשרת DNS הפרטי"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"חיבור מוגבל"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"אין אינטרנט"</string>
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index 28b98ee..47020ed 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> で接続しました"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s経由で使用可能"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"タップして登録してください"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"接続済み、インターネット接続なし"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"プライベート DNS サーバーにアクセスできません"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"接続が制限されています"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"インターネット未接続"</string>
diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml
index 6f5d0b3..116488d 100644
--- a/packages/SettingsLib/res/values-ka/strings.xml
+++ b/packages/SettingsLib/res/values-ka/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"დაკავშირებულია <xliff:g id="NAME">%1$s</xliff:g>-ით"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"ხელმისაწვდომია %1$s-ით"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"შეეხეთ რეგისტრაციისთვის"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"დაკავშირებულია, ინტერნეტის გარეშე"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"პირად DNS სერვერზე წვდომა შეუძლებელია"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"შეზღუდული კავშირი"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"ინტერნეტ-კავშირი არ არის"</string>
diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml
index 3fe426e..3c4fdb7 100644
--- a/packages/SettingsLib/res/values-kk/strings.xml
+++ b/packages/SettingsLib/res/values-kk/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> арқылы жалғанған"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s арқылы қолжетімді"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Тіркелу үшін түртіңіз."</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Қосылған, интернет жоқ"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Жеке DNS серверіне кіру мүмкін емес."</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Шектеулі байланыс"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Интернетпен байланыс жоқ"</string>
diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml
index 24bfa35..87a4f19 100644
--- a/packages/SettingsLib/res/values-km/strings.xml
+++ b/packages/SettingsLib/res/values-km/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"ភ្ជាប់​តាម <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"មានតាមរយៈ %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"ចុច​ដើម្បី​ចុះឈ្មោះ"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"បាន​ភ្ជាប់ ប៉ុន្តែ​គ្មាន​អ៊ីនធឺណិត​ទេ"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"មិនអាច​ចូលប្រើ​ម៉ាស៊ីនមេ DNS ឯកជន​បានទេ"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"ការតភ្ជាប់មានកម្រិត"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"គ្មាន​អ៊ីនធឺណិតទេ"</string>
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index 0699bbc..144dddb 100644
--- a/packages/SettingsLib/res/values-kn/strings.xml
+++ b/packages/SettingsLib/res/values-kn/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> ಆ್ಯಪ್ ಮೂಲಕ ಸಂಪರ್ಕಿಸಲಾಗಿದೆ"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s ಮೂಲಕ ಲಭ್ಯವಿದೆ"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"ಸೈನ್ ಅಪ್ ಮಾಡಲು ಟ್ಯಾಪ್‌ ಮಾಡಿ"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"ಸಂಪರ್ಕಪಡಿಸಲಾಗಿದೆ, ಇಂಟರ್ನೆಟ್ ಇಲ್ಲ"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"ಖಾಸಗಿ DNS ಸರ್ವರ್ ಅನ್ನು ಪ್ರವೇಶಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"ಸೀಮಿತ ಸಂಪರ್ಕ"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"ಇಂಟರ್ನೆಟ್ ಇಲ್ಲ"</string>
diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml
index 25e9cfe..df9f21d 100644
--- a/packages/SettingsLib/res/values-ko/strings.xml
+++ b/packages/SettingsLib/res/values-ko/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g>을(를) 통해 연결됨"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s을(를) 통해 사용 가능"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"탭하여 가입"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"연결됨, 인터넷 사용 불가"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"비공개 DNS 서버에 액세스할 수 없습니다."</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"제한된 연결"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"인터넷 연결 없음"</string>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index 3dfce1e..13c4144 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> аркылуу туташты"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s аркылуу жеткиликтүү"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Катталуу үчүн таптап коюңуз"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Туташып турат, Интернет жок"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Жеке DNS сервери жеткиликсиз"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Байланыш чектелген"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Интернет жок"</string>
diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml
index 48e5093..48224f4 100644
--- a/packages/SettingsLib/res/values-lo/strings.xml
+++ b/packages/SettingsLib/res/values-lo/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"ເຊື່ອມ​ຕໍ່​ຜ່ານ <xliff:g id="NAME">%1$s</xliff:g> ແລ້ວ"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"ມີ​ໃຫ້​ຜ່ານ %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"ແຕະເພື່ອສະໝັກ"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"ເຊື່ອມຕໍ່ແລ້ວ, ບໍ່ມີອິນເຕີເນັດ"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"ບໍ່ສາມາດເຂົ້າເຖິງເຊີບເວີ DNS ສ່ວນຕົວໄດ້"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"ການເຊື່ອມຕໍ່ຈຳກັດ"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"ບໍ່ມີອິນເຕີເນັດ"</string>
diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml
index 8b3fbad..d080334 100644
--- a/packages/SettingsLib/res/values-lt/strings.xml
+++ b/packages/SettingsLib/res/values-lt/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Prisijungta naudojant programą „<xliff:g id="NAME">%1$s</xliff:g>“"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Pasiekiama naudojant „%1$s“"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Palieskite, kad prisiregistruotumėte"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Prisijungta, nėra interneto"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Privataus DNS serverio negalima pasiekti"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Ribotas ryšys"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nėra interneto ryšio"</string>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index 4fc5b22..e13a50d 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Savienojums ar <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Pieejams, izmantojot %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Pieskarieties, lai reģistrētos"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Savienojums izveidots, nav piekļuves internetam"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Nevar piekļūt privātam DNS serverim."</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Ierobežots savienojums"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nav piekļuves internetam"</string>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index 288e526..d027ffe 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Поврзано преку <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Достапно преку %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Допрете за да се регистрирате"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Поврзана, нема интернет"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Не може да се пристапи до приватниот DNS-сервер"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Ограничена врска"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Нема интернет"</string>
diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml
index e775297..e2a3d9f 100644
--- a/packages/SettingsLib/res/values-ml/strings.xml
+++ b/packages/SettingsLib/res/values-ml/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> മുഖേന കണക്‌റ്റ് ചെയ്‌തു"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s വഴി ലഭ്യം"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"സൈൻ അപ്പ് ചെയ്യാൻ ടാപ്പ് ചെയ്യുക"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"കണക്റ്റ് ചെയ്‌തു, ഇന്റർനെറ്റ് ഇല്ല"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"സ്വകാര്യ DNS സെർവർ ആക്‌സസ് ചെയ്യാനാവില്ല"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"പരിമിത കണക്‌ഷൻ"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"ഇന്റർനെറ്റ് ഇല്ല"</string>
diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml
index ba69f9b..dd08c9e 100644
--- a/packages/SettingsLib/res/values-mn/strings.xml
+++ b/packages/SettingsLib/res/values-mn/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g>-р холбогдсон"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s-р боломжтой"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Бүртгүүлэхийн тулд товшино уу"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Холбогдсон хэдий ч интернет алга"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Хувийн DNS серверт хандах боломжгүй байна"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Хязгаарлагдмал холболт"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Интернэт алга"</string>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index 1930cdf..fb7cd1f 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> द्वारे कनेक्ट केले"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s द्वारे उपलब्‍ध"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"साइन अप करण्यासाठी टॅप करा"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"कनेक्‍ट केले, इंटरनेट नाही"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"खाजगी DNS सर्व्हर ॲक्सेस करू शकत नाही"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"मर्यादित कनेक्शन"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"इंटरनेट नाही"</string>
diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml
index e493188..72b15ff 100644
--- a/packages/SettingsLib/res/values-ms/strings.xml
+++ b/packages/SettingsLib/res/values-ms/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Disambungkan melalui <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Tersedia melalui %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Ketik untuk daftar"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Disambungkan, tiada Internet"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Pelayan DNS peribadi tidak boleh diakses"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Sambungan terhad"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Tiada Internet"</string>
diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml
index 2c4b32c..24017ec 100644
--- a/packages/SettingsLib/res/values-my/strings.xml
+++ b/packages/SettingsLib/res/values-my/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> မှတစ်ဆင့် ချိတ်ဆက်ထားသည်"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s မှတစ်ဆင့်ရနိုင်သည်"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"အကောင့်ဖွင့်ရန် တို့ပါ"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"ချိတ်ဆက်ထားသည်၊ အင်တာနက်မရှိ"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"သီးသန့် ဒီအန်အက်စ် (DNS) ဆာဗာကို သုံး၍မရပါ။"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"ချိတ်ဆက်မှု ကန့်သတ်ထားသည်"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"အင်တာနက် မရှိပါ"</string>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index 093c06f..aca13a7 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Tilkoblet via <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Tilgjengelig via %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Trykk for å registrere deg"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Tilkoblet – ingen Internett-tilgang"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Den private DNS-tjeneren kan ikke nås"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Begrenset tilkobling"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Ingen internettilkobling"</string>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index fb8b737..b2c7518 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> मार्फत जडान गरिएको"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s मार्फत उपलब्ध"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"साइन अप गर्न ट्याप गर्नुहोस्"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"जडान गरियो तर इन्टरनेट छैन"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"निजी DNS सर्भरमाथि पहुँच प्राप्त गर्न सकिँदैन"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"सीमित जडान"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"इन्टरनेटमाथिको पहुँच छैन"</string>
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index 267dab4..c74683b 100644
--- a/packages/SettingsLib/res/values-nl/strings.xml
+++ b/packages/SettingsLib/res/values-nl/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Verbonden via <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Beschikbaar via %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Tik om aan te melden"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Verbonden, geen internet"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Geen toegang tot privé-DNS-server"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Beperkte verbinding"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Geen internet"</string>
diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml
index d8ae3bf..704fc42 100644
--- a/packages/SettingsLib/res/values-or/strings.xml
+++ b/packages/SettingsLib/res/values-or/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> ଦ୍ବାରା ସଂଯୋଗ କରାଯାଇଛି"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s ମାଧ୍ୟମରେ ଉପଲବ୍ଧ"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"ସାଇନ୍ ଅପ୍ ପାଇଁ ଟାପ୍ କରନ୍ତୁ"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"ସଂଯୁକ୍ତ, ଇଣ୍ଟର୍‌ନେଟ୍‌ ନାହିଁ"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"ବ୍ୟକ୍ତିଗତ DNS ସର୍ଭର୍ ଆକ୍ସେସ୍ କରିହେବ ନାହିଁ"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"ସୀମିତ ସଂଯୋଗ"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"କୌଣସି ଇଣ୍ଟରନେଟ୍‌ ନାହିଁ"</string>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index 6a78486..bf5a32c 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> ਰਾਹੀਂ ਕਨੈਕਟ ਕੀਤਾ ਗਿਆ"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s ਰਾਹੀਂ ਉਪਲਬਧ"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"ਸਾਈਨ-ਅੱਪ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"ਕਨੈਕਟ ਕੀਤਾ, ਕੋਈ ਇੰਟਰਨੈੱਟ ਨਹੀਂ"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"ਨਿੱਜੀ ਡੋਮੇਨ ਨਾਮ ਪ੍ਰਣਾਲੀ (DNS) ਸਰਵਰ \'ਤੇ ਪਹੁੰਚ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕੀ"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"ਸੀਮਤ ਕਨੈਕਸ਼ਨ"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"ਇੰਟਰਨੈੱਟ ਨਹੀਂ"</string>
diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml
index 8c5547c..d907760 100644
--- a/packages/SettingsLib/res/values-pl/strings.xml
+++ b/packages/SettingsLib/res/values-pl/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Połączenie przez: <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Dostępne przez %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Kliknij, by się zarejestrować"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Połączono, brak internetu"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Brak dostępu do prywatnego serwera DNS"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Ograniczone połączenie"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Brak internetu"</string>
diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml
index 8c03616..e61c84e 100644
--- a/packages/SettingsLib/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Conectado via <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Disponível via %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Toque para se inscrever"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Conectada, sem Internet"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Não é possível acessar o servidor DNS privado"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Conexão limitada"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Sem Internet"</string>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index 6aeff1c..8ceeb63 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Ligado via <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Disponível através de %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Toque para se inscrever"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Ligado, sem Internet."</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Não é possível aceder ao servidor DNS."</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Ligação limitada"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Sem Internet"</string>
diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml
index 8c03616..e61c84e 100644
--- a/packages/SettingsLib/res/values-pt/strings.xml
+++ b/packages/SettingsLib/res/values-pt/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Conectado via <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Disponível via %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Toque para se inscrever"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Conectada, sem Internet"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Não é possível acessar o servidor DNS privado"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Conexão limitada"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Sem Internet"</string>
diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml
index 387441f..5ad8dfd 100644
--- a/packages/SettingsLib/res/values-ro/strings.xml
+++ b/packages/SettingsLib/res/values-ro/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Conectat prin <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Disponibilă prin %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Atingeți pentru a vă înscrie"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Conectată, fără internet"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Serverul DNS privat nu poate fi accesat"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Conexiune limitată"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Fără conexiune la internet"</string>
diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml
index 361e29f..104efdc 100644
--- a/packages/SettingsLib/res/values-ru/strings.xml
+++ b/packages/SettingsLib/res/values-ru/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Подключено через приложение \"<xliff:g id="NAME">%1$s</xliff:g>\"."</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Доступно через %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Нажмите, чтобы зарегистрироваться"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Подключено, без доступа к Интернету"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Доступа к частному DNS-серверу нет."</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Подключение к сети ограничено."</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Нет подключения к Интернету"</string>
diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml
index faa848f..f5dad87 100644
--- a/packages/SettingsLib/res/values-si/strings.xml
+++ b/packages/SettingsLib/res/values-si/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> හරහා සම්බන්ධයි"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s හරහා ලබා ගැනීමට හැකිය"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"ලියාපදිංචි වීමට තට්ටු කරන්න"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"සම්බන්ධයි, අන්තර්ජාලය නැත"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"පුද්ගලික DNS සේවාදායකයට ප්‍රවේශ වීමට නොහැකිය"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"සීමිත සම්බන්ධතාව"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"අන්තර්ජාලය නැත"</string>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index 2035d88c..527dafb 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Pripojené prostredníctvom siete <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"K dispozícii prostredníctvom %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Prihláste sa klepnutím"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Pripojené, žiadny internet"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"K súkromnému serveru DNS sa nepodarilo získať prístup"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Obmedzené pripojenie"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Žiadny internet"</string>
diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml
index 2889619..4816950 100644
--- a/packages/SettingsLib/res/values-sl/strings.xml
+++ b/packages/SettingsLib/res/values-sl/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Povezava vzpostavljena prek omrežja <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Na voljo prek: %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Dotaknite se, če se želite registrirati"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Vzpostavljena povezava, brez interneta"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Do zasebnega strežnika DNS ni mogoče dostopati"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Omejena povezava"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Brez internetne povezave"</string>
diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml
index ccd4e306..12511dd3 100644
--- a/packages/SettingsLib/res/values-sq/strings.xml
+++ b/packages/SettingsLib/res/values-sq/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Lidhur përmes <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"E mundshme përmes %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Trokit për t\'u regjistruar"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"U lidh, por nuk ka internet"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Serveri privat DNS nuk mund të qaset"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Lidhje e kufizuar"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nuk ka internet"</string>
diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml
index 08e2bc8..4724be5 100644
--- a/packages/SettingsLib/res/values-sr/strings.xml
+++ b/packages/SettingsLib/res/values-sr/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Повезано преко: <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Доступна је преко приступне тачке %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Додирните да бисте се регистровали"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Веза је успостављена, нема интернета"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Приступ приватном DNS серверу није успео"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Ограничена веза"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Нема интернета"</string>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index c0cdbc9..9d1bc42 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Anslutet via <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Tillgängligt via %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Tryck för att logga in"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Ansluten, inget internet"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Det går inte att komma åt den privata DNS-servern."</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Begränsad anslutning"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Inget internet"</string>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index f00dea3..1e2489f 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Imeunganishwa kupitia <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Inapatikana kupitia %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Gusa ili ujisajili"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Imeunganishwa, hakuna intaneti"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Seva ya faragha ya DNS haiwezi kufikiwa"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Muunganisho hafifu"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Hakuna intaneti"</string>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index 52e0363..fcb801b 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> மூலம் இணைக்கப்பட்டது"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s வழியாகக் கிடைக்கிறது"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"பதிவு செய்யத் தட்டவும்"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"இணைக்கப்பட்டுள்ளது, ஆனால் இண்டர்நெட் இல்லை"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"தனிப்பட்ட DNS சேவையகத்தை அணுக இயலாது"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"வரம்பிற்கு உட்பட்ட இணைப்பு"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"இணைய இணைப்பு இல்லை"</string>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index a39c4e1..7a80451 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> ద్వారా కనెక్ట్ చేయబడింది"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s ద్వారా అందుబాటులో ఉంది"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"సైన్ అప్ చేయడానికి నొక్కండి"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"కనెక్ట్ చేయబడింది, ఇంటర్నెట్ లేదు"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"ప్రైవేట్ DNS సర్వర్‌ను యాక్సెస్ చేయడం సాధ్యపడదు"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"పరిమిత కనెక్షన్"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"ఇంటర్నెట్ లేదు"</string>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index 635d77a..130e1c4 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"เชื่อมต่อแล้วผ่าน <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"พร้อมใช้งานผ่านทาง %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"แตะเพื่อลงชื่อสมัครใช้"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"เชื่อมต่อแล้ว ไม่พบอินเทอร์เน็ต"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"เข้าถึงเซิร์ฟเวอร์ DNS ไม่ได้"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"การเชื่อมต่อที่จำกัด"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"ไม่มีอินเทอร์เน็ต"</string>
diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml
index 3f7f0ff..bc1a405 100644
--- a/packages/SettingsLib/res/values-tl/strings.xml
+++ b/packages/SettingsLib/res/values-tl/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Nakakonekta sa pamamagitan ng <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Available sa pamamagitan ng %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"I-tap para mag-sign up"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Nakakonekta, walang internet"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Hindi ma-access ang pribadong DNS server"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Limitadong koneksyon"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Walang internet"</string>
diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml
index 7ad6fcd..4e263ef 100644
--- a/packages/SettingsLib/res/values-tr/strings.xml
+++ b/packages/SettingsLib/res/values-tr/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> ile bağlandı"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s üzerinden kullanılabilir"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Kaydolmak için dokunun"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Bağlı, internet yok"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Gizli DNS sunucusuna erişilemiyor"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Sınırlı bağlantı"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"İnternet yok"</string>
diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml
index b5dd618..ad9f600 100644
--- a/packages/SettingsLib/res/values-uk/strings.xml
+++ b/packages/SettingsLib/res/values-uk/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Підключено через додаток <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Доступ через %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Торкніться, щоб увійти"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Під’єднано, але немає доступу до Інтернету"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Немає доступу до приватного DNS-сервера"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Обмежене з’єднання"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Немає Інтернету"</string>
diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml
index ef9b2a1..b995da1 100644
--- a/packages/SettingsLib/res/values-ur/strings.xml
+++ b/packages/SettingsLib/res/values-ur/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> کے ذریعے منسلک"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"‏دستیاب بذریعہ ‎%1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"سائن اپ کے لیے تھپتھپائیں"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"منسلک، انٹرنیٹ نہیں ہے"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"‏نجی DNS سرور تک رسائی حاصل نہیں کی جا سکی"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"محدود کنکشن"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"انٹرنیٹ نہیں ہے"</string>
diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml
index b202d64..f02b639 100644
--- a/packages/SettingsLib/res/values-uz/strings.xml
+++ b/packages/SettingsLib/res/values-uz/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> orqali ulandi"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s orqali ishlaydi"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Yozilish uchun bosing"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Ulangan, lekin internet aloqasi yo‘q"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Xususiy DNS server ishlamayapti"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Cheklangan aloqa"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Internet yo‘q"</string>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index 5bcff5a..8fcf1f2 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Đã kết nối qua <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Có sẵn qua %1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Nhấn để đăng ký"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Đã kết nối, không có Internet"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Không thể truy cập máy chủ DNS riêng tư"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Kết nối giới hạn"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Không có Internet"</string>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index 418370b..755cbee 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"已通过<xliff:g id="NAME">%1$s</xliff:g>连接到网络"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"可通过%1$s连接"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"点按即可注册"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"已连接,但无法访问互联网"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"无法访问私人 DNS 服务器"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"网络连接受限"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"无法访问互联网"</string>
diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml
index 79b5579..e50acf5 100644
--- a/packages/SettingsLib/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"已透過「<xliff:g id="NAME">%1$s</xliff:g>」連線"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"可透過 %1$s 連線"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"輕按即可登入"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"已連線,但沒有互聯網"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"無法存取私人 DNS 伺服器"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"連線受限"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"沒有互聯網連線"</string>
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index 47ab764..7df6fed 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"透過「<xliff:g id="NAME">%1$s</xliff:g>」連線"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"可透過 %1$s 使用"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"輕觸即可註冊"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"已連線,沒有網際網路"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"無法存取私人 DNS 伺服器"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"連線能力受限"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"沒有網際網路連線"</string>
diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml
index 87f45de..46dffbe 100644
--- a/packages/SettingsLib/res/values-zu/strings.xml
+++ b/packages/SettingsLib/res/values-zu/strings.xml
@@ -42,7 +42,8 @@
     <string name="connected_via_app" msgid="3532267661404276584">"Ixhumeke nge-<xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Iyatholakala nge-%1$s"</string>
     <string name="tap_to_sign_up" msgid="5356397741063740395">"Thepha ukuze ubhalisele"</string>
-    <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Kuxhunyiwe, ayikho i-inthanethi"</string>
+    <!-- no translation found for wifi_connected_no_internet (5087420713443350646) -->
+    <skip />
     <string name="private_dns_broken" msgid="1984159464346556931">"Iseva eyimfihlo ye-DNS ayikwazi ukufinyelelwa"</string>
     <string name="wifi_limited_connection" msgid="1184778285475204682">"Iqoqo elikhawulelwe"</string>
     <string name="wifi_status_no_internet" msgid="3799933875988829048">"Ayikho i-inthanethi"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index e008cd03..a4be46c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -40,15 +40,16 @@
     final Executor mExecutor = Executors.newSingleThreadExecutor();
     @VisibleForTesting
     MediaRouter2Manager mRouterManager;
+    @VisibleForTesting
+    String mPackageName;
 
-    private String mPackageName;
     private MediaDevice mCurrentConnectedDevice;
 
     public InfoMediaManager(Context context, String packageName, Notification notification) {
         super(context, notification);
 
         mRouterManager = MediaRouter2Manager.getInstance(context);
-        if (packageName != null) {
+        if (!TextUtils.isEmpty(packageName)) {
             mPackageName = packageName;
         }
     }
@@ -57,6 +58,7 @@
     public void startScan() {
         mMediaDevices.clear();
         mRouterManager.registerCallback(mExecutor, mMediaRouterCallback);
+        refreshDevices();
     }
 
     @VisibleForTesting
@@ -79,21 +81,37 @@
         return mCurrentConnectedDevice;
     }
 
-    class RouterManagerCallback extends MediaRouter2Manager.Callback {
-
-        private void refreshDevices() {
-            mMediaDevices.clear();
-            mCurrentConnectedDevice = null;
-            for (MediaRoute2Info route : mRouterManager.getAvailableRoutes(mPackageName)) {
-                final MediaDevice device = new InfoMediaDevice(mContext, mRouterManager, route,
-                        mPackageName);
-                if (TextUtils.equals(route.getClientPackageName(), mPackageName)) {
-                    mCurrentConnectedDevice = device;
-                }
-                mMediaDevices.add(device);
-            }
-            dispatchDeviceListAdded();
+    private void refreshDevices() {
+        mMediaDevices.clear();
+        mCurrentConnectedDevice = null;
+        if (TextUtils.isEmpty(mPackageName)) {
+            buildAllRoutes();
+        } else {
+            buildAvailableRoutes();
         }
+        dispatchDeviceListAdded();
+    }
+
+    private void buildAllRoutes() {
+        for (MediaRoute2Info route : mRouterManager.getAllRoutes()) {
+            final MediaDevice device = new InfoMediaDevice(mContext, mRouterManager, route,
+                    mPackageName);
+            mMediaDevices.add(device);
+        }
+    }
+
+    private void buildAvailableRoutes() {
+        for (MediaRoute2Info route : mRouterManager.getAvailableRoutes(mPackageName)) {
+            final MediaDevice device = new InfoMediaDevice(mContext, mRouterManager, route,
+                    mPackageName);
+            if (TextUtils.equals(route.getClientPackageName(), mPackageName)) {
+                mCurrentConnectedDevice = device;
+            }
+            mMediaDevices.add(device);
+        }
+    }
+
+    class RouterManagerCallback extends MediaRouter2Manager.Callback {
 
         @Override
         public void onRoutesAdded(List<MediaRoute2Info> routes) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index e85a102..50196d2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -18,7 +18,6 @@
 import android.app.Notification;
 import android.bluetooth.BluetoothProfile;
 import android.content.Context;
-import android.text.TextUtils;
 import android.util.Log;
 
 import androidx.annotation.IntDef;
@@ -162,10 +161,8 @@
         mMediaDevices.clear();
         mBluetoothMediaManager.registerCallback(mMediaDeviceCallback);
         mBluetoothMediaManager.startScan();
-        if (!TextUtils.isEmpty(mPackageName)) {
-            mInfoMediaManager.registerCallback(mMediaDeviceCallback);
-            mInfoMediaManager.startScan();
-        }
+        mInfoMediaManager.registerCallback(mMediaDeviceCallback);
+        mInfoMediaManager.startScan();
     }
 
     private void addPhoneDeviceIfNecessary() {
@@ -208,10 +205,8 @@
     public void stopScan() {
         mBluetoothMediaManager.unregisterCallback(mMediaDeviceCallback);
         mBluetoothMediaManager.stopScan();
-        if (!TextUtils.isEmpty(mPackageName)) {
-            mInfoMediaManager.unregisterCallback(mMediaDeviceCallback);
-            mInfoMediaManager.stopScan();
-        }
+        mInfoMediaManager.unregisterCallback(mMediaDeviceCallback);
+        mInfoMediaManager.stopScan();
     }
 
     /**
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index 67f6dd90..3726fb2 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -74,7 +74,7 @@
     }
 
     @Test
-    public void onRouteAdded_shouldAddMediaDevice() {
+    public void onRouteAdded_getAvailableRoutes_shouldAddMediaDevice() {
         final MediaRoute2Info info = mock(MediaRoute2Info.class);
         when(info.getId()).thenReturn(TEST_ID);
         when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
@@ -95,6 +95,27 @@
     }
 
     @Test
+    public void onRouteAdded_buildAllRoutes_shouldAddMediaDevice() {
+        final MediaRoute2Info info = mock(MediaRoute2Info.class);
+        when(info.getId()).thenReturn(TEST_ID);
+        when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+
+        final List<MediaRoute2Info> routes = new ArrayList<>();
+        routes.add(info);
+        when(mRouterManager.getAllRoutes()).thenReturn(routes);
+
+        final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID);
+        assertThat(mediaDevice).isNull();
+
+        mInfoMediaManager.mPackageName = "";
+        mInfoMediaManager.mMediaRouterCallback.onRoutesAdded(routes);
+
+        final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0);
+        assertThat(infoDevice.getId()).isEqualTo(TEST_ID);
+        assertThat(mInfoMediaManager.mMediaDevices).hasSize(routes.size());
+    }
+
+    @Test
     public void onControlCategoriesChanged_samePackageName_shouldAddMediaDevice() {
         final MediaRoute2Info info = mock(MediaRoute2Info.class);
         when(info.getId()).thenReturn(TEST_ID);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index cdf9728..3c52f54 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -45,6 +45,7 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.BackupUtils;
+import android.util.FeatureFlagUtils;
 import android.util.Log;
 import android.util.Slog;
 import android.view.Display;
@@ -280,6 +281,16 @@
         Settings.Secure.getMovedToGlobalSettings(movedToGlobal);
         Set<String> movedToSecure = getMovedToSecureSettings();
 
+        Set<String> preservedGlobalSettings = getSettingsToPreserveInRestore(
+                Settings.Global.CONTENT_URI);
+        Set<String> preservedSecureSettings = getSettingsToPreserveInRestore(
+                Settings.Secure.CONTENT_URI);
+        Set<String> preservedSystemSettings = getSettingsToPreserveInRestore(
+                Settings.System.CONTENT_URI);
+        Set<String> preservedSettings = new HashSet<>(preservedGlobalSettings);
+        preservedSettings.addAll(preservedSecureSettings);
+        preservedSettings.addAll(preservedSystemSettings);
+
         byte[] restoredWifiSupplicantData = null;
         byte[] restoredWifiIpConfigData = null;
 
@@ -300,7 +311,8 @@
                 case KEY_SYSTEM :
                     restoreSettings(data, Settings.System.CONTENT_URI, movedToGlobal,
                             movedToSecure, R.array.restore_blocked_system_settings,
-                            dynamicBlockList);
+                            dynamicBlockList,
+                            preservedSystemSettings);
                     mSettingsHelper.applyAudioSettings();
                     break;
 
@@ -311,7 +323,8 @@
                             movedToGlobal,
                             null,
                             R.array.restore_blocked_secure_settings,
-                            dynamicBlockList);
+                            dynamicBlockList,
+                            preservedSecureSettings);
                     break;
 
                 case KEY_GLOBAL :
@@ -321,7 +334,8 @@
                             null,
                             movedToSecure,
                             R.array.restore_blocked_global_settings,
-                            dynamicBlockList);
+                            dynamicBlockList,
+                            preservedGlobalSettings);
                     break;
 
                 case KEY_WIFI_SUPPLICANT :
@@ -368,7 +382,8 @@
                     restoreDeviceSpecificConfig(
                             restoredDeviceSpecificConfig,
                             R.array.restore_blocked_device_specific_settings,
-                            dynamicBlockList);
+                            dynamicBlockList,
+                            preservedSettings);
                     break;
 
                 default :
@@ -418,7 +433,7 @@
             in.readFully(buffer, 0, nBytes);
             restoreSettings(buffer, nBytes, Settings.System.CONTENT_URI, movedToGlobal,
                     movedToSecure, R.array.restore_blocked_system_settings,
-                    Collections.emptySet());
+                    Collections.emptySet(), Collections.emptySet());
 
             // secure settings
             nBytes = in.readInt();
@@ -432,7 +447,7 @@
                     movedToGlobal,
                     null,
                     R.array.restore_blocked_secure_settings,
-                    Collections.emptySet());
+                    Collections.emptySet(), Collections.emptySet());
 
             // Global only if sufficiently new
             if (version >= FULL_BACKUP_ADDED_GLOBAL) {
@@ -443,7 +458,7 @@
                 movedToGlobal.clear();  // no redirection; this *is* the global namespace
                 restoreSettings(buffer, nBytes, Settings.Global.CONTENT_URI, movedToGlobal,
                         movedToSecure, R.array.restore_blocked_global_settings,
-                        Collections.emptySet());
+                        Collections.emptySet(), Collections.emptySet());
             }
 
             // locale
@@ -608,6 +623,40 @@
     }
 
     /**
+     * Get names of the settings for which the current value should be preserved during restore.
+     */
+    private Set<String> getSettingsToPreserveInRestore(Uri settingsUri) {
+        if (!FeatureFlagUtils.isEnabled(getApplicationContext(),
+                FeatureFlagUtils.SETTINGS_DO_NOT_RESTORE_PRESERVED)) {
+            return Collections.emptySet();
+        }
+
+        Cursor cursor = getContentResolver().query(settingsUri, new String[] {
+                Settings.NameValueTable.NAME, Settings.NameValueTable.IS_PRESERVED_IN_RESTORE },
+                /* selection */ null, /* selectionArgs */ null, /* sortOrder */ null);
+
+        if (!cursor.moveToFirst()) {
+            Slog.i(TAG, "No settings to be preserved in restore");
+            return Collections.emptySet();
+        }
+
+        int nameIndex = cursor.getColumnIndex(Settings.NameValueTable.NAME);
+        int isPreservedIndex = cursor.getColumnIndex(
+                Settings.NameValueTable.IS_PRESERVED_IN_RESTORE);
+
+        Set<String> preservedSettings = new HashSet<>();
+        while (!cursor.isAfterLast()) {
+            if (Boolean.parseBoolean(cursor.getString(isPreservedIndex))) {
+                preservedSettings.add(getQualifiedKeyForSetting(cursor.getString(nameIndex),
+                        settingsUri));
+            }
+            cursor.moveToNext();
+        }
+
+        return preservedSettings;
+    }
+
+    /**
      * Serialize the owner info and other lock settings
      */
     private byte[] getLockSettings(@UserIdInt int userId) {
@@ -650,7 +699,8 @@
             HashSet<String> movedToGlobal,
             Set<String> movedToSecure,
             int blockedSettingsArrayId,
-            Set<String> dynamicBlockList) {
+            Set<String> dynamicBlockList,
+            Set<String> settingsToPreserve) {
         byte[] settings = new byte[data.getDataSize()];
         try {
             data.readEntityData(settings, 0, settings.length);
@@ -665,7 +715,8 @@
                 movedToGlobal,
                 movedToSecure,
                 blockedSettingsArrayId,
-                dynamicBlockList);
+                dynamicBlockList,
+                settingsToPreserve);
     }
 
     private void restoreSettings(
@@ -675,7 +726,8 @@
             HashSet<String> movedToGlobal,
             Set<String> movedToSecure,
             int blockedSettingsArrayId,
-            Set<String> dynamicBlockList) {
+            Set<String> dynamicBlockList,
+            Set<String> settingsToPreserve) {
         restoreSettings(
                 settings,
                 0,
@@ -684,10 +736,12 @@
                 movedToGlobal,
                 movedToSecure,
                 blockedSettingsArrayId,
-                dynamicBlockList);
+                dynamicBlockList,
+                settingsToPreserve);
     }
 
-    private void restoreSettings(
+    @VisibleForTesting
+    void restoreSettings(
             byte[] settings,
             int pos,
             int bytes,
@@ -695,31 +749,13 @@
             HashSet<String> movedToGlobal,
             Set<String> movedToSecure,
             int blockedSettingsArrayId,
-            Set<String> dynamicBlockList) {
+            Set<String> dynamicBlockList,
+            Set<String> settingsToPreserve) {
         if (DEBUG) {
             Log.i(TAG, "restoreSettings: " + contentUri);
         }
 
-        // Figure out the white list and redirects to the global table.  We restore anything
-        // in either the backup whitelist or the legacy-restore whitelist for this table.
-        final String[] whitelist;
-        Map<String, Validator> validators = null;
-        if (contentUri.equals(Settings.Secure.CONTENT_URI)) {
-            whitelist = ArrayUtils.concatElements(String.class, SecureSettings.SETTINGS_TO_BACKUP,
-                    Settings.Secure.LEGACY_RESTORE_SETTINGS,
-                    DeviceSpecificSettings.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP);
-            validators = SecureSettingsValidators.VALIDATORS;
-        } else if (contentUri.equals(Settings.System.CONTENT_URI)) {
-            whitelist = ArrayUtils.concatElements(String.class, SystemSettings.SETTINGS_TO_BACKUP,
-                    Settings.System.LEGACY_RESTORE_SETTINGS);
-            validators = SystemSettingsValidators.VALIDATORS;
-        } else if (contentUri.equals(Settings.Global.CONTENT_URI)) {
-            whitelist = ArrayUtils.concatElements(String.class, GlobalSettings.SETTINGS_TO_BACKUP,
-                    Settings.Global.LEGACY_RESTORE_SETTINGS);
-            validators = GlobalSettingsValidators.VALIDATORS;
-        } else {
-            throw new IllegalArgumentException("Unknown URI: " + contentUri);
-        }
+        SettingsBackupWhitelist whitelist = getBackupWhitelist(contentUri);
 
         // Restore only the white list data.
         final ArrayMap<String, String> cachedEntries = new ArrayMap<>();
@@ -729,7 +765,7 @@
 
         Set<String> blockedSettings = getBlockedSettings(blockedSettingsArrayId);
 
-        for (String key : whitelist) {
+        for (String key : whitelist.mSettingsWhitelist) {
             boolean isBlockedBySystem = blockedSettings != null && blockedSettings.contains(key);
             if (isBlockedBySystem || isBlockedByDynamicList(dynamicBlockList, contentUri,  key)) {
                 Log.i(
@@ -742,6 +778,12 @@
                 continue;
             }
 
+            if (settingsToPreserve.contains(getQualifiedKeyForSetting(key, contentUri))) {
+                Log.i(TAG, "Skipping restore for setting " + key + " as it is marked as "
+                        + "preserved");
+                continue;
+            }
+
             String value = null;
             boolean hasValueToRestore = false;
             if (cachedEntries.indexOfKey(key) >= 0) {
@@ -775,7 +817,7 @@
             }
 
             // only restore the settings that have valid values
-            if (!isValidSettingValue(key, value, validators)) {
+            if (!isValidSettingValue(key, value, whitelist.mSettingsValidators)) {
                 Log.w(TAG, "Attempted restore of " + key + " setting, but its value didn't pass"
                         + " validation, value: " + value);
                 continue;
@@ -798,11 +840,42 @@
         }
     }
 
+    @VisibleForTesting
+    SettingsBackupWhitelist getBackupWhitelist(Uri contentUri) {
+        // Figure out the white list and redirects to the global table.  We restore anything
+        // in either the backup whitelist or the legacy-restore whitelist for this table.
+        String[] whitelist;
+        Map<String, Validator> validators = null;
+        if (contentUri.equals(Settings.Secure.CONTENT_URI)) {
+            whitelist = ArrayUtils.concatElements(String.class, SecureSettings.SETTINGS_TO_BACKUP,
+                    Settings.Secure.LEGACY_RESTORE_SETTINGS,
+                    DeviceSpecificSettings.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP);
+            validators = SecureSettingsValidators.VALIDATORS;
+        } else if (contentUri.equals(Settings.System.CONTENT_URI)) {
+            whitelist = ArrayUtils.concatElements(String.class, SystemSettings.SETTINGS_TO_BACKUP,
+                    Settings.System.LEGACY_RESTORE_SETTINGS);
+            validators = SystemSettingsValidators.VALIDATORS;
+        } else if (contentUri.equals(Settings.Global.CONTENT_URI)) {
+            whitelist = ArrayUtils.concatElements(String.class, GlobalSettings.SETTINGS_TO_BACKUP,
+                    Settings.Global.LEGACY_RESTORE_SETTINGS);
+            validators = GlobalSettingsValidators.VALIDATORS;
+        } else {
+            throw new IllegalArgumentException("Unknown URI: " + contentUri);
+        }
+
+        return new SettingsBackupWhitelist(whitelist, validators);
+    }
+
     private boolean isBlockedByDynamicList(Set<String> dynamicBlockList, Uri areaUri, String key) {
         String contentKey = Uri.withAppendedPath(areaUri, key).toString();
         return dynamicBlockList.contains(contentKey);
     }
 
+    @VisibleForTesting
+    static String getQualifiedKeyForSetting(String settingName, Uri settingUri) {
+        return Uri.withAppendedPath(settingUri, settingName).toString();
+    }
+
     // There may be other sources of blocked settings, so I'm separating out this
     // code to make it easy to modify in the future.
     @VisibleForTesting
@@ -1089,7 +1162,7 @@
      */
     @VisibleForTesting
     boolean restoreDeviceSpecificConfig(byte[] data, int blockedSettingsArrayId,
-            Set<String> dynamicBlocklist) {
+            Set<String> dynamicBlocklist, Set<String> preservedSettings) {
         // We're using an AtomicInteger to wrap the position int and allow called methods to
         // modify it.
         AtomicInteger pos = new AtomicInteger(0);
@@ -1108,7 +1181,8 @@
                 null,
                 null,
                 blockedSettingsArrayId,
-                dynamicBlocklist);
+                dynamicBlocklist,
+                preservedSettings);
 
         updateWindowManagerIfNeeded(originalDensity);
 
@@ -1240,4 +1314,20 @@
                 | ((in[pos + 3] & 0xFF) <<  0);
         return result;
     }
+
+    /**
+     * Store the whitelist of settings to be backed up and validators for them.
+     */
+    @VisibleForTesting
+    static class SettingsBackupWhitelist {
+        final String[] mSettingsWhitelist;
+        final Map<String, Validator> mSettingsValidators;
+
+
+        SettingsBackupWhitelist(String[] settingsWhitelist,
+                Map<String, Validator> settingsValidators) {
+            mSettingsWhitelist = settingsWhitelist;
+            mSettingsValidators = settingsValidators;
+        }
+    }
 }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 874e299..c969bfd 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -180,10 +180,17 @@
     private static final int MUTATION_OPERATION_UPDATE = 3;
     private static final int MUTATION_OPERATION_RESET = 4;
 
+    private static final String[] LEGACY_SQL_COLUMNS = new String[] {
+            Settings.NameValueTable._ID,
+            Settings.NameValueTable.NAME,
+            Settings.NameValueTable.VALUE,
+    };
+
     private static final String[] ALL_COLUMNS = new String[] {
             Settings.NameValueTable._ID,
             Settings.NameValueTable.NAME,
-            Settings.NameValueTable.VALUE
+            Settings.NameValueTable.VALUE,
+            Settings.NameValueTable.IS_PRESERVED_IN_RESTORE,
     };
 
     public static final int SETTINGS_TYPE_GLOBAL = SettingsState.SETTINGS_TYPE_GLOBAL;
@@ -2353,6 +2360,10 @@
                 case Settings.NameValueTable.VALUE: {
                     values[i] = setting.getValue();
                 } break;
+
+                case Settings.NameValueTable.IS_PRESERVED_IN_RESTORE: {
+                    values[i] = String.valueOf(setting.isValuePreservedInRestore());
+                } break;
             }
         }
 
@@ -3097,7 +3108,7 @@
             SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
             queryBuilder.setTables(table);
 
-            Cursor cursor = queryBuilder.query(database, ALL_COLUMNS,
+            Cursor cursor = queryBuilder.query(database, LEGACY_SQL_COLUMNS,
                     null, null, null, null, null);
 
             if (cursor == null) {
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java
index e650882..f5334fb 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java
@@ -32,6 +32,8 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.provider.Settings;
+import android.provider.settings.validators.SettingsValidators;
+import android.provider.settings.validators.Validator;
 import android.test.mock.MockContentProvider;
 import android.test.mock.MockContentResolver;
 
@@ -43,6 +45,7 @@
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.nio.ByteBuffer;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
@@ -55,12 +58,24 @@
 /** Tests for the SettingsHelperTest */
 @RunWith(AndroidJUnit4.class)
 public class SettingsBackupAgentTest extends BaseSettingsProviderTest {
-
+    private static final Uri TEST_URI = Uri.EMPTY;
     private static final String TEST_DISPLAY_DENSITY_FORCED = "123";
+    private static final String OVERRIDDEN_TEST_SETTING = "overridden_setting";
+    private static final String PRESERVED_TEST_SETTING = "preserved_setting";
+    private static final Map<String, String> DEVICE_SPECIFIC_TEST_VALUES = new HashMap<>();
     private static final Map<String, String> TEST_VALUES = new HashMap<>();
+    private static final Map<String, Validator> TEST_VALUES_VALIDATORS = new HashMap<>();
 
     static {
-        TEST_VALUES.put(Settings.Secure.DISPLAY_DENSITY_FORCED, TEST_DISPLAY_DENSITY_FORCED);
+        DEVICE_SPECIFIC_TEST_VALUES.put(Settings.Secure.DISPLAY_DENSITY_FORCED,
+                TEST_DISPLAY_DENSITY_FORCED);
+
+        TEST_VALUES.put(OVERRIDDEN_TEST_SETTING, "123");
+        TEST_VALUES.put(PRESERVED_TEST_SETTING, "124");
+
+        TEST_VALUES_VALIDATORS.put(OVERRIDDEN_TEST_SETTING,
+                SettingsValidators.ANY_STRING_VALIDATOR);
+        TEST_VALUES_VALIDATORS.put(PRESERVED_TEST_SETTING, SettingsValidators.ANY_STRING_VALIDATOR);
     }
 
     private TestFriendlySettingsBackupAgent mAgentUnderTest;
@@ -83,14 +98,15 @@
 
         byte[] settingsBackup = mAgentUnderTest.getDeviceSpecificConfiguration();
 
-        assertEquals("Not all values backed up.", TEST_VALUES.keySet(), helper.mReadEntries);
+        assertEquals("Not all values backed up.", DEVICE_SPECIFIC_TEST_VALUES.keySet(), helper.mReadEntries);
 
         mAgentUnderTest.restoreDeviceSpecificConfig(
                 settingsBackup,
                 R.array.restore_blocked_device_specific_settings,
+                Collections.emptySet(),
                 Collections.emptySet());
 
-        assertEquals("Not all values were restored.", TEST_VALUES, helper.mWrittenValues);
+        assertEquals("Not all values were restored.", DEVICE_SPECIFIC_TEST_VALUES, helper.mWrittenValues);
     }
 
     @Test
@@ -100,12 +116,13 @@
 
         byte[] settingsBackup = mAgentUnderTest.getDeviceSpecificConfiguration();
 
-        assertEquals("Not all values backed up.", TEST_VALUES.keySet(), helper.mReadEntries);
-        mAgentUnderTest.setBlockedSettings(TEST_VALUES.keySet().toArray(new String[0]));
+        assertEquals("Not all values backed up.", DEVICE_SPECIFIC_TEST_VALUES.keySet(), helper.mReadEntries);
+        mAgentUnderTest.setBlockedSettings(DEVICE_SPECIFIC_TEST_VALUES.keySet().toArray(new String[0]));
 
         mAgentUnderTest.restoreDeviceSpecificConfig(
                 settingsBackup,
                 R.array.restore_blocked_device_specific_settings,
+                Collections.emptySet(),
                 Collections.emptySet());
 
         assertTrue("Not all values were blocked.", helper.mWrittenValues.isEmpty());
@@ -172,9 +189,50 @@
                 mAgentUnderTest.restoreDeviceSpecificConfig(
                         data,
                         R.array.restore_blocked_device_specific_settings,
+                        Collections.emptySet(),
                         Collections.emptySet()));
     }
 
+    @Test
+    public void testOnRestore_preservedSettingsAreNotRestored() {
+        SettingsBackupAgent.SettingsBackupWhitelist whitelist =
+                new SettingsBackupAgent.SettingsBackupWhitelist(
+                        new String[] { OVERRIDDEN_TEST_SETTING, PRESERVED_TEST_SETTING },
+                        TEST_VALUES_VALIDATORS);
+        mAgentUnderTest.setSettingsWhitelist(whitelist);
+        mAgentUnderTest.setBlockedSettings();
+        TestSettingsHelper settingsHelper = new TestSettingsHelper(mContext);
+        mAgentUnderTest.mSettingsHelper = settingsHelper;
+
+        byte[] backupData = generateBackupData(TEST_VALUES);
+        mAgentUnderTest.restoreSettings(backupData, /* pos */ 0, backupData.length, TEST_URI, new HashSet<>(),
+                Collections.emptySet(), /* blockedSettingsArrayId */ 0, Collections.emptySet(),
+                new HashSet<>(Collections.singletonList(SettingsBackupAgent.getQualifiedKeyForSetting(PRESERVED_TEST_SETTING, TEST_URI))));
+
+        assertTrue(settingsHelper.mWrittenValues.containsKey(OVERRIDDEN_TEST_SETTING));
+        assertFalse(settingsHelper.mWrittenValues.containsKey(PRESERVED_TEST_SETTING));
+    }
+
+    private byte[] generateBackupData(Map<String, String> keyValueData) {
+        int totalBytes = 0;
+        for (String key : keyValueData.keySet()) {
+            totalBytes += 2 * Integer.BYTES + key.getBytes().length
+                    + keyValueData.get(key).getBytes().length;
+        }
+
+        ByteBuffer buffer = ByteBuffer.allocate(totalBytes);
+        for (String key : keyValueData.keySet()) {
+            byte[] keyBytes = key.getBytes();
+            byte[] valueBytes = keyValueData.get(key).getBytes();
+            buffer.putInt(keyBytes.length);
+            buffer.put(keyBytes);
+            buffer.putInt(valueBytes.length);
+            buffer.put(valueBytes);
+        }
+
+        return buffer.array();
+    }
+
     private byte[] generateUncorruptedHeader() throws IOException {
         try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
             mAgentUnderTest.writeHeader(os);
@@ -219,6 +277,7 @@
     private static class TestFriendlySettingsBackupAgent extends SettingsBackupAgent {
         private Boolean mForcedDeviceInfoRestoreAcceptability = null;
         private String[] mBlockedSettings = null;
+        private SettingsBackupWhitelist mSettingsWhitelist = null;
 
         void setForcedDeviceInfoRestoreAcceptability(boolean value) {
             mForcedDeviceInfoRestoreAcceptability = value;
@@ -228,6 +287,10 @@
             mBlockedSettings = blockedSettings;
         }
 
+        void setSettingsWhitelist(SettingsBackupWhitelist settingsWhitelist) {
+            mSettingsWhitelist = settingsWhitelist;
+        }
+
         @Override
         protected Set<String> getBlockedSettings(int blockedSettingsArrayId) {
             return mBlockedSettings == null
@@ -241,6 +304,15 @@
                     ? super.isSourceAcceptable(data, pos)
                     : mForcedDeviceInfoRestoreAcceptability;
         }
+
+        @Override
+        SettingsBackupWhitelist getBackupWhitelist(Uri contentUri) {
+            if (mSettingsWhitelist == null) {
+                return super.getBackupWhitelist(contentUri);
+            }
+
+            return mSettingsWhitelist;
+        }
     }
 
     /** The TestSettingsHelper tracks which values have been backed up and/or restored. */
@@ -257,7 +329,7 @@
         @Override
         public String onBackupValue(String key, String value) {
             mReadEntries.add(key);
-            String readValue = TEST_VALUES.get(key);
+            String readValue = DEVICE_SPECIFIC_TEST_VALUES.get(key);
             assert readValue != null;
             return readValue;
         }
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 26fa1cf..149eaf4 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -646,6 +646,7 @@
                   android:label="Controls Providers"
                   android:theme="@style/Theme.SystemUI"
                   android:exported="true"
+                  android:showForAllUsers="true"
                   android:excludeFromRecents="true"
                   android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
                   android:visibleToInstantApps="true">
@@ -655,6 +656,7 @@
                   android:parentActivityName=".controls.management.ControlsProviderSelectorActivity"
                   android:theme="@style/Theme.SystemUI"
                   android:excludeFromRecents="true"
+                  android:showForAllUsers="true"
                   android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
                   android:visibleToInstantApps="true">
         </activity>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 2aa6d95..d72ce5e 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -328,6 +328,10 @@
 
     <item type="id" name="action_toggle_overview"/>
 
+    <!-- Whether or not to show notifications to the user. If disabled, SystemUI will still be
+         registered as a notification listener, but will ignore all notification events. -->
+    <bool name="config_renderNotifications">true</bool>
+
     <!-- Whether or not the gear icon on notifications should be shown. The gear is shown when the
          the notification is not swiped enough to dismiss it. -->
     <bool name="config_showNotificationGear">true</bool>
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt
index e6cdf50..53841e2 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt
@@ -18,4 +18,8 @@
 
 import android.service.controls.Control
 
-data class ControlStatus(val control: Control, val favorite: Boolean, val removed: Boolean = false)
\ No newline at end of file
+data class ControlStatus(
+    val control: Control,
+    val favorite: Boolean,
+    val removed: Boolean = false
+)
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
index 265ddd8..588ef5c 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
@@ -22,7 +22,7 @@
 
 class ControlsServiceInfo(
     context: Context,
-    serviceInfo: ServiceInfo
+    val serviceInfo: ServiceInfo
 ) : DefaultAppInfo(
     context,
     context.packageManager,
diff --git a/packages/SystemUI/src/com/android/systemui/controls/UserAwareController.kt b/packages/SystemUI/src/com/android/systemui/controls/UserAwareController.kt
new file mode 100644
index 0000000..4f39f22
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/UserAwareController.kt
@@ -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.
+ */
+
+package com.android.systemui.controls
+
+import android.os.UserHandle
+
+interface UserAwareController {
+
+    fun changeUser(newUser: UserHandle) {}
+    val currentUserId: Int
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt
index 6b7fc4b..12c3ce9 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt
@@ -19,8 +19,9 @@
 import android.content.ComponentName
 import android.service.controls.Control
 import android.service.controls.actions.ControlAction
+import com.android.systemui.controls.UserAwareController
 
-interface ControlsBindingController {
+interface ControlsBindingController : UserAwareController {
     fun bindAndLoad(component: ComponentName, callback: (List<Control>) -> Unit)
     fun bindServices(components: List<ComponentName>)
     fun subscribe(controls: List<ControlInfo>)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
index 2db2cf1..48d2fa6 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
@@ -19,6 +19,7 @@
 import android.content.ComponentName
 import android.content.Context
 import android.os.IBinder
+import android.os.UserHandle
 import android.service.controls.Control
 import android.service.controls.IControlsActionCallback
 import android.service.controls.IControlsLoadCallback
@@ -50,12 +51,17 @@
 
     private val refreshing = AtomicBoolean(false)
 
+    private var currentUser = context.user
+
+    override val currentUserId: Int
+        get() = currentUser.identifier
+
     @GuardedBy("componentMap")
     private val tokenMap: MutableMap<IBinder, ControlsProviderLifecycleManager> =
             ArrayMap<IBinder, ControlsProviderLifecycleManager>()
     @GuardedBy("componentMap")
-    private val componentMap: MutableMap<ComponentName, ControlsProviderLifecycleManager> =
-            ArrayMap<ComponentName, ControlsProviderLifecycleManager>()
+    private val componentMap: MutableMap<Key, ControlsProviderLifecycleManager> =
+            ArrayMap<Key, ControlsProviderLifecycleManager>()
 
     private val loadCallbackService = object : IControlsLoadCallback.Stub() {
         override fun accept(token: IBinder, controls: MutableList<Control>) {
@@ -103,6 +109,7 @@
                 loadCallbackService,
                 actionCallbackService,
                 subscriberService,
+                currentUser,
                 component
         )
     }
@@ -110,7 +117,7 @@
     private fun retrieveLifecycleManager(component: ComponentName):
             ControlsProviderLifecycleManager {
         synchronized(componentMap) {
-            val provider = componentMap.getOrPut(component) {
+            val provider = componentMap.getOrPut(Key(component, currentUser)) {
                 createProviderManager(component)
             }
             tokenMap.putIfAbsent(provider.token, provider)
@@ -137,7 +144,7 @@
         val providersWithFavorites = controlsByComponentName.keys
         synchronized(componentMap) {
             componentMap.forEach {
-                if (it.key !in providersWithFavorites) {
+                if (it.key.component !in providersWithFavorites) {
                     backgroundExecutor.execute { it.value.unbindService() }
                 }
             }
@@ -167,6 +174,36 @@
         }
     }
 
+    override fun changeUser(newUser: UserHandle) {
+        if (newUser == currentUser) return
+        synchronized(componentMap) {
+            unbindAllProvidersLocked() // unbind all providers from the old user
+        }
+        refreshing.set(false)
+        currentUser = newUser
+    }
+
+    private fun unbindAllProvidersLocked() {
+        componentMap.values.forEach {
+            if (it.user == currentUser) {
+                it.unbindService()
+            }
+        }
+    }
+
+    override fun toString(): String {
+        return StringBuilder("  ControlsBindingController:\n").apply {
+            append("    refreshing=${refreshing.get()}\n")
+            append("    currentUser=$currentUser\n")
+            append("    Providers:\n")
+            synchronized(componentMap) {
+                componentMap.values.forEach {
+                    append("      $it\n")
+                }
+            }
+        }.toString()
+    }
+
     private abstract inner class CallbackRunnable(val token: IBinder) : Runnable {
         protected val provider: ControlsProviderLifecycleManager? =
                 synchronized(componentMap) {
@@ -183,6 +220,10 @@
                 Log.e(TAG, "No provider found for token:$token")
                 return
             }
+            if (provider.user != currentUser) {
+                Log.e(TAG, "User ${provider.user} is not current user")
+                return
+            }
             synchronized(componentMap) {
                 if (token !in tokenMap.keys) {
                     Log.e(TAG, "Provider for token:$token does not exist anymore")
@@ -204,6 +245,10 @@
             if (!refreshing.get()) {
                 Log.d(TAG, "onRefresh outside of window from:${provider?.componentName}")
             }
+            if (provider?.user != currentUser) {
+                Log.e(TAG, "User ${provider?.user} is not current user")
+                return
+            }
             provider?.let {
                 lazyController.get().refreshStatus(it.componentName, control)
             }
@@ -229,7 +274,7 @@
     ) : CallbackRunnable(token) {
         override fun run() {
             provider?.let {
-                Log.i(TAG, "onComplete receive from '${provider?.componentName}'")
+                Log.i(TAG, "onComplete receive from '${provider.componentName}'")
             }
         }
     }
@@ -240,7 +285,7 @@
     ) : CallbackRunnable(token) {
         override fun run() {
             provider?.let {
-                Log.e(TAG, "onError receive from '${provider?.componentName}': $error")
+                Log.e(TAG, "onError receive from '${provider.componentName}': $error")
             }
         }
     }
@@ -251,9 +296,15 @@
         @ControlAction.ResponseResult val response: Int
     ) : CallbackRunnable(token) {
         override fun run() {
+            if (provider?.user != currentUser) {
+                Log.e(TAG, "User ${provider?.user} is not current user")
+                return
+            }
             provider?.let {
                 lazyController.get().onActionResponse(it.componentName, controlId, response)
             }
         }
     }
 }
+
+private data class Key(val component: ComponentName, val user: UserHandle)
\ No newline at end of file
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 e098faa..b02de45 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
@@ -20,8 +20,9 @@
 import android.service.controls.Control
 import android.service.controls.actions.ControlAction
 import com.android.systemui.controls.ControlStatus
+import com.android.systemui.controls.UserAwareController
 
-interface ControlsController {
+interface ControlsController : UserAwareController {
     val available: Boolean
 
     fun getFavoriteControls(): List<ControlInfo>
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 d5b5b5f..6ff1cf8 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -17,10 +17,13 @@
 package com.android.systemui.controls.controller
 
 import android.app.PendingIntent
+import android.content.BroadcastReceiver
 import android.content.ComponentName
 import android.content.Context
 import android.content.Intent
+import android.content.IntentFilter
 import android.os.Environment
+import android.os.UserHandle
 import android.provider.Settings
 import android.service.controls.Control
 import android.service.controls.actions.ControlAction
@@ -29,14 +32,17 @@
 import com.android.internal.annotations.GuardedBy
 import com.android.systemui.DumpController
 import com.android.systemui.Dumpable
+import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.controls.ControlStatus
 import com.android.systemui.controls.management.ControlsFavoritingActivity
+import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.controls.ui.ControlsUiController
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.util.concurrency.DelayableExecutor
 import java.io.FileDescriptor
 import java.io.PrintWriter
 import java.util.Optional
+import java.util.concurrent.TimeUnit
 import javax.inject.Inject
 import javax.inject.Singleton
 
@@ -46,35 +52,101 @@
     @Background private val executor: DelayableExecutor,
     private val uiController: ControlsUiController,
     private val bindingController: ControlsBindingController,
-    private val optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>,
+    private val listingController: ControlsListingController,
+    broadcastDispatcher: BroadcastDispatcher,
+    optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>,
     dumpController: DumpController
 ) : Dumpable, ControlsController {
 
     companion object {
         private const val TAG = "ControlsControllerImpl"
         const val CONTROLS_AVAILABLE = "systemui.controls_available"
+        const val USER_CHANGE_RETRY_DELAY = 500L // ms
     }
 
-    override val available = Settings.Secure.getInt(
+    // Map of map: ComponentName -> (String -> ControlInfo).
+    // Only for current user
+    @GuardedBy("currentFavorites")
+    private val currentFavorites = ArrayMap<ComponentName, MutableMap<String, ControlInfo>>()
+
+    private var userChanging = true
+    override var available = Settings.Secure.getInt(
             context.contentResolver, CONTROLS_AVAILABLE, 0) != 0
-    val persistenceWrapper = optionalWrapper.orElseGet {
+        private set
+
+    private var currentUser = context.user
+    override val currentUserId
+        get() = currentUser.identifier
+
+    private val persistenceWrapper = optionalWrapper.orElseGet {
         ControlsFavoritePersistenceWrapper(
                 Environment.buildPath(
-                        context.filesDir,
-                        ControlsFavoritePersistenceWrapper.FILE_NAME),
+                    context.filesDir,
+                    ControlsFavoritePersistenceWrapper.FILE_NAME
+                ),
                 executor
         )
     }
 
-    // Map of map: ComponentName -> (String -> ControlInfo)
-    @GuardedBy("currentFavorites")
-    private val currentFavorites = ArrayMap<ComponentName, MutableMap<String, ControlInfo>>()
-
-    init {
+    private fun setValuesForUser(newUser: UserHandle) {
+        Log.d(TAG, "Changing to user: $newUser")
+        currentUser = newUser
+        val userContext = context.createContextAsUser(currentUser, 0)
+        val fileName = Environment.buildPath(
+                userContext.filesDir, ControlsFavoritePersistenceWrapper.FILE_NAME)
+        persistenceWrapper.changeFile(fileName)
+        available = Settings.Secure.getIntForUser(
+                context.contentResolver, CONTROLS_AVAILABLE, 0) != 0
+        synchronized(currentFavorites) {
+            currentFavorites.clear()
+        }
         if (available) {
-            dumpController.registerDumpable(this)
             loadFavorites()
         }
+        bindingController.changeUser(newUser)
+        listingController.changeUser(newUser)
+        userChanging = false
+    }
+
+    private val userSwitchReceiver = object : BroadcastReceiver() {
+        override fun onReceive(context: Context, intent: Intent) {
+            if (intent.action == Intent.ACTION_USER_SWITCHED) {
+                userChanging = true
+                val newUser =
+                        UserHandle.of(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, sendingUserId))
+                if (currentUser == newUser) {
+                    userChanging = false
+                    return
+                }
+                setValuesForUser(newUser)
+            }
+        }
+    }
+
+    init {
+        dumpController.registerDumpable(this)
+        if (available) {
+            loadFavorites()
+        }
+        userChanging = false
+        broadcastDispatcher.registerReceiver(
+                userSwitchReceiver,
+                IntentFilter(Intent.ACTION_USER_SWITCHED),
+                executor,
+                UserHandle.ALL
+        )
+    }
+
+    private fun confirmAvailability(): Boolean {
+        if (userChanging) {
+            Log.w(TAG, "Controls not available while user is changing")
+            return false
+        }
+        if (!available) {
+            Log.d(TAG, "Controls not available")
+            return false
+        }
+        return true
     }
 
     private fun loadFavorites() {
@@ -91,8 +163,18 @@
         componentName: ComponentName,
         callback: (List<ControlStatus>) -> Unit
     ) {
-        if (!available) {
-            Log.d(TAG, "Controls not available")
+        if (!confirmAvailability()) {
+            if (userChanging) {
+                // Try again later, userChanging should not last forever. If so, we have bigger
+                // problems
+                executor.executeDelayed(
+                        { loadForComponent(componentName, callback) },
+                        USER_CHANGE_RETRY_DELAY,
+                        TimeUnit.MILLISECONDS
+                )
+            } else {
+                callback(emptyList())
+            }
             return
         }
         bindingController.bindAndLoad(componentName) {
@@ -158,10 +240,7 @@
     }
 
     override fun subscribeToFavorites() {
-        if (!available) {
-            Log.d(TAG, "Controls not available")
-            return
-        }
+        if (!confirmAvailability()) return
         // Make a copy of the favorites list
         val favorites = synchronized(currentFavorites) {
             currentFavorites.flatMap { it.value.values.toList() }
@@ -170,18 +249,12 @@
     }
 
     override fun unsubscribe() {
-        if (!available) {
-            Log.d(TAG, "Controls not available")
-            return
-        }
+        if (!confirmAvailability()) return
         bindingController.unsubscribe()
     }
 
     override fun changeFavoriteStatus(controlInfo: ControlInfo, state: Boolean) {
-        if (!available) {
-            Log.d(TAG, "Controls not available")
-            return
-        }
+        if (!confirmAvailability()) return
         var changed = false
         val listOfControls = synchronized(currentFavorites) {
             if (state) {
@@ -211,7 +284,7 @@
     }
 
     override fun refreshStatus(componentName: ComponentName, control: Control) {
-        if (!available) {
+        if (!confirmAvailability()) {
             Log.d(TAG, "Controls not available")
             return
         }
@@ -227,28 +300,24 @@
     }
 
     override fun onActionResponse(componentName: ComponentName, controlId: String, response: Int) {
-        if (!available) {
-            Log.d(TAG, "Controls not available")
-            return
-        }
+        if (!confirmAvailability()) return
         uiController.onActionResponse(componentName, controlId, response)
     }
 
     override fun getFavoriteControls(): List<ControlInfo> {
-        if (!available) {
-            Log.d(TAG, "Controls not available")
-            return emptyList()
-        }
+        if (!confirmAvailability()) return emptyList()
         synchronized(currentFavorites) {
             return favoritesAsListLocked()
         }
     }
 
     override fun action(controlInfo: ControlInfo, action: ControlAction) {
+        if (!confirmAvailability()) return
         bindingController.action(controlInfo, action)
     }
 
     override fun clearFavorites() {
+        if (!confirmAvailability()) return
         val changed = synchronized(currentFavorites) {
             currentFavorites.isNotEmpty().also {
                 currentFavorites.clear()
@@ -261,6 +330,9 @@
 
     override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
         pw.println("ControlsController state:")
+        pw.println("  Available: $available")
+        pw.println("  Changing users: $userChanging")
+        pw.println("  Current user: ${currentUser.identifier}")
         pw.println("  Favorites:")
         synchronized(currentFavorites) {
             currentFavorites.forEach {
@@ -269,5 +341,6 @@
                 }
             }
         }
+        pw.println(bindingController.toString())
     }
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt
index 6f2d71f..7d1df14 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.controls.controller
 
-import android.app.ActivityManager
 import android.content.ComponentName
 import android.util.AtomicFile
 import android.util.Log
@@ -32,8 +31,8 @@
 import java.io.IOException
 
 class ControlsFavoritePersistenceWrapper(
-    val file: File,
-    val executor: DelayableExecutor
+    private var file: File,
+    private var executor: DelayableExecutor
 ) {
 
     companion object {
@@ -47,11 +46,13 @@
         private const val TAG_TYPE = "type"
     }
 
-    val currentUser: Int
-        get() = ActivityManager.getCurrentUser()
+    fun changeFile(fileName: File) {
+        file = fileName
+    }
 
     fun storeFavorites(list: List<ControlInfo>) {
         executor.execute {
+            Log.d(TAG, "Saving data to file: $file")
             val atomicFile = AtomicFile(file)
             val writer = try {
                 atomicFile.startWrite()
@@ -98,6 +99,7 @@
             return emptyList()
         }
         try {
+            Log.d(TAG, "Reading data from file: $file")
             val parser = Xml.newPullParser()
             parser.setInput(reader, null)
             return parseXml(parser)
@@ -111,7 +113,7 @@
     }
 
     private fun parseXml(parser: XmlPullParser): List<ControlInfo> {
-        var type: Int = 0
+        var type = 0
         val infos = mutableListOf<ControlInfo>()
         while (parser.next().also { type = it } != XmlPullParser.END_DOCUMENT) {
             if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
@@ -123,9 +125,9 @@
                         parser.getAttributeValue(null, TAG_COMPONENT))
                 val id = parser.getAttributeValue(null, TAG_ID)
                 val title = parser.getAttributeValue(null, TAG_TITLE)
-                val type = parser.getAttributeValue(null, TAG_TYPE)?.toInt()
-                if (component != null && id != null && title != null && type != null) {
-                    infos.add(ControlInfo(component, id, title, type))
+                val deviceType = parser.getAttributeValue(null, TAG_TYPE)?.toInt()
+                if (component != null && id != null && title != null && deviceType != null) {
+                    infos.add(ControlInfo(component, id, title, deviceType))
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
index 99aa360..739ca7e 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
@@ -24,6 +24,7 @@
 import android.os.Bundle
 import android.os.IBinder
 import android.os.RemoteException
+import android.os.UserHandle
 import android.service.controls.Control
 import android.service.controls.ControlsProviderService.CALLBACK_BUNDLE
 import android.service.controls.ControlsProviderService.CALLBACK_TOKEN
@@ -46,6 +47,7 @@
     private val loadCallbackService: IControlsLoadCallback.Stub,
     private val actionCallbackService: IControlsActionCallback.Stub,
     private val subscriberService: IControlsSubscriber.Stub,
+    val user: UserHandle,
     val componentName: ComponentName
 ) : IBinder.DeathRecipient {
 
@@ -96,7 +98,7 @@
             }
             bindTryCount++
             try {
-                isBound = context.bindService(intent, serviceConnection, BIND_FLAGS)
+                isBound = context.bindServiceAsUser(intent, serviceConnection, BIND_FLAGS, user)
             } catch (e: SecurityException) {
                 Log.e(TAG, "Failed to bind to service", e)
                 isBound = false
@@ -152,7 +154,9 @@
             load()
         }
         queue.filter { it is Message.Subscribe }.flatMap { (it as Message.Subscribe).list }.run {
-            subscribe(this)
+            if (this.isNotEmpty()) {
+                subscribe(this)
+            }
         }
         queue.filter { it is Message.Action }.forEach {
             val msg = it as Message.Action
@@ -286,6 +290,15 @@
         maybeUnbindAndRemoveCallback()
     }
 
+    override fun toString(): String {
+        return StringBuilder("ControlsProviderLifecycleManager(").apply {
+            append("component=$componentName")
+            append(", user=$user")
+            append(", bound=$isBound")
+            append(")")
+        }.toString()
+    }
+
     sealed class Message {
         abstract val type: Int
         object Load : Message() {
@@ -301,4 +314,4 @@
             override val type = MSG_ACTION
         }
     }
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt
index d62bb4d..22c6908 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt
@@ -23,6 +23,7 @@
 import android.widget.ImageView
 import android.widget.TextView
 import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
 import androidx.recyclerview.widget.RecyclerView
 import com.android.settingslib.widget.CandidateInfo
 import com.android.systemui.R
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 01c4fef..7ee4fd5 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
@@ -22,15 +22,18 @@
 import android.view.LayoutInflater
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
+import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.controls.controller.ControlInfo
 import com.android.systemui.controls.controller.ControlsControllerImpl
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.settings.CurrentUserTracker
 import java.util.concurrent.Executor
 import javax.inject.Inject
 
 class ControlsFavoritingActivity @Inject constructor(
     @Main private val executor: Executor,
-    private val controller: ControlsControllerImpl
+    private val controller: ControlsControllerImpl,
+    broadcastDispatcher: BroadcastDispatcher
 ) : Activity() {
 
     companion object {
@@ -42,11 +45,24 @@
     private lateinit var recyclerView: RecyclerView
     private lateinit var adapter: ControlAdapter
 
+    private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) {
+        private val startingUser = controller.currentUserId
+
+        override fun onUserSwitched(newUserId: Int) {
+            if (newUserId != startingUser) {
+                stopTracking()
+                finish()
+            }
+        }
+    }
+
+    private var component: ComponentName? = null
+
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
 
         val app = intent.getCharSequenceExtra(EXTRA_APP)
-        val component = intent.getParcelableExtra<ComponentName>(EXTRA_COMPONENT)
+        component = intent.getParcelableExtra<ComponentName>(EXTRA_COMPONENT)
 
         // If we have no component name, there's not much we can do.
         val callback = component?.let {
@@ -68,6 +84,11 @@
         }
         setContentView(recyclerView)
 
+        currentUserTracker.startTracking()
+    }
+
+    override fun onResume() {
+        super.onResume()
         component?.let {
             controller.loadForComponent(it) {
                 executor.execute {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt
index 09e0ce9..34db684 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt
@@ -18,14 +18,17 @@
 
 import android.content.ComponentName
 import com.android.settingslib.widget.CandidateInfo
+import com.android.systemui.controls.UserAwareController
 import com.android.systemui.statusbar.policy.CallbackController
 
 interface ControlsListingController :
-        CallbackController<ControlsListingController.ControlsListingCallback> {
+        CallbackController<ControlsListingController.ControlsListingCallback>,
+        UserAwareController {
 
     fun getCurrentServices(): List<CandidateInfo>
     fun getAppLabel(name: ComponentName): CharSequence? = ""
 
+    @FunctionalInterface
     interface ControlsListingCallback {
         fun onServicesUpdated(list: List<CandidateInfo>)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
index 3949c592..882382c 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
@@ -19,6 +19,7 @@
 import android.content.ComponentName
 import android.content.Context
 import android.content.pm.ServiceInfo
+import android.os.UserHandle
 import android.service.controls.ControlsProviderService
 import android.util.Log
 import com.android.internal.annotations.VisibleForTesting
@@ -31,6 +32,16 @@
 import javax.inject.Inject
 import javax.inject.Singleton
 
+private fun createServiceListing(context: Context): ServiceListing {
+    return ServiceListing.Builder(context).apply {
+        setIntentAction(ControlsProviderService.SERVICE_CONTROLS)
+        setPermission("android.permission.BIND_CONTROLS")
+        setNoun("Controls Provider")
+        setSetting("controls_providers")
+        setTag("controls_providers")
+    }.build()
+}
+
 /**
  * Provides a listing of components to be used as ControlsServiceProvider.
  *
@@ -43,41 +54,55 @@
 class ControlsListingControllerImpl @VisibleForTesting constructor(
     private val context: Context,
     @Background private val backgroundExecutor: Executor,
-    private val serviceListing: ServiceListing
+    private val serviceListingBuilder: (Context) -> ServiceListing
 ) : ControlsListingController {
 
     @Inject
     constructor(context: Context, executor: Executor): this(
             context,
             executor,
-            ServiceListing.Builder(context)
-                    .setIntentAction(ControlsProviderService.SERVICE_CONTROLS)
-                    .setPermission("android.permission.BIND_CONTROLS")
-                    .setNoun("Controls Provider")
-                    .setSetting("controls_providers")
-                    .setTag("controls_providers")
-                    .build()
+            ::createServiceListing
     )
 
+    private var serviceListing = serviceListingBuilder(context)
+
     companion object {
         private const val TAG = "ControlsListingControllerImpl"
     }
 
     private var availableServices = emptyList<ServiceInfo>()
 
-    init {
-        serviceListing.addCallback {
-            Log.d(TAG, "ServiceConfig reloaded")
-            availableServices = it.toList()
+    override var currentUserId = context.userId
+        private set
 
-            backgroundExecutor.execute {
-                callbacks.forEach {
-                    it.onServicesUpdated(getCurrentServices())
-                }
+    private val serviceListingCallback = ServiceListing.Callback {
+        Log.d(TAG, "ServiceConfig reloaded")
+        availableServices = it.toList()
+
+        backgroundExecutor.execute {
+            callbacks.forEach {
+                it.onServicesUpdated(getCurrentServices())
             }
         }
     }
 
+    init {
+        serviceListing.addCallback(serviceListingCallback)
+    }
+
+    override fun changeUser(newUser: UserHandle) {
+        backgroundExecutor.execute {
+            callbacks.clear()
+            availableServices = emptyList()
+            serviceListing.setListening(false)
+            serviceListing.removeCallback(serviceListingCallback)
+            currentUserId = newUser.identifier
+            val contextForUser = context.createContextAsUser(newUser, 0)
+            serviceListing = serviceListingBuilder(contextForUser)
+            serviceListing.addCallback(serviceListingCallback)
+        }
+    }
+
     // All operations in background thread
     private val callbacks = mutableSetOf<ControlsListingController.ControlsListingCallback>()
 
@@ -91,6 +116,7 @@
      */
     override fun addCallback(listener: ControlsListingController.ControlsListingCallback) {
         backgroundExecutor.execute {
+            Log.d(TAG, "Subscribing callback")
             callbacks.add(listener)
             if (callbacks.size == 1) {
                 serviceListing.setListening(true)
@@ -108,6 +134,7 @@
      */
     override fun removeCallback(listener: ControlsListingController.ControlsListingCallback) {
         backgroundExecutor.execute {
+            Log.d(TAG, "Unsubscribing callback")
             callbacks.remove(listener)
             if (callbacks.size == 0) {
                 serviceListing.setListening(false)
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 69af516..5ff949c 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
@@ -22,7 +22,10 @@
 import android.view.LayoutInflater
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.settings.CurrentUserTracker
 import com.android.systemui.util.LifecycleActivity
 import java.util.concurrent.Executor
 import javax.inject.Inject
@@ -32,7 +35,9 @@
  */
 class ControlsProviderSelectorActivity @Inject constructor(
     @Main private val executor: Executor,
-    private val listingController: ControlsListingController
+    @Background private val backExecutor: Executor,
+    private val listingController: ControlsListingController,
+    broadcastDispatcher: BroadcastDispatcher
 ) : LifecycleActivity() {
 
     companion object {
@@ -40,6 +45,16 @@
     }
 
     private lateinit var recyclerView: RecyclerView
+    private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) {
+        private val startingUser = listingController.currentUserId
+
+        override fun onUserSwitched(newUserId: Int) {
+            if (newUserId != startingUser) {
+                stopTracking()
+                finish()
+            }
+        }
+    }
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
@@ -50,6 +65,7 @@
         recyclerView.layoutManager = LinearLayoutManager(applicationContext)
 
         setContentView(recyclerView)
+        currentUserTracker.startTracking()
     }
 
     /**
@@ -57,13 +73,17 @@
      * @param component a component name for a [ControlsProviderService]
      */
     fun launchFavoritingActivity(component: ComponentName?) {
-        component?.let {
-            val intent = Intent(applicationContext, ControlsFavoritingActivity::class.java).apply {
-                putExtra(ControlsFavoritingActivity.EXTRA_APP, listingController.getAppLabel(it))
-                putExtra(ControlsFavoritingActivity.EXTRA_COMPONENT, it)
-                flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP
+        backExecutor.execute {
+            component?.let {
+                val intent = Intent(applicationContext, ControlsFavoritingActivity::class.java)
+                        .apply {
+                    putExtra(ControlsFavoritingActivity.EXTRA_APP,
+                            listingController.getAppLabel(it))
+                    putExtra(ControlsFavoritingActivity.EXTRA_COMPONENT, it)
+                    flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP
+                }
+                startActivity(intent)
             }
-            startActivity(intent)
         }
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index f793b3d..7b54199 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -38,6 +38,7 @@
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
+import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
 import com.android.systemui.statusbar.notification.people.PeopleHubModule;
 import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
 import com.android.systemui.statusbar.phone.KeyguardLiftController;
@@ -64,6 +65,7 @@
             AssistModule.class,
             ConcurrencyModule.class,
             LogModule.class,
+            NotificationsModule.class,
             PeopleHubModule.class,
         },
         subcomponents = {StatusBarComponent.class, NotificationRowComponent.class})
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
index 43db85b..6f655bb 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
@@ -118,6 +118,7 @@
                 new DozeWallpaperState(mWallpaperManager, mBiometricUnlockController,
                         mDozeParameters),
                 new DozeDockHandler(config, machine, mDockManager),
+                new DozeSuppressedHandler(dozeService, config, machine),
                 new DozeAuthRemover(dozeService)
         });
 
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index 8afdf1a..96ae6c9 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -266,6 +266,14 @@
         mLogger.logSensorTriggered(reason);
     }
 
+    /**
+     * Appends doze suppressed event to the logs
+     * @param suppressedState The {@link DozeMachine.State} that was suppressed
+     */
+    public void traceDozeSuppressed(DozeMachine.State suppressedState) {
+        mLogger.logDozeSuppressed(suppressedState);
+    }
+
     private class SummaryStats {
         private int mCount;
 
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
index 42decd5..732745a 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
@@ -194,6 +194,14 @@
             "Sensor triggered, type=${reasonToString(int1)}"
         })
     }
+
+    fun logDozeSuppressed(state: DozeMachine.State) {
+        buffer.log(TAG, INFO, {
+            str1 = state.name
+        }, {
+            "Doze state suppressed, state=$str1"
+        })
+    }
 }
 
 private const val TAG = "DozeLog"
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
index 03c25ee..6e81d3a 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
@@ -102,6 +102,10 @@
             }
         }
 
+        boolean isAlwaysOn() {
+            return this == DOZE_AOD || this == DOZE_AOD_DOCKED;
+        }
+
         int screenState(DozeParameters parameters) {
             switch (this) {
                 case UNINITIALIZED:
@@ -324,6 +328,11 @@
         if (mState == State.FINISH) {
             return State.FINISH;
         }
+        if (mConfig.dozeSuppressed(UserHandle.USER_CURRENT) && requestedState.isAlwaysOn()) {
+            Log.i(TAG, "Doze is suppressed. Suppressing state: " + requestedState);
+            mDozeLog.traceDozeSuppressed(requestedState);
+            return State.DOZE;
+        }
         if ((mState == State.DOZE_AOD_PAUSED || mState == State.DOZE_AOD_PAUSING
                 || mState == State.DOZE_AOD || mState == State.DOZE)
                 && requestedState == State.DOZE_PULSE_DONE) {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
index 3abeea9..e50f1fb 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
@@ -18,7 +18,6 @@
 
 import static com.android.systemui.doze.DozeMachine.State.DOZE;
 import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD;
-import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_DOCKED;
 import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSED;
 import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSING;
 import static com.android.systemui.doze.DozeMachine.State.DOZE_PULSE_DONE;
@@ -90,10 +89,10 @@
         }
 
         final boolean messagePending = mHandler.hasCallbacks(mApplyPendingScreenState);
-        final boolean pulseEnding = oldState == DOZE_PULSE_DONE && isAlwaysOnState(newState);
+        final boolean pulseEnding = oldState == DOZE_PULSE_DONE && newState.isAlwaysOn();
         final boolean turningOn = (oldState == DOZE_AOD_PAUSED || oldState == DOZE)
-                && isAlwaysOnState(newState);
-        final boolean turningOff = (isAlwaysOnState(oldState) && newState == DOZE)
+                && newState.isAlwaysOn();
+        final boolean turningOff = (newState.isAlwaysOn() && newState == DOZE)
                 || (oldState == DOZE_AOD_PAUSING && newState == DOZE_AOD_PAUSED);
         final boolean justInitialized = oldState == DozeMachine.State.INITIALIZED;
         if (messagePending || justInitialized || pulseEnding || turningOn) {
@@ -132,10 +131,6 @@
         }
     }
 
-    private boolean isAlwaysOnState(DozeMachine.State state) {
-        return state == DOZE_AOD || state == DOZE_AOD_DOCKED;
-    }
-
     private void applyPendingScreenState() {
         applyScreenState(mPendingScreenState);
         mPendingScreenState = Display.STATE_UNKNOWN;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressedHandler.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressedHandler.java
new file mode 100644
index 0000000..3a5c1a0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressedHandler.java
@@ -0,0 +1,124 @@
+/*
+ * 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.doze;
+
+import static java.util.Objects.requireNonNull;
+
+import android.app.ActivityManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.hardware.display.AmbientDisplayConfiguration;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/** Handles updating the doze state when doze is suppressed. */
+public final class DozeSuppressedHandler implements DozeMachine.Part {
+
+    private static final String TAG = DozeSuppressedHandler.class.getSimpleName();
+    private static final boolean DEBUG = DozeService.DEBUG;
+
+    private final ContentResolver mResolver;
+    private final AmbientDisplayConfiguration mConfig;
+    private final DozeMachine mMachine;
+    private final DozeSuppressedSettingObserver mSettingObserver;
+    private final Handler mHandler = new Handler();
+
+    public DozeSuppressedHandler(Context context, AmbientDisplayConfiguration config,
+            DozeMachine machine) {
+        this(context, config, machine, null);
+    }
+
+    @VisibleForTesting
+    DozeSuppressedHandler(Context context, AmbientDisplayConfiguration config, DozeMachine machine,
+            DozeSuppressedSettingObserver observer) {
+        mResolver = context.getContentResolver();
+        mConfig = requireNonNull(config);
+        mMachine = requireNonNull(machine);
+        if (observer == null) {
+            mSettingObserver = new DozeSuppressedSettingObserver(mHandler);
+        } else {
+            mSettingObserver = observer;
+        }
+    }
+
+    @Override
+    public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
+        switch (newState) {
+            case INITIALIZED:
+                mSettingObserver.register();
+                break;
+            case FINISH:
+                mSettingObserver.unregister();
+                break;
+            default:
+                // no-op
+        }
+    }
+
+    /**
+     * Listens to changes to the DOZE_SUPPRESSED secure setting and updates the doze state
+     * accordingly.
+     */
+    final class DozeSuppressedSettingObserver extends ContentObserver {
+        private boolean mRegistered;
+
+        private DozeSuppressedSettingObserver(Handler handler) {
+            super(handler);
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri, int userId) {
+            if (userId != ActivityManager.getCurrentUser()) {
+                return;
+            }
+            final DozeMachine.State nextState;
+            if (mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT)
+                    && !mConfig.dozeSuppressed(UserHandle.USER_CURRENT)) {
+                nextState = DozeMachine.State.DOZE_AOD;
+            } else {
+                nextState = DozeMachine.State.DOZE;
+            }
+            mMachine.requestState(nextState);
+        }
+
+        void register() {
+            if (mRegistered) {
+                return;
+            }
+            mResolver.registerContentObserver(
+                    Settings.Secure.getUriFor(Settings.Secure.SUPPRESS_DOZE),
+                    false, this, UserHandle.USER_CURRENT);
+            Log.d(TAG, "Register");
+            mRegistered = true;
+        }
+
+        void unregister() {
+            if (!mRegistered) {
+                return;
+            }
+            mResolver.unregisterContentObserver(this);
+            Log.d(TAG, "Unregister");
+            mRegistered = false;
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index b9c056d..305a4c8 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -127,6 +127,11 @@
             mDozeLog.tracePulseDropped("pulseOnNotificationsDisabled");
             return;
         }
+        if (mConfig.dozeSuppressed(UserHandle.USER_CURRENT)) {
+            runIfNotNull(onPulseSuppressedListener);
+            mDozeLog.tracePulseDropped("dozeSuppressed");
+            return;
+        }
         requestPulse(DozeLog.PULSE_REASON_NOTIFICATION, false /* performedProxCheck */,
                 onPulseSuppressedListener);
         mDozeLog.traceNotificationPulse();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
index 3fcd1c1..476af20 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
@@ -116,6 +116,7 @@
 
     @Override
     public void onClick(View v) {
+        if (!hasFooter()) return;
         mHandler.sendEmptyMessage(H.CLICK);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/BypassHeadsUpNotifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/BypassHeadsUpNotifier.kt
index 015c323..269a7a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/BypassHeadsUpNotifier.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/BypassHeadsUpNotifier.kt
@@ -43,10 +43,10 @@
     private val headsUpManager: HeadsUpManagerPhone,
     private val notificationLockscreenUserManager: NotificationLockscreenUserManager,
     private val mediaManager: NotificationMediaManager,
+    private val entryManager: NotificationEntryManager,
     tunerService: TunerService
 ) : StatusBarStateController.StateListener, NotificationMediaManager.MediaListener {
 
-    private lateinit var entryManager: NotificationEntryManager
     private var currentMediaEntry: NotificationEntry? = null
     private var enabled = true
 
@@ -70,8 +70,7 @@
                 }, Settings.Secure.SHOW_MEDIA_WHEN_BYPASSING)
     }
 
-    fun setUp(entryManager: NotificationEntryManager) {
-        this.entryManager = entryManager
+    fun setUp() {
         mediaManager.addCallback(this)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index 8ac4d30..cb8cef8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -92,8 +92,8 @@
  * {@link #addNotificationLifetimeExtender(NotifLifetimeExtender)}).
  *
  * Interested parties can register listeners
- * ({@link #addCollectionListener(NotifCollectionListener)}) to be informed when notifications are
- * added, updated, or removed.
+ * ({@link #addCollectionListener(NotifCollectionListener)}) to be informed when notifications
+ * events occur.
  */
 @MainThread
 @Singleton
@@ -230,6 +230,8 @@
 
             entry = new NotificationEntry(sbn, ranking);
             mNotificationSet.put(sbn.getKey(), entry);
+            dispatchOnEntryInit(entry);
+
             if (rankingMap != null) {
                 applyRanking(rankingMap);
             }
@@ -297,6 +299,7 @@
             }
 
             dispatchOnEntryRemoved(entry, reason, dismissedByUserStats != null /* removedByUser */);
+            dispatchOnEntryCleanUp(entry);
         }
 
         rebuildList();
@@ -378,6 +381,14 @@
         return ranking;
     }
 
+    private void dispatchOnEntryInit(NotificationEntry entry) {
+        mAmDispatchingToOtherCode = true;
+        for (NotifCollectionListener listener : mNotifCollectionListeners) {
+            listener.onEntryInit(entry);
+        }
+        mAmDispatchingToOtherCode = false;
+    }
+
     private void dispatchOnEntryAdded(NotificationEntry entry) {
         mAmDispatchingToOtherCode = true;
         for (NotifCollectionListener listener : mNotifCollectionListeners) {
@@ -413,6 +424,14 @@
         mAmDispatchingToOtherCode = false;
     }
 
+    private void dispatchOnEntryCleanUp(NotificationEntry entry) {
+        mAmDispatchingToOtherCode = true;
+        for (NotifCollectionListener listener : mNotifCollectionListeners) {
+            listener.onEntryCleanUp(entry);
+        }
+        mAmDispatchingToOtherCode = false;
+    }
+
     private final BatchableNotificationHandler mNotifHandler = new BatchableNotificationHandler() {
         @Override
         public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
index 0377f57..9142388 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
@@ -90,7 +90,8 @@
     }
 
     /**
-     * Registers a listener to be informed when notifications are added, removed or updated.
+     * Registers a listener to be informed when there is a notification entry event such as an add,
+     * update, or remove.
      */
     public void addCollectionListener(NotifCollectionListener listener) {
         mNotifCollection.addCollectionListener(listener);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java
index 6adcabd..ff6da12 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java
@@ -18,15 +18,25 @@
 
 import android.service.notification.NotificationListenerService;
 
-import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
 /**
- * Listener interface for {@link NotifCollection}.
+ * Listener interface for {@link NotificationEntry} events.
  */
 public interface NotifCollectionListener {
     /**
+     * Called whenever a new {@link NotificationEntry} is initialized. This should be used for
+     * initializing any decorated state tied to the notification.
+     *
+     * Do not reference other registered {@link NotifCollectionListener} implementations here as
+     * there is no guarantee of order and they may not have had a chance to initialize yet. Instead,
+     * use {@link #onEntryAdded} which is called after all initialization.
+     */
+    default void onEntryInit(NotificationEntry entry) {
+    }
+
+    /**
      * Called whenever a notification with a new key is posted.
      */
     default void onEntryAdded(NotificationEntry entry) {
@@ -51,6 +61,18 @@
     }
 
     /**
+     * Called whenever a {@link NotificationEntry} is considered deleted. This should be used for
+     * cleaning up any state tied to the notification.
+     *
+     * This is the deletion parallel of {@link #onEntryInit} and similarly means that you cannot
+     * expect other {@link NotifCollectionListener} implementations to still have valid state for
+     * the entry during this call. Instead, use {@link #onEntryRemoved} which will be called before
+     * deletion.
+     */
+    default void onEntryCleanUp(NotificationEntry entry) {
+    }
+
+    /**
      * Called whenever the RankingMap is updated by system server. By the time this listener is
      * called, the Rankings of all entries will have been updated.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
new file mode 100644
index 0000000..c7666e4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -0,0 +1,48 @@
+/*
+ * 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.statusbar.notification.dagger;
+
+import android.content.Context;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.notification.init.NotificationsController;
+import com.android.systemui.statusbar.notification.init.NotificationsControllerImpl;
+import com.android.systemui.statusbar.notification.init.NotificationsControllerStub;
+
+import javax.inject.Singleton;
+
+import dagger.Lazy;
+import dagger.Module;
+import dagger.Provides;
+
+/** Module for classes related to the notifications data pipeline */
+@Module
+public class NotificationsModule {
+    /** Initializes the notification data pipeline (can be disabled via config). */
+    @Singleton
+    @Provides
+    static NotificationsController provideNotificationsController(
+            Context context,
+            Lazy<NotificationsControllerImpl> realController,
+            Lazy<NotificationsControllerStub> stubController) {
+        if (context.getResources().getBoolean(R.bool.config_renderNotifications)) {
+            return realController.get();
+        } else {
+            return stubController.get();
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt
new file mode 100644
index 0000000..9da8b8a3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.statusbar.notification.init
+
+import android.service.notification.StatusBarNotification
+import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption
+import com.android.systemui.statusbar.NotificationPresenter
+import com.android.systemui.statusbar.notification.NotificationActivityStarter
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl
+import com.android.systemui.statusbar.notification.stack.NotificationListContainer
+import com.android.systemui.statusbar.phone.StatusBar
+import java.io.FileDescriptor
+import java.io.PrintWriter
+
+/**
+ * The master controller for all notifications-related work
+ *
+ * Split into two implementations: [NotificationsControllerImpl] (most cases) and
+ * [NotificationsControllerStub] (for builds that disable notification rendering).
+ */
+interface NotificationsController {
+    fun initialize(
+        statusBar: StatusBar,
+        presenter: NotificationPresenter,
+        listContainer: NotificationListContainer,
+        notificationActivityStarter: NotificationActivityStarter,
+        bindRowCallback: NotificationRowBinderImpl.BindRowCallback
+    )
+
+    fun requestNotificationUpdate(reason: String)
+    fun resetUserExpandedStates()
+    fun setNotificationSnoozed(sbn: StatusBarNotification, snoozeOption: SnoozeOption)
+    fun getActiveNotificationsCount(): Int
+    fun setNotificationSnoozed(sbn: StatusBarNotification, hoursToSnooze: Int)
+    fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>, dumpTruck: Boolean)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
new file mode 100644
index 0000000..61e3192
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
@@ -0,0 +1,157 @@
+/*
+ * 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.statusbar.notification.init
+
+import android.service.notification.StatusBarNotification
+import com.android.systemui.bubbles.BubbleController
+import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption
+import com.android.systemui.statusbar.FeatureFlags
+import com.android.systemui.statusbar.NotificationListener
+import com.android.systemui.statusbar.NotificationPresenter
+import com.android.systemui.statusbar.notification.NotificationActivityStarter
+import com.android.systemui.statusbar.notification.NotificationClicker
+import com.android.systemui.statusbar.notification.NotificationEntryManager
+import com.android.systemui.statusbar.notification.NotificationListController
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl
+import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer
+import com.android.systemui.statusbar.notification.stack.NotificationListContainer
+import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper
+import com.android.systemui.statusbar.phone.NotificationGroupManager
+import com.android.systemui.statusbar.phone.StatusBar
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.statusbar.policy.HeadsUpManager
+import com.android.systemui.statusbar.policy.RemoteInputUriController
+import dagger.Lazy
+import java.io.FileDescriptor
+import java.io.PrintWriter
+import java.util.Optional
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/**
+ * Master controller for all notifications-related work
+ *
+ * At the moment exposes a number of event-handler-esque methods; these are for historical reasons.
+ * Once we migrate away from the need for such things, this class becomes primarily a place to do
+ * any initialization work that notifications require.
+ */
+@Singleton
+class NotificationsControllerImpl @Inject constructor(
+    private val featureFlags: FeatureFlags,
+    private val notificationListener: NotificationListener,
+    private val entryManager: NotificationEntryManager,
+    private val newNotifPipeline: Lazy<NotifPipelineInitializer>,
+    private val deviceProvisionedController: DeviceProvisionedController,
+    private val notificationRowBinder: NotificationRowBinderImpl,
+    private val remoteInputUriController: RemoteInputUriController,
+    private val bubbleController: BubbleController,
+    private val groupManager: NotificationGroupManager,
+    private val groupAlertTransferHelper: NotificationGroupAlertTransferHelper,
+    private val headsUpManager: HeadsUpManager
+) : NotificationsController {
+
+    override fun initialize(
+        statusBar: StatusBar,
+        presenter: NotificationPresenter,
+        listContainer: NotificationListContainer,
+        notificationActivityStarter: NotificationActivityStarter,
+        bindRowCallback: NotificationRowBinderImpl.BindRowCallback
+    ) {
+        notificationListener.registerAsSystemService()
+
+        val listController =
+                NotificationListController(
+                        entryManager,
+                        listContainer,
+                        deviceProvisionedController)
+        listController.bind()
+
+        notificationRowBinder.setNotificationClicker(
+                NotificationClicker(
+                        Optional.of(statusBar),
+                        bubbleController,
+                        notificationActivityStarter))
+        notificationRowBinder.setUpWithPresenter(
+                presenter,
+                listContainer,
+                headsUpManager,
+                bindRowCallback)
+
+        if (featureFlags.isNewNotifPipelineEnabled) {
+            newNotifPipeline.get().initialize(notificationListener, notificationRowBinder)
+        }
+
+        if (featureFlags.isNewNotifPipelineRenderingEnabled) {
+            // TODO
+        } else {
+            notificationRowBinder.setInflationCallback(entryManager)
+
+            remoteInputUriController.attach(entryManager)
+            groupAlertTransferHelper.bind(entryManager, groupManager)
+            headsUpManager.addListener(groupManager)
+            headsUpManager.addListener(groupAlertTransferHelper)
+            groupManager.setHeadsUpManager(headsUpManager)
+            groupAlertTransferHelper.setHeadsUpManager(headsUpManager)
+
+            entryManager.attach(notificationListener)
+        }
+    }
+
+    override fun dump(
+        fd: FileDescriptor,
+        pw: PrintWriter,
+        args: Array<String>,
+        dumpTruck: Boolean
+    ) {
+        if (dumpTruck) {
+            entryManager.dump(pw, "  ")
+        }
+        groupManager.dump(fd, pw, args)
+    }
+
+    // TODO: Convert all functions below this line into listeners instead of public methods
+
+    override fun requestNotificationUpdate(reason: String) {
+        entryManager.updateNotifications(reason)
+    }
+
+    override fun resetUserExpandedStates() {
+        for (entry in entryManager.visibleNotifications) {
+            entry.resetUserExpansion()
+        }
+    }
+
+    override fun setNotificationSnoozed(sbn: StatusBarNotification, snoozeOption: SnoozeOption) {
+        if (snoozeOption.snoozeCriterion != null) {
+            notificationListener.snoozeNotification(sbn.key, snoozeOption.snoozeCriterion.id)
+        } else {
+            notificationListener.snoozeNotification(
+                    sbn.key,
+                    snoozeOption.minutesToSnoozeFor * 60 * 1000.toLong())
+        }
+    }
+
+    override fun getActiveNotificationsCount(): Int {
+        return entryManager.activeNotificationsCount
+    }
+
+    override fun setNotificationSnoozed(sbn: StatusBarNotification, hoursToSnooze: Int) {
+        notificationListener.snoozeNotification(
+                sbn.key,
+                hoursToSnooze * 60 * 60 * 1000.toLong())
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt
new file mode 100644
index 0000000..ded855d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.statusbar.notification.init
+
+import android.service.notification.StatusBarNotification
+import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption
+import com.android.systemui.statusbar.NotificationListener
+import com.android.systemui.statusbar.NotificationPresenter
+import com.android.systemui.statusbar.notification.NotificationActivityStarter
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl
+import com.android.systemui.statusbar.notification.stack.NotificationListContainer
+import com.android.systemui.statusbar.phone.StatusBar
+import java.io.FileDescriptor
+import java.io.PrintWriter
+import javax.inject.Inject
+
+/**
+ * Implementation of [NotificationsController] that's used when notifications rendering is disabled.
+ */
+class NotificationsControllerStub @Inject constructor(
+    private val notificationListener: NotificationListener
+) : NotificationsController {
+
+    override fun initialize(
+        statusBar: StatusBar,
+        presenter: NotificationPresenter,
+        listContainer: NotificationListContainer,
+        notificationActivityStarter: NotificationActivityStarter,
+        bindRowCallback: NotificationRowBinderImpl.BindRowCallback
+    ) {
+        // Always connect the listener even if notification-handling is disabled. Being a listener
+        // grants special permissions and it's not clear if other things will break if we lose those
+        notificationListener.registerAsSystemService()
+    }
+
+    override fun requestNotificationUpdate(reason: String) {
+    }
+
+    override fun resetUserExpandedStates() {
+    }
+
+    override fun setNotificationSnoozed(sbn: StatusBarNotification, snoozeOption: SnoozeOption) {
+    }
+
+    override fun setNotificationSnoozed(sbn: StatusBarNotification, hoursToSnooze: Int) {
+    }
+
+    override fun getActiveNotificationsCount(): Int {
+        return 0
+    }
+
+    override fun dump(
+        fd: FileDescriptor,
+        pw: PrintWriter,
+        args: Array<String>,
+        dumpTruck: Boolean
+    ) {
+        pw.println()
+        pw.println("Notification handling disabled")
+        pw.println()
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 1726b488..11f7079 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -168,12 +168,10 @@
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.CrossFadeHelper;
 import com.android.systemui.statusbar.EmptyShadeView;
-import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.GestureRecorder;
 import com.android.systemui.statusbar.KeyboardShortcuts;
 import com.android.systemui.statusbar.KeyguardIndicationController;
 import com.android.systemui.statusbar.NavigationBarController;
-import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationPresenter;
@@ -191,15 +189,11 @@
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.NotificationAlertingManager;
-import com.android.systemui.statusbar.notification.NotificationClicker;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
-import com.android.systemui.statusbar.notification.NotificationListController;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
-import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer;
+import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -218,7 +212,6 @@
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
-import com.android.systemui.statusbar.policy.RemoteInputUriController;
 import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.volume.VolumeComponent;
@@ -356,7 +349,6 @@
 
     private final Object mQueueLock = new Object();
 
-    private final FeatureFlags mFeatureFlags;
     private final StatusBarIconController mIconController;
     private final PulseExpansionHandler mPulseExpansionHandler;
     private final NotificationWakeUpCoordinator mWakeUpCoordinator;
@@ -365,7 +357,6 @@
     private final HeadsUpManagerPhone mHeadsUpManager;
     private final DynamicPrivacyController mDynamicPrivacyController;
     private final BypassHeadsUpNotifier mBypassHeadsUpNotifier;
-    private final Lazy<NotifPipelineInitializer> mNewNotifPipeline;
     private final FalsingManager mFalsingManager;
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final ConfigurationController mConfigurationController;
@@ -374,7 +365,6 @@
     private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
     private final Provider<StatusBarComponent.Builder> mStatusBarComponentBuilder;
     private final PluginManager mPluginManager;
-    private final RemoteInputUriController mRemoteInputUriController;
     private final Optional<Divider> mDividerOptional;
     private final StatusBarNotificationActivityStarter.Builder
             mStatusBarNotificationActivityStarterBuilder;
@@ -387,8 +377,8 @@
     private final KeyguardDismissUtil mKeyguardDismissUtil;
     private final ExtensionController mExtensionController;
     private final UserInfoControllerImpl mUserInfoControllerImpl;
-    private final NotificationRowBinderImpl mNotificationRowBinder;
     private final DismissCallbackRegistry mDismissCallbackRegistry;
+    private NotificationsController mNotificationsController;
 
     // expanded notifications
     // the sliding/resizing panel within the notification window
@@ -412,8 +402,6 @@
 
     private final NotificationGutsManager mGutsManager;
     private final NotificationLogger mNotificationLogger;
-    private final NotificationEntryManager mEntryManager;
-    private NotificationListController mNotificationListController;
     private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
     private final NotificationViewHierarchyManager mViewHierarchyManager;
     private final KeyguardViewMediator mKeyguardViewMediator;
@@ -589,7 +577,7 @@
                 @Override
                 public void onStrongAuthStateChanged(int userId) {
                     super.onStrongAuthStateChanged(userId);
-                    mEntryManager.updateNotifications("onStrongAuthStateChanged");
+                    mNotificationsController.requestNotificationUpdate("onStrongAuthStateChanged");
                 }
             };
     private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
@@ -614,7 +602,7 @@
     @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
     public StatusBar(
             Context context,
-            FeatureFlags featureFlags,
+            NotificationsController notificationsController,
             LightBarController lightBarController,
             AutoHideController autoHideController,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -626,13 +614,11 @@
             HeadsUpManagerPhone headsUpManagerPhone,
             DynamicPrivacyController dynamicPrivacyController,
             BypassHeadsUpNotifier bypassHeadsUpNotifier,
-            Lazy<NotifPipelineInitializer> newNotifPipeline,
             FalsingManager falsingManager,
             BroadcastDispatcher broadcastDispatcher,
             RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler,
             NotificationGutsManager notificationGutsManager,
             NotificationLogger notificationLogger,
-            NotificationEntryManager notificationEntryManager,
             NotificationInterruptionStateProvider notificationInterruptionStateProvider,
             NotificationViewHierarchyManager notificationViewHierarchyManager,
             KeyguardViewMediator keyguardViewMediator,
@@ -653,12 +639,10 @@
             VibratorHelper vibratorHelper,
             BubbleController bubbleController,
             NotificationGroupManager groupManager,
-            NotificationGroupAlertTransferHelper groupAlertTransferHelper,
             VisualStabilityManager visualStabilityManager,
             DeviceProvisionedController deviceProvisionedController,
             NavigationBarController navigationBarController,
             Lazy<AssistManager> assistManagerLazy,
-            NotificationListener notificationListener,
             ConfigurationController configurationController,
             NotificationShadeWindowController notificationShadeWindowController,
             LockscreenLockIconController lockscreenLockIconController,
@@ -676,7 +660,6 @@
             Optional<Recents> recentsOptional,
             Provider<StatusBarComponent.Builder> statusBarComponentBuilder,
             PluginManager pluginManager,
-            RemoteInputUriController remoteInputUriController,
             Optional<Divider> dividerOptional,
             LightsOutNotifController lightsOutNotifController,
             StatusBarNotificationActivityStarter.Builder
@@ -692,10 +675,9 @@
             KeyguardDismissUtil keyguardDismissUtil,
             ExtensionController extensionController,
             UserInfoControllerImpl userInfoControllerImpl,
-            NotificationRowBinderImpl notificationRowBinder,
             DismissCallbackRegistry dismissCallbackRegistry) {
         super(context);
-        mFeatureFlags = featureFlags;
+        mNotificationsController = notificationsController;
         mLightBarController = lightBarController;
         mAutoHideController = autoHideController;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -707,13 +689,11 @@
         mHeadsUpManager = headsUpManagerPhone;
         mDynamicPrivacyController = dynamicPrivacyController;
         mBypassHeadsUpNotifier = bypassHeadsUpNotifier;
-        mNewNotifPipeline = newNotifPipeline;
         mFalsingManager = falsingManager;
         mBroadcastDispatcher = broadcastDispatcher;
         mRemoteInputQuickSettingsDisabler = remoteInputQuickSettingsDisabler;
         mGutsManager = notificationGutsManager;
         mNotificationLogger = notificationLogger;
-        mEntryManager = notificationEntryManager;
         mNotificationInterruptionStateProvider = notificationInterruptionStateProvider;
         mViewHierarchyManager = notificationViewHierarchyManager;
         mKeyguardViewMediator = keyguardViewMediator;
@@ -734,12 +714,10 @@
         mVibratorHelper = vibratorHelper;
         mBubbleController = bubbleController;
         mGroupManager = groupManager;
-        mGroupAlertTransferHelper = groupAlertTransferHelper;
         mVisualStabilityManager = visualStabilityManager;
         mDeviceProvisionedController = deviceProvisionedController;
         mNavigationBarController = navigationBarController;
         mAssistManagerLazy = assistManagerLazy;
-        mNotificationListener = notificationListener;
         mConfigurationController = configurationController;
         mNotificationShadeWindowController = notificationShadeWindowController;
         mLockscreenLockIconController = lockscreenLockIconController;
@@ -757,7 +735,6 @@
         mRecentsOptional = recentsOptional;
         mStatusBarComponentBuilder = statusBarComponentBuilder;
         mPluginManager = pluginManager;
-        mRemoteInputUriController = remoteInputUriController;
         mDividerOptional = dividerOptional;
         mStatusBarNotificationActivityStarterBuilder = statusBarNotificationActivityStarterBuilder;
         mShadeController = shadeController;
@@ -771,12 +748,11 @@
         mKeyguardDismissUtil = keyguardDismissUtil;
         mExtensionController = extensionController;
         mUserInfoControllerImpl = userInfoControllerImpl;
-        mNotificationRowBinder = notificationRowBinder;
         mDismissCallbackRegistry = dismissCallbackRegistry;
 
         mBubbleExpandListener =
                 (isExpanding, key) -> {
-                    mEntryManager.updateNotifications("onBubbleExpandChanged");
+                    mNotificationsController.requestNotificationUpdate("onBubbleExpandChanged");
                     updateScrimController();
                 };
 
@@ -786,11 +762,10 @@
 
     @Override
     public void start() {
-        mNotificationListener.registerAsSystemService();
         mScreenLifecycle.addObserver(mScreenObserver);
         mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
         mUiModeManager = mContext.getSystemService(UiModeManager.class);
-        mBypassHeadsUpNotifier.setUp(mEntryManager);
+        mBypassHeadsUpNotifier.setUp();
         mBubbleController.setExpandListener(mBubbleExpandListener);
         mActivityIntentHelper = new ActivityIntentHelper(mContext);
 
@@ -1060,12 +1035,8 @@
         mConfigurationController.addCallback(mHeadsUpManager);
         mHeadsUpManager.addListener(this);
         mHeadsUpManager.addListener(mNotificationPanelViewController.getOnHeadsUpChangedListener());
-        mHeadsUpManager.addListener(mGroupManager);
-        mHeadsUpManager.addListener(mGroupAlertTransferHelper);
         mHeadsUpManager.addListener(mVisualStabilityManager);
         mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager);
-        mGroupManager.setHeadsUpManager(mHeadsUpManager);
-        mGroupAlertTransferHelper.setHeadsUpManager(mHeadsUpManager);
         mNotificationLogger.setHeadsUpManager(mHeadsUpManager);
 
         createNavigationBar(result);
@@ -1243,16 +1214,10 @@
         mPresenter = new StatusBarNotificationPresenter(mContext, mNotificationPanelViewController,
                 mHeadsUpManager, mNotificationShadeWindowView, mStackScroller, mDozeScrimController,
                 mScrimController, mActivityLaunchAnimator, mDynamicPrivacyController,
-                mNotificationAlertingManager, mNotificationRowBinder, mKeyguardStateController,
+                mNotificationAlertingManager, mKeyguardStateController,
                 mKeyguardIndicationController,
                 this /* statusBar */, mShadeController, mCommandQueue, mInitController);
 
-        mNotificationListController =
-                new NotificationListController(
-                        mEntryManager,
-                        (NotificationListContainer) mStackScroller,
-                        mDeviceProvisionedController);
-
         mNotificationShelf.setOnActivatedListener(mPresenter);
         mRemoteInputManager.getController().addCallback(mNotificationShadeWindowController);
 
@@ -1266,22 +1231,12 @@
 
         mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
 
-        if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
-            mNotificationRowBinder.setInflationCallback(mEntryManager);
-        }
-
-        mRemoteInputUriController.attach(mEntryManager);
-
-        mNotificationRowBinder.setNotificationClicker(new NotificationClicker(
-                Optional.of(this), mBubbleController, mNotificationActivityStarter));
-
-        mGroupAlertTransferHelper.bind(mEntryManager, mGroupManager);
-        mNotificationListController.bind();
-
-        if (mFeatureFlags.isNewNotifPipelineEnabled()) {
-            mNewNotifPipeline.get().initialize(mNotificationListener, mNotificationRowBinder);
-        }
-        mEntryManager.attach(mNotificationListener);
+        mNotificationsController.initialize(
+                this,
+                mPresenter,
+                (NotificationListContainer) mStackScroller,
+                mNotificationActivityStarter,
+                mPresenter);
     }
 
     /**
@@ -1518,7 +1473,7 @@
      * @param reason why we're requesting a notification update
      */
     public void requestNotificationUpdate(String reason) {
-        mEntryManager.updateNotifications(reason);
+        mNotificationsController.requestNotificationUpdate(reason);
     }
 
     /**
@@ -1726,7 +1681,7 @@
 
     @Override
     public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
-        mEntryManager.updateNotifications("onHeadsUpStateChanged");
+        mNotificationsController.requestNotificationUpdate("onHeadsUpStateChanged");
         if (mStatusBarStateController.isDozing() && isHeadsUp) {
             entry.setPulseSuppressed(false);
             mDozeServiceHost.fireNotificationPulse(entry);
@@ -2485,11 +2440,9 @@
             mStatusBarKeyguardViewManager.dump(pw);
         }
 
-        if (DUMPTRUCK) {
-            synchronized (mEntryManager) {
-                mEntryManager.dump(pw, "  ");
-            }
+        mNotificationsController.dump(fd, pw, args, DUMPTRUCK);
 
+        if (DUMPTRUCK) {
             if (false) {
                 pw.println("see the logcat for a dump of the views we have created.");
                 // must happen on ui thread
@@ -2512,11 +2465,6 @@
         } else {
             pw.println("  mHeadsUpManager: null");
         }
-        if (mGroupManager != null) {
-            mGroupManager.dump(fd, pw, args);
-        } else {
-            pw.println("  mGroupManager: null");
-        }
 
         if (mBubbleController != null) {
             mBubbleController.dump(fd, pw, args);
@@ -2747,9 +2695,7 @@
     };
 
     public void resetUserExpandedStates() {
-        for (NotificationEntry entry : mEntryManager.getVisibleNotifications()) {
-            entry.resetUserExpansion();
-        }
+        mNotificationsController.resetUserExpandedStates();
     }
 
     private void executeWhenUnlocked(OnDismissAction action, boolean requiresShadeOpen) {
@@ -2855,7 +2801,7 @@
         try {
             // consider the transition from peek to expanded to be a panel open,
             // but not one that clears notification effects.
-            int notificationLoad = mEntryManager.getActiveNotificationsCount();
+            int notificationLoad = mNotificationsController.getActiveNotificationsCount();
             mBarService.onPanelRevealed(false, notificationLoad);
         } catch (RemoteException ex) {
             // Won't fail unless the world has ended.
@@ -2873,7 +2819,7 @@
                     !mPresenter.isPresenterFullyCollapsed() &&
                             (mState == StatusBarState.SHADE
                                     || mState == StatusBarState.SHADE_LOCKED);
-            int notificationLoad = mEntryManager.getActiveNotificationsCount();
+            int notificationLoad = mNotificationsController.getActiveNotificationsCount();
             if (pinnedHeadsUp && mPresenter.isPresenterFullyCollapsed()) {
                 notificationLoad = 1;
             }
@@ -3514,7 +3460,7 @@
         updateQsExpansionEnabled();
         mKeyguardViewMediator.setDozing(mDozing);
 
-        mEntryManager.updateNotifications("onDozingChanged");
+        mNotificationsController.requestNotificationUpdate("onDozingChanged");
         updateDozingState();
         mDozeServiceHost.updateDozing();
         updateScrimController();
@@ -3965,7 +3911,6 @@
     protected ViewGroup mStackScroller;
 
     private final NotificationGroupManager mGroupManager;
-    private final NotificationGroupAlertTransferHelper mGroupAlertTransferHelper;
 
     // handling reordering
     private final VisualStabilityManager mVisualStabilityManager;
@@ -4032,21 +3977,12 @@
         }
     };
 
-    private final NotificationListener mNotificationListener;
-
     public void setNotificationSnoozed(StatusBarNotification sbn, SnoozeOption snoozeOption) {
-        if (snoozeOption.getSnoozeCriterion() != null) {
-            mNotificationListener.snoozeNotification(sbn.getKey(),
-                    snoozeOption.getSnoozeCriterion().getId());
-        } else {
-            mNotificationListener.snoozeNotification(sbn.getKey(),
-                    snoozeOption.getMinutesToSnoozeFor() * 60 * 1000);
-        }
+        mNotificationsController.setNotificationSnoozed(sbn, snoozeOption);
     }
 
     public void setNotificationSnoozed(StatusBarNotification sbn, int hoursToSnooze) {
-        mNotificationListener.snoozeNotification(sbn.getKey(),
-                hoursToSnooze * 60 * 60 * 1000);
+        mNotificationsController.setNotificationSnoozed(sbn, hoursToSnooze);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java
index 7615bf8..15a0e08 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java
@@ -46,9 +46,7 @@
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NavigationBarController;
-import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -61,12 +59,10 @@
 import com.android.systemui.statusbar.notification.BypassHeadsUpNotifier;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationAlertingManager;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
-import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
-import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer;
+import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
@@ -77,7 +73,6 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
-import com.android.systemui.statusbar.policy.RemoteInputUriController;
 import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.volume.VolumeComponent;
@@ -105,7 +100,7 @@
     @Singleton
     static StatusBar provideStatusBar(
             Context context,
-            FeatureFlags featureFlags,
+            NotificationsController notificationsController,
             LightBarController lightBarController,
             AutoHideController autoHideController,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -117,13 +112,11 @@
             HeadsUpManagerPhone headsUpManagerPhone,
             DynamicPrivacyController dynamicPrivacyController,
             BypassHeadsUpNotifier bypassHeadsUpNotifier,
-            Lazy<NotifPipelineInitializer> newNotifPipeline,
             FalsingManager falsingManager,
             BroadcastDispatcher broadcastDispatcher,
             RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler,
             NotificationGutsManager notificationGutsManager,
             NotificationLogger notificationLogger,
-            NotificationEntryManager notificationEntryManager,
             NotificationInterruptionStateProvider notificationInterruptionStateProvider,
             NotificationViewHierarchyManager notificationViewHierarchyManager,
             KeyguardViewMediator keyguardViewMediator,
@@ -144,12 +137,10 @@
             VibratorHelper vibratorHelper,
             BubbleController bubbleController,
             NotificationGroupManager groupManager,
-            NotificationGroupAlertTransferHelper groupAlertTransferHelper,
             VisualStabilityManager visualStabilityManager,
             DeviceProvisionedController deviceProvisionedController,
             NavigationBarController navigationBarController,
             Lazy<AssistManager> assistManagerLazy,
-            NotificationListener notificationListener,
             ConfigurationController configurationController,
             NotificationShadeWindowController notificationShadeWindowController,
             LockscreenLockIconController lockscreenLockIconController,
@@ -167,7 +158,6 @@
             Optional<Recents> recentsOptional,
             Provider<StatusBarComponent.Builder> statusBarComponentBuilder,
             PluginManager pluginManager,
-            RemoteInputUriController remoteInputUriController,
             Optional<Divider> dividerOptional,
             LightsOutNotifController lightsOutNotifController,
             StatusBarNotificationActivityStarter.Builder
@@ -183,11 +173,10 @@
             KeyguardDismissUtil keyguardDismissUtil,
             ExtensionController extensionController,
             UserInfoControllerImpl userInfoControllerImpl,
-            NotificationRowBinderImpl notificationRowBinder,
             DismissCallbackRegistry dismissCallbackRegistry) {
         return new StatusBar(
                 context,
-                featureFlags,
+                notificationsController,
                 lightBarController,
                 autoHideController,
                 keyguardUpdateMonitor,
@@ -199,13 +188,11 @@
                 headsUpManagerPhone,
                 dynamicPrivacyController,
                 bypassHeadsUpNotifier,
-                newNotifPipeline,
                 falsingManager,
                 broadcastDispatcher,
                 remoteInputQuickSettingsDisabler,
                 notificationGutsManager,
                 notificationLogger,
-                notificationEntryManager,
                 notificationInterruptionStateProvider,
                 notificationViewHierarchyManager,
                 keyguardViewMediator,
@@ -226,12 +213,10 @@
                 vibratorHelper,
                 bubbleController,
                 groupManager,
-                groupAlertTransferHelper,
                 visualStabilityManager,
                 deviceProvisionedController,
                 navigationBarController,
                 assistManagerLazy,
-                notificationListener,
                 configurationController,
                 notificationShadeWindowController,
                 lockscreenLockIconController,
@@ -249,7 +234,6 @@
                 recentsOptional,
                 statusBarComponentBuilder,
                 pluginManager,
-                remoteInputUriController,
                 dividerOptional,
                 lightsOutNotifController,
                 statusBarNotificationActivityStarterBuilder,
@@ -264,7 +248,6 @@
                 keyguardDismissUtil,
                 extensionController,
                 userInfoControllerImpl,
-                notificationRowBinder,
                 dismissCallbackRegistry);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 1336b2de..2485513 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -141,7 +141,6 @@
             ActivityLaunchAnimator activityLaunchAnimator,
             DynamicPrivacyController dynamicPrivacyController,
             NotificationAlertingManager notificationAlertingManager,
-            NotificationRowBinderImpl notificationRowBinder,
             KeyguardStateController keyguardStateController,
             KeyguardIndicationController keyguardIndicationController,
             StatusBar statusBar,
@@ -216,8 +215,6 @@
             mEntryManager.addNotificationLifetimeExtender(mGutsManager);
             mEntryManager.addNotificationLifetimeExtenders(
                     remoteInputManager.getLifetimeExtenders());
-            notificationRowBinder.setUpWithPresenter(this, notifListContainer, mHeadsUpManager,
-                    this);
             mNotificationInterruptionStateProvider.setUpWithPresenter(
                     this, mHeadsUpManager, this::canHeadsUp);
             mLockscreenUserManager.setUpWithPresenter(this);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt
index 7c8c7c8..c1ebae7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt
@@ -19,6 +19,7 @@
 import android.content.ComponentName
 import android.content.Context
 import android.os.Binder
+import android.os.UserHandle
 import android.service.controls.Control
 import android.service.controls.DeviceTypes
 import android.testing.AndroidTestingRunner
@@ -38,6 +39,7 @@
 import org.mockito.Mockito.`when`
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
@@ -55,6 +57,9 @@
     @Mock
     private lateinit var mockControlsController: ControlsController
 
+    private val user = UserHandle.of(mContext.userId)
+    private val otherUser = UserHandle.of(user.identifier + 1)
+
     private val executor = FakeExecutor(FakeSystemClock())
     private lateinit var controller: ControlsBindingController
     private val providers = TestableControlsBindingControllerImpl.providers
@@ -75,6 +80,11 @@
     }
 
     @Test
+    fun testStartOnUser() {
+        assertEquals(user.identifier, controller.currentUserId)
+    }
+
+    @Test
     fun testBindAndLoad() {
         val callback: (List<Control>) -> Unit = {}
         controller.bindAndLoad(TEST_COMPONENT_NAME_1, callback)
@@ -145,6 +155,41 @@
             verify(it).unsubscribe()
         }
     }
+
+    @Test
+    fun testCurrentUserId() {
+        controller.changeUser(otherUser)
+        assertEquals(otherUser.identifier, controller.currentUserId)
+    }
+
+    @Test
+    fun testChangeUsers_providersHaveCorrectUser() {
+        controller.bindServices(listOf(TEST_COMPONENT_NAME_1))
+        controller.changeUser(otherUser)
+        controller.bindServices(listOf(TEST_COMPONENT_NAME_2))
+
+        val provider1 = providers.first { it.componentName == TEST_COMPONENT_NAME_1 }
+        assertEquals(user, provider1.user)
+        val provider2 = providers.first { it.componentName == TEST_COMPONENT_NAME_2 }
+        assertEquals(otherUser, provider2.user)
+    }
+
+    @Test
+    fun testChangeUsers_providersUnbound() {
+        controller.bindServices(listOf(TEST_COMPONENT_NAME_1))
+        controller.changeUser(otherUser)
+
+        val provider1 = providers.first { it.componentName == TEST_COMPONENT_NAME_1 }
+        verify(provider1).unbindService()
+
+        controller.bindServices(listOf(TEST_COMPONENT_NAME_2))
+        controller.changeUser(user)
+
+        reset(provider1)
+        val provider2 = providers.first { it.componentName == TEST_COMPONENT_NAME_2 }
+        verify(provider2).unbindService()
+        verify(provider1, never()).unbindService()
+    }
 }
 
 class TestableControlsBindingControllerImpl(
@@ -157,12 +202,16 @@
         val providers = mutableSetOf<ControlsProviderLifecycleManager>()
     }
 
+    // Replaces the real provider with a mock and puts the mock in a visible set.
+    // The mock has the same componentName and user as the real one would have
     override fun createProviderManager(component: ComponentName):
             ControlsProviderLifecycleManager {
+        val realProvider = super.createProviderManager(component)
         val provider = mock(ControlsProviderLifecycleManager::class.java)
         val token = Binder()
-        `when`(provider.componentName).thenReturn(component)
+        `when`(provider.componentName).thenReturn(realProvider.componentName)
         `when`(provider.token).thenReturn(token)
+        `when`(provider.user).thenReturn(realProvider.user)
         providers.add(provider)
         return provider
     }
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 be86a9c..897091f 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
@@ -17,7 +17,12 @@
 package com.android.systemui.controls.controller
 
 import android.app.PendingIntent
+import android.content.BroadcastReceiver
 import android.content.ComponentName
+import android.content.Context
+import android.content.ContextWrapper
+import android.content.Intent
+import android.os.UserHandle
 import android.provider.Settings
 import android.service.controls.Control
 import android.service.controls.DeviceTypes
@@ -26,6 +31,8 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.DumpController
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.controls.ControlStatus
 import com.android.systemui.controls.ui.ControlsUiController
 import com.android.systemui.util.concurrency.FakeExecutor
@@ -37,10 +44,11 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers
-import org.mockito.ArgumentMatchers.eq
 import org.mockito.Captor
 import org.mockito.Mock
+import org.mockito.Mockito
 import org.mockito.Mockito.`when`
+import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
@@ -61,18 +69,25 @@
     private lateinit var pendingIntent: PendingIntent
     @Mock
     private lateinit var persistenceWrapper: ControlsFavoritePersistenceWrapper
+    @Mock
+    private lateinit var broadcastDispatcher: BroadcastDispatcher
+    @Mock
+    private lateinit var listingController: ControlsListingController
 
     @Captor
     private lateinit var controlInfoListCaptor: ArgumentCaptor<List<ControlInfo>>
     @Captor
     private lateinit var controlLoadCallbackCaptor: ArgumentCaptor<(List<Control>) -> Unit>
+    @Captor
+    private lateinit var broadcastReceiverCaptor: ArgumentCaptor<BroadcastReceiver>
 
     private lateinit var delayableExecutor: FakeExecutor
     private lateinit var controller: ControlsController
 
     companion object {
         fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
-        fun <T : Any> safeEq(value: T): T = eq(value) ?: value
+        fun <T> eq(value: T): T = Mockito.eq(value) ?: value
+        fun <T> any(): T = Mockito.any<T>()
 
         private val TEST_COMPONENT = ComponentName("test.pkg", "test.class")
         private const val TEST_CONTROL_ID = "control1"
@@ -89,24 +104,39 @@
                 TEST_COMPONENT_2, TEST_CONTROL_ID_2, TEST_CONTROL_TITLE_2, TEST_DEVICE_TYPE_2)
     }
 
+    private val user = mContext.userId
+    private val otherUser = user + 1
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
         Settings.Secure.putInt(mContext.contentResolver,
                 ControlsControllerImpl.CONTROLS_AVAILABLE, 1)
+        Settings.Secure.putIntForUser(mContext.contentResolver,
+                ControlsControllerImpl.CONTROLS_AVAILABLE, 1, otherUser)
 
         delayableExecutor = FakeExecutor(FakeSystemClock())
 
+        val wrapper = object : ContextWrapper(mContext) {
+            override fun createContextAsUser(user: UserHandle, flags: Int): Context {
+                return baseContext
+            }
+        }
+
         controller = ControlsControllerImpl(
-                mContext,
+                wrapper,
                 delayableExecutor,
                 uiController,
                 bindingController,
+                listingController,
+                broadcastDispatcher,
                 Optional.of(persistenceWrapper),
                 dumpController
         )
         assertTrue(controller.available)
+        verify(broadcastDispatcher).registerReceiver(
+                capture(broadcastReceiverCaptor), any(), any(), eq(UserHandle.ALL))
     }
 
     private fun builderFromInfo(controlInfo: ControlInfo): Control.StatelessBuilder {
@@ -115,6 +145,11 @@
     }
 
     @Test
+    fun testStartOnUser() {
+        assertEquals(user, controller.currentUserId)
+    }
+
+    @Test
     fun testStartWithoutFavorites() {
         assertTrue(controller.getFavoriteControls().isEmpty())
     }
@@ -127,6 +162,8 @@
                 delayableExecutor,
                 uiController,
                 bindingController,
+                listingController,
+                broadcastDispatcher,
                 Optional.of(persistenceWrapper),
                 dumpController
         )
@@ -190,7 +227,7 @@
         controller.loadForComponent(TEST_COMPONENT) {}
 
         reset(persistenceWrapper)
-        verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT),
+        verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
                 capture(controlLoadCallbackCaptor))
 
         controlLoadCallbackCaptor.value.invoke(listOf(control))
@@ -262,7 +299,7 @@
             assertEquals(ControlStatus(control, false), controlStatus)
         }
 
-        verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT),
+        verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
                 capture(controlLoadCallbackCaptor))
 
         controlLoadCallbackCaptor.value.invoke(listOf(control))
@@ -287,7 +324,7 @@
             assertEquals(ControlStatus(control2, false), controlStatus2)
         }
 
-        verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT),
+        verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
                 capture(controlLoadCallbackCaptor))
 
         controlLoadCallbackCaptor.value.invoke(listOf(control, control2))
@@ -309,7 +346,7 @@
             assertTrue(controlStatus.removed)
         }
 
-        verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT),
+        verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
                 capture(controlLoadCallbackCaptor))
 
         controlLoadCallbackCaptor.value.invoke(emptyList())
@@ -325,7 +362,7 @@
 
         controller.loadForComponent(TEST_COMPONENT) {}
 
-        verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT),
+        verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
                 capture(controlLoadCallbackCaptor))
 
         controlLoadCallbackCaptor.value.invoke(listOf(control))
@@ -358,4 +395,26 @@
         controller.clearFavorites()
         assertTrue(controller.getFavoriteControls().isEmpty())
     }
-}
+
+    @Test
+    fun testSwitchUsers() {
+        controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+
+        reset(persistenceWrapper)
+        val intent = Intent(Intent.ACTION_USER_SWITCHED).apply {
+            putExtra(Intent.EXTRA_USER_HANDLE, otherUser)
+        }
+        val pendingResult = mock(BroadcastReceiver.PendingResult::class.java)
+        `when`(pendingResult.sendingUserId).thenReturn(otherUser)
+        broadcastReceiverCaptor.value.pendingResult = pendingResult
+
+        broadcastReceiverCaptor.value.onReceive(mContext, intent)
+
+        verify(persistenceWrapper).changeFile(any())
+        verify(persistenceWrapper).readFavorites()
+        verify(bindingController).changeUser(UserHandle.of(otherUser))
+        verify(listingController).changeUser(UserHandle.of(otherUser))
+        assertTrue(controller.getFavoriteControls().isEmpty())
+        assertEquals(otherUser, controller.currentUserId)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt
index 4fc1cca..3f1435b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.controls.controller
 
 import android.content.ComponentName
+import android.os.UserHandle
 import android.service.controls.Control
 import android.service.controls.IControlsActionCallback
 import android.service.controls.IControlsLoadCallback
@@ -86,6 +87,7 @@
                 loadCallback,
                 actionCallback,
                 subscriber,
+                UserHandle.of(0),
                 componentName
         )
     }
@@ -148,4 +150,4 @@
                 eq(actionCallback))
         assertEquals(action, wrapperCaptor.getValue().getWrappedAction())
     }
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
index f09aab9..85e937e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
@@ -17,12 +17,15 @@
 package com.android.systemui.controls.management
 
 import android.content.ComponentName
+import android.content.Context
+import android.content.ContextWrapper
 import android.content.pm.ServiceInfo
+import android.os.UserHandle
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.settingslib.applications.ServiceListing
-import com.android.settingslib.widget.CandidateInfo
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.time.FakeSystemClock
 import org.junit.After
@@ -69,13 +72,22 @@
     private var serviceListingCallbackCaptor =
             ArgumentCaptor.forClass(ServiceListing.Callback::class.java)
 
+    private val user = mContext.userId
+    private val otherUser = user + 1
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
         `when`(serviceInfo.componentName).thenReturn(componentName)
 
-        controller = ControlsListingControllerImpl(mContext, executor, mockSL)
+        val wrapper = object : ContextWrapper(mContext) {
+            override fun createContextAsUser(user: UserHandle, flags: Int): Context {
+                return baseContext
+            }
+        }
+
+        controller = ControlsListingControllerImpl(wrapper, executor, { mockSL })
         verify(mockSL).addCallback(capture(serviceListingCallbackCaptor))
     }
 
@@ -86,6 +98,11 @@
     }
 
     @Test
+    fun testStartsOnUser() {
+        assertEquals(user, controller.currentUserId)
+    }
+
+    @Test
     fun testNoServices_notListening() {
         assertTrue(controller.getCurrentServices().isEmpty())
     }
@@ -167,8 +184,9 @@
         controller.addCallback(mockCallbackOther)
 
         @Suppress("unchecked_cast")
-        val captor: ArgumentCaptor<List<CandidateInfo>> =
-                ArgumentCaptor.forClass(List::class.java) as ArgumentCaptor<List<CandidateInfo>>
+        val captor: ArgumentCaptor<List<ControlsServiceInfo>> =
+                ArgumentCaptor.forClass(List::class.java)
+                        as ArgumentCaptor<List<ControlsServiceInfo>>
 
         executor.runAllReady()
         reset(mockCallback)
@@ -185,4 +203,11 @@
         assertEquals(1, captor.value.size)
         assertEquals(componentName.flattenToString(), captor.value[0].key)
     }
+
+    @Test
+    fun testChangeUser() {
+        controller.changeUser(UserHandle.of(otherUser))
+        executor.runAllReady()
+        assertEquals(otherUser, controller.currentUserId)
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java
index 752e145..00d333fb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java
@@ -53,6 +53,7 @@
         when(config.pickupGestureEnabled(anyInt())).thenReturn(false);
         when(config.pulseOnNotificationEnabled(anyInt())).thenReturn(true);
         when(config.alwaysOnEnabled(anyInt())).thenReturn(false);
+        when(config.dozeSuppressed(anyInt())).thenReturn(false);
         when(config.enabled(anyInt())).thenReturn(true);
         when(config.getWakeLockScreenDebounce()).thenReturn(0L);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
index f4cf314..63cbca9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
@@ -139,6 +139,65 @@
     }
 
     @Test
+    public void testInitialize_dozeSuppressed_alwaysOnDisabled_goesToDoze() {
+        when(mConfigMock.dozeSuppressed(anyInt())).thenReturn(true);
+        when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false);
+
+        mMachine.requestState(INITIALIZED);
+
+        verify(mPartMock).transitionTo(INITIALIZED, DOZE);
+        assertEquals(DOZE, mMachine.getState());
+    }
+
+    @Test
+    public void testInitialize_dozeSuppressed_alwaysOnEnabled_goesToDoze() {
+        when(mConfigMock.dozeSuppressed(anyInt())).thenReturn(true);
+        when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
+
+        mMachine.requestState(INITIALIZED);
+
+        verify(mPartMock).transitionTo(INITIALIZED, DOZE);
+        assertEquals(DOZE, mMachine.getState());
+    }
+
+    @Test
+    public void testInitialize_dozeSuppressed_afterDocked_goesToDoze() {
+        when(mConfigMock.dozeSuppressed(anyInt())).thenReturn(true);
+        when(mDockManager.isDocked()).thenReturn(true);
+
+        mMachine.requestState(INITIALIZED);
+
+        verify(mPartMock).transitionTo(INITIALIZED, DOZE);
+        assertEquals(DOZE, mMachine.getState());
+    }
+
+    @Test
+    public void testInitialize_dozeSuppressed_alwaysOnDisabled_afterDockPaused_goesToDoze() {
+        when(mConfigMock.dozeSuppressed(anyInt())).thenReturn(true);
+        when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false);
+        when(mDockManager.isDocked()).thenReturn(true);
+        when(mDockManager.isHidden()).thenReturn(true);
+
+        mMachine.requestState(INITIALIZED);
+
+        verify(mPartMock).transitionTo(INITIALIZED, DOZE);
+        assertEquals(DOZE, mMachine.getState());
+    }
+
+    @Test
+    public void testInitialize_dozeSuppressed_alwaysOnEnabled_afterDockPaused_goesToDoze() {
+        when(mConfigMock.dozeSuppressed(anyInt())).thenReturn(true);
+        when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
+        when(mDockManager.isDocked()).thenReturn(true);
+        when(mDockManager.isHidden()).thenReturn(true);
+
+        mMachine.requestState(INITIALIZED);
+
+        verify(mPartMock).transitionTo(INITIALIZED, DOZE);
+        assertEquals(DOZE, mMachine.getState());
+    }
+
+    @Test
     public void testPulseDone_goesToDoze() {
         when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false);
         mMachine.requestState(INITIALIZED);
@@ -165,6 +224,20 @@
     }
 
     @Test
+    public void testPulseDone_dozeSuppressed_goesToSuppressed() {
+        when(mConfigMock.dozeSuppressed(anyInt())).thenReturn(true);
+        when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
+        mMachine.requestState(INITIALIZED);
+        mMachine.requestPulse(DozeLog.PULSE_REASON_NOTIFICATION);
+        mMachine.requestState(DOZE_PULSING);
+
+        mMachine.requestState(DOZE_PULSE_DONE);
+
+        verify(mPartMock).transitionTo(DOZE_PULSE_DONE, DOZE);
+        assertEquals(DOZE, mMachine.getState());
+    }
+
+    @Test
     public void testPulseDone_afterDocked_goesToDockedAoD() {
         when(mDockManager.isDocked()).thenReturn(true);
         mMachine.requestState(INITIALIZED);
@@ -178,6 +251,20 @@
     }
 
     @Test
+    public void testPulseDone_dozeSuppressed_afterDocked_goesToDoze() {
+        when(mConfigMock.dozeSuppressed(anyInt())).thenReturn(true);
+        when(mDockManager.isDocked()).thenReturn(true);
+        mMachine.requestState(INITIALIZED);
+        mMachine.requestPulse(DozeLog.PULSE_REASON_NOTIFICATION);
+        mMachine.requestState(DOZE_PULSING);
+
+        mMachine.requestState(DOZE_PULSE_DONE);
+
+        verify(mPartMock).transitionTo(DOZE_PULSE_DONE, DOZE);
+        assertEquals(DOZE, mMachine.getState());
+    }
+
+    @Test
     public void testPulseDone_afterDockPaused_goesToDoze() {
         when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
         when(mDockManager.isDocked()).thenReturn(true);
@@ -193,6 +280,22 @@
     }
 
     @Test
+    public void testPulseDone_dozeSuppressed_afterDockPaused_goesToDoze() {
+        when(mConfigMock.dozeSuppressed(anyInt())).thenReturn(true);
+        when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
+        when(mDockManager.isDocked()).thenReturn(true);
+        when(mDockManager.isHidden()).thenReturn(true);
+        mMachine.requestState(INITIALIZED);
+        mMachine.requestPulse(DozeLog.PULSE_REASON_NOTIFICATION);
+        mMachine.requestState(DOZE_PULSING);
+
+        mMachine.requestState(DOZE_PULSE_DONE);
+
+        verify(mPartMock).transitionTo(DOZE_PULSE_DONE, DOZE);
+        assertEquals(DOZE, mMachine.getState());
+    }
+
+    @Test
     public void testFinished_staysFinished() {
         mMachine.requestState(INITIALIZED);
         mMachine.requestState(FINISH);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressedHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressedHandlerTest.java
new file mode 100644
index 0000000..5bdca76
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressedHandlerTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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.doze;
+
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.hardware.display.AmbientDisplayConfiguration;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper.RunWithLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.doze.DozeSuppressedHandler.DozeSuppressedSettingObserver;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public class DozeSuppressedHandlerTest extends SysuiTestCase {
+    @Mock private DozeMachine mMachine;
+    @Mock private DozeSuppressedSettingObserver mObserver;
+    private AmbientDisplayConfiguration mConfig;
+    private DozeSuppressedHandler mSuppressedHandler;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mConfig = DozeConfigurationUtil.createMockConfig();
+        mSuppressedHandler = new DozeSuppressedHandler(mContext, mConfig, mMachine, mObserver);
+    }
+
+    @Test
+    public void transitionTo_initialized_registersObserver() throws Exception {
+        mSuppressedHandler.transitionTo(DozeMachine.State.UNINITIALIZED,
+                DozeMachine.State.INITIALIZED);
+
+        verify(mObserver).register();
+    }
+
+    @Test
+    public void transitionTo_finish_unregistersObserver() throws Exception {
+        mSuppressedHandler.transitionTo(DozeMachine.State.INITIALIZED,
+                DozeMachine.State.FINISH);
+
+        verify(mObserver).unregister();
+    }
+
+    @Test
+    public void transitionTo_doze_doesNothing() throws Exception {
+        mSuppressedHandler.transitionTo(DozeMachine.State.INITIALIZED,
+                DozeMachine.State.DOZE);
+
+        verify(mObserver, never()).register();
+        verify(mObserver, never()).unregister();
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
index 47933ba..e58a3a6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
@@ -16,8 +16,11 @@
 
 import static junit.framework.Assert.assertEquals;
 
+import static org.junit.Assert.assertFalse;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
@@ -447,6 +450,21 @@
                 view.findViewById(R.id.vpn_subtitle).getVisibility());
     }
 
+    @Test
+    public void testNoClickWhenGone() {
+        QSTileHost mockHost = mock(QSTileHost.class);
+        mFooter.setHostEnvironment(mockHost);
+        mFooter.refreshState();
+
+        TestableLooper.get(this).processAllMessages();
+
+        assertFalse(mFooter.hasFooter());
+        mFooter.onClick(mFooter.getView());
+
+        // Proxy for dialog being created
+        verify(mockHost, never()).collapsePanels();
+    }
+
     private CharSequence addLink(CharSequence description) {
         final SpannableStringBuilder message = new SpannableStringBuilder();
         message.append(description);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
index 9a7e97b..b652980 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
@@ -139,9 +139,10 @@
                         .setRank(4747));
 
         // THEN the listener is notified
-        verify(mCollectionListener).onEntryAdded(mEntryCaptor.capture());
-
+        verify(mCollectionListener).onEntryInit(mEntryCaptor.capture());
         NotificationEntry entry = mEntryCaptor.getValue();
+
+        verify(mCollectionListener).onEntryAdded(entry);
         assertEquals(notif1.key, entry.getKey());
         assertEquals(notif1.sbn, entry.getSbn());
         assertEquals(notif1.ranking, entry.getRanking());
@@ -236,6 +237,7 @@
 
         // THEN the listener is notified
         verify(mCollectionListener).onEntryRemoved(entry, REASON_APP_CANCEL, false);
+        verify(mCollectionListener).onEntryCleanUp(entry);
         assertEquals(notif.sbn, entry.getSbn());
         assertEquals(notif.ranking, entry.getRanking());
     }
@@ -606,6 +608,10 @@
         private final Map<String, NotificationEntry> mLastSeenEntries = new ArrayMap<>();
 
         @Override
+        public void onEntryInit(NotificationEntry entry) {
+        }
+
+        @Override
         public void onEntryAdded(NotificationEntry entry) {
             mLastSeenEntries.put(entry.getKey(), entry);
         }
@@ -618,6 +624,10 @@
         public void onEntryRemoved(NotificationEntry entry, int reason, boolean removedByUser) {
         }
 
+        @Override
+        public void onEntryCleanUp(NotificationEntry entry) {
+        }
+
         public NotificationEntry getEntry(String key) {
             if (!mLastSeenEntries.containsKey(key)) {
                 throw new RuntimeException("Key not found: " + key);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index dd896be..b9d2d22 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -54,7 +54,6 @@
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -113,10 +112,9 @@
                 notificationShadeWindowView, mock(NotificationListContainerViewGroup.class),
                 mock(DozeScrimController.class), mock(ScrimController.class),
                 mock(ActivityLaunchAnimator.class), mock(DynamicPrivacyController.class),
-                mock(NotificationAlertingManager.class),
-                mock(NotificationRowBinderImpl.class), mock(KeyguardStateController.class),
-                mock(KeyguardIndicationController.class),
-                mStatusBar, mock(ShadeControllerImpl.class), mCommandQueue, mInitController);
+                mock(NotificationAlertingManager.class), mock(KeyguardStateController.class),
+                mock(KeyguardIndicationController.class), mStatusBar,
+                mock(ShadeControllerImpl.class), mCommandQueue, mInitController);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index e90e398..db17a6e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -95,7 +95,6 @@
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.KeyguardIndicationController;
 import com.android.systemui.statusbar.NavigationBarController;
 import com.android.systemui.statusbar.NotificationListener;
@@ -120,8 +119,7 @@
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
-import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer;
+import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
@@ -133,10 +131,8 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
-import com.android.systemui.statusbar.policy.RemoteInputUriController;
 import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
-import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 import com.android.systemui.volume.VolumeComponent;
@@ -149,7 +145,6 @@
 
 import java.io.ByteArrayOutputStream;
 import java.io.PrintWriter;
-import java.util.ArrayList;
 import java.util.Optional;
 
 import javax.inject.Provider;
@@ -166,7 +161,7 @@
     private PowerManager mPowerManager;
     private TestableNotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
 
-    @Mock private FeatureFlags mFeatureFlags;
+    @Mock private NotificationsController mNotificationsController;
     @Mock private LightBarController mLightBarController;
     @Mock private StatusBarIconController mStatusBarIconController;
     @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@@ -180,7 +175,6 @@
     @Mock private IDreamManager mDreamManager;
     @Mock private ScrimController mScrimController;
     @Mock private DozeScrimController mDozeScrimController;
-    @Mock private ArrayList<NotificationEntry> mNotificationList;
     @Mock private Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
     @Mock private BiometricUnlockController mBiometricUnlockController;
     @Mock private NotificationInterruptionStateProvider.HeadsUpSuppressor mHeadsUpSuppressor;
@@ -190,7 +184,6 @@
     @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
     @Mock private NotificationRemoteInputManager mRemoteInputManager;
     @Mock private RemoteInputController mRemoteInputController;
-    @Mock private RemoteInputUriController mRemoteInputUriController;
     @Mock private StatusBarStateControllerImpl mStatusBarStateController;
     @Mock private BatteryController mBatteryController;
     @Mock private DeviceProvisionedController mDeviceProvisionedController;
@@ -214,8 +207,6 @@
     @Mock private NotificationWakeUpCoordinator mNotificationWakeUpCoordinator;
     @Mock private KeyguardBypassController mKeyguardBypassController;
     @Mock private DynamicPrivacyController mDynamicPrivacyController;
-    @Mock private NotifPipelineInitializer mNewNotifPipeline;
-    @Mock private ZenModeController mZenModeController;
     @Mock private AutoHideController mAutoHideController;
     @Mock private NotificationViewHierarchyManager mNotificationViewHierarchyManager;
     @Mock private UserSwitcherController mUserSwitcherController;
@@ -223,7 +214,6 @@
     @Mock private VibratorHelper mVibratorHelper;
     @Mock private BubbleController mBubbleController;
     @Mock private NotificationGroupManager mGroupManager;
-    @Mock private NotificationGroupAlertTransferHelper mGroupAlertTransferHelper;
     @Mock private NotificationShadeWindowController mNotificationShadeWindowController;
     @Mock private NotificationIconAreaController mNotificationIconAreaController;
     @Mock private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
@@ -247,7 +237,6 @@
     @Mock private ViewMediatorCallback mViewMediatorCallback;
     @Mock private DismissCallbackRegistry mDismissCallbackRegistry;
     @Mock private ScreenPinningRequest mScreenPinningRequest;
-    @Mock private NotificationEntryManager mEntryManager;
     @Mock private LockscreenLockIconController mLockscreenLockIconController;
     @Mock private StatusBarNotificationActivityStarter.Builder
             mStatusBarNotificationActivityStarterBuilder;
@@ -256,7 +245,6 @@
     @Mock private KeyguardDismissUtil mKeyguardDismissUtil;
     @Mock private ExtensionController mExtensionController;
     @Mock private UserInfoControllerImpl mUserInfoControllerImpl;
-    @Mock private NotificationRowBinderImpl mNotificationRowBinder;
     private ShadeController mShadeController;
     private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
     private InitController mInitController = new InitController();
@@ -280,7 +268,7 @@
 
         mMetricsLogger = new FakeMetricsLogger();
         NotificationLogger notificationLogger = new NotificationLogger(mNotificationListener,
-                mUiBgExecutor, mEntryManager, mStatusBarStateController,
+                mUiBgExecutor, mock(NotificationEntryManager.class), mStatusBarStateController,
                 mExpansionStateLogger);
         notificationLogger.setVisibilityReporter(mock(Runnable.class));
 
@@ -334,7 +322,7 @@
 
         mStatusBar = new StatusBar(
                 mContext,
-                mFeatureFlags,
+                mNotificationsController,
                 mLightBarController,
                 mAutoHideController,
                 mKeyguardUpdateMonitor,
@@ -346,7 +334,6 @@
                 mHeadsUpManager,
                 mDynamicPrivacyController,
                 mBypassHeadsUpNotifier,
-                () -> mNewNotifPipeline,
                 new FalsingManagerFake(),
                 mBroadcastDispatcher,
                 new RemoteInputQuickSettingsDisabler(
@@ -356,7 +343,6 @@
                 ),
                 mNotificationGutsManager,
                 notificationLogger,
-                mEntryManager,
                 mNotificationInterruptionStateProvider,
                 mNotificationViewHierarchyManager,
                 mKeyguardViewMediator,
@@ -377,12 +363,10 @@
                 mVibratorHelper,
                 mBubbleController,
                 mGroupManager,
-                mGroupAlertTransferHelper,
                 mVisualStabilityManager,
                 mDeviceProvisionedController,
                 mNavigationBarController,
                 () -> mAssistManager,
-                mNotificationListener,
                 configurationController,
                 mNotificationShadeWindowController,
                 mLockscreenLockIconController,
@@ -399,7 +383,6 @@
                 Optional.of(mRecents),
                 mStatusBarComponentBuilderProvider,
                 mPluginManager,
-                mRemoteInputUriController,
                 Optional.of(mDivider),
                 mLightsOutNotifController,
                 mStatusBarNotificationActivityStarterBuilder,
@@ -414,7 +397,6 @@
                 mKeyguardDismissUtil,
                 mExtensionController,
                 mUserInfoControllerImpl,
-                mNotificationRowBinder,
                 mDismissCallbackRegistry);
 
         when(mNotificationShadeWindowView.findViewById(R.id.lock_icon_container)).thenReturn(
@@ -675,7 +657,7 @@
     public void testPanelOpenForHeadsUp() {
         when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
         when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true);
-        when(mEntryManager.getActiveNotificationsCount()).thenReturn(5);
+        when(mNotificationsController.getActiveNotificationsCount()).thenReturn(5);
         when(mNotificationPresenter.isPresenterFullyCollapsed()).thenReturn(true);
         mStatusBar.setBarStateForTest(StatusBarState.SHADE);
 
@@ -693,7 +675,7 @@
     @Test
     public void testPanelOpenAndClear() {
         when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false);
-        when(mEntryManager.getActiveNotificationsCount()).thenReturn(5);
+        when(mNotificationsController.getActiveNotificationsCount()).thenReturn(5);
 
         when(mNotificationPresenter.isPresenterFullyCollapsed()).thenReturn(false);
         mStatusBar.setBarStateForTest(StatusBarState.SHADE);
@@ -712,7 +694,7 @@
     @Test
     public void testPanelOpenAndNoClear() {
         when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false);
-        when(mEntryManager.getActiveNotificationsCount()).thenReturn(5);
+        when(mNotificationsController.getActiveNotificationsCount()).thenReturn(5);
         when(mNotificationPresenter.isPresenterFullyCollapsed()).thenReturn(false);
         mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD);
 
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java
index 7dc5c5f..020b32a 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java
@@ -77,8 +77,8 @@
         mLog.mark("onCreate");
         mDeps = getTetheringDependencies();
         mContext = mDeps.getContext();
-        mTethering = makeTethering(mDeps);
         mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+        mTethering = makeTethering(mDeps);
     }
 
     /**
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
index 1fe162c..5d170d3 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
@@ -18,9 +18,17 @@
 
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SINGLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_RIGHT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_UP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_TRIPLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SINGLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_RIGHT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_UP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_TRIPLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP_AND_HOLD;
@@ -110,6 +118,7 @@
         mGestures.add(new Swipe(context, UP, DOWN, GESTURE_SWIPE_UP_AND_DOWN, this));
         mGestures.add(new Swipe(context, UP, LEFT, GESTURE_SWIPE_UP_AND_LEFT, this));
         mGestures.add(new Swipe(context, UP, RIGHT, GESTURE_SWIPE_UP_AND_RIGHT, this));
+        // Set up multi-finger gestures to be enabled later.
         // Two-finger taps.
         mMultiFingerGestures.add(
                 new MultiFingerMultiTap(mContext, 2, 1, GESTURE_2_FINGER_SINGLE_TAP, this));
@@ -124,6 +133,24 @@
                 new MultiFingerMultiTap(mContext, 3, 2, GESTURE_3_FINGER_DOUBLE_TAP, this));
         mMultiFingerGestures.add(
                 new MultiFingerMultiTap(mContext, 3, 3, GESTURE_3_FINGER_TRIPLE_TAP, this));
+        // Two-finger swipes.
+        mMultiFingerGestures.add(
+                new MultiFingerSwipe(context, 2, DOWN, GESTURE_2_FINGER_SWIPE_DOWN, this));
+        mMultiFingerGestures.add(
+                new MultiFingerSwipe(context, 2, LEFT, GESTURE_2_FINGER_SWIPE_LEFT, this));
+        mMultiFingerGestures.add(
+                new MultiFingerSwipe(context, 2, RIGHT, GESTURE_2_FINGER_SWIPE_RIGHT, this));
+        mMultiFingerGestures.add(
+                new MultiFingerSwipe(context, 2, UP, GESTURE_2_FINGER_SWIPE_UP, this));
+        // Three-finger swipes.
+        mMultiFingerGestures.add(
+                new MultiFingerSwipe(context, 3, DOWN, GESTURE_3_FINGER_SWIPE_DOWN, this));
+        mMultiFingerGestures.add(
+                new MultiFingerSwipe(context, 3, LEFT, GESTURE_3_FINGER_SWIPE_LEFT, this));
+        mMultiFingerGestures.add(
+                new MultiFingerSwipe(context, 3, RIGHT, GESTURE_3_FINGER_SWIPE_RIGHT, this));
+        mMultiFingerGestures.add(
+                new MultiFingerSwipe(context, 3, UP, GESTURE_3_FINGER_SWIPE_UP, this));
     }
 
     /**
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/GestureUtils.java b/services/accessibility/java/com/android/server/accessibility/gestures/GestureUtils.java
index 0f5dd08..ac67480 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/GestureUtils.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureUtils.java
@@ -8,6 +8,9 @@
  */
 public final class GestureUtils {
 
+    public static int MM_PER_CM = 10;
+    public static float CM_PER_INCH = 2.54f;
+
     private GestureUtils() {
         /* cannot be instantiated */
     }
@@ -85,4 +88,12 @@
 
         return true;
     }
+
+    /**
+     * Gets the index of the pointer that went up or down from a motion event.
+     */
+    public static int getActionIndex(MotionEvent event) {
+        return (event.getAction()
+                & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+    }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerSwipe.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerSwipe.java
new file mode 100644
index 0000000..8249239
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerSwipe.java
@@ -0,0 +1,496 @@
+/*
+ * 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.accessibility.gestures;
+
+import static android.view.MotionEvent.INVALID_POINTER_ID;
+
+import static com.android.server.accessibility.gestures.GestureUtils.MM_PER_CM;
+import static com.android.server.accessibility.gestures.GestureUtils.getActionIndex;
+import static com.android.server.accessibility.gestures.Swipe.CANCEL_ON_PAUSE_THRESHOLD_NOT_STARTED_MS;
+import static com.android.server.accessibility.gestures.Swipe.CANCEL_ON_PAUSE_THRESHOLD_STARTED_MS;
+import static com.android.server.accessibility.gestures.Swipe.GESTURE_CONFIRM_CM;
+import static com.android.server.accessibility.gestures.TouchExplorer.DEBUG;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.os.Handler;
+import android.util.DisplayMetrics;
+import android.util.Slog;
+import android.util.TypedValue;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * This class is responsible for matching one-finger swipe gestures. Each instance matches one swipe
+ * gesture. A swipe is specified as a series of one or more directions e.g. left, left and up, etc.
+ * At this time swipes with more than two directions are not supported.
+ */
+class MultiFingerSwipe extends GestureMatcher {
+
+    // Direction constants.
+    public static final int LEFT = 0;
+    public static final int RIGHT = 1;
+    public static final int UP = 2;
+    public static final int DOWN = 3;
+    // This is the calculated movement threshold used track if the user is still
+    // moving their finger.
+    private final float mGestureDetectionThresholdPixels;
+
+    // Buffer for storing points for gesture detection.
+    private final ArrayList<PointF>[] mStrokeBuffers;
+
+    // The swipe direction for this matcher.
+    private int mDirection;
+    private int[] mPointerIds;
+    // The starting point of each finger's path in the gesture.
+    private PointF[] mBase;
+    // The most recent entry in each finger's gesture path.
+    private PointF[] mPreviousGesturePoint;
+    private int mTargetFingerCount;
+    private int mCurrentFingerCount;
+    // Whether the appropriate number of fingers have gone down at some point. This is reset only on
+    // clear.
+    private boolean mTargetFingerCountReached = false;
+    // Constants for sampling motion event points.
+    // We sample based on a minimum distance between points, primarily to improve accuracy by
+    // reducing noisy minor changes in direction.
+    private static final float MIN_CM_BETWEEN_SAMPLES = 0.25f;
+    private final float mMinPixelsBetweenSamplesX;
+    private final float mMinPixelsBetweenSamplesY;
+    // The minmimum distance the finger must travel before we evaluate the initial direction of the
+    // swipe.
+    // Anything less is still considered a touch.
+    private int mTouchSlop;
+
+    MultiFingerSwipe(
+            Context context,
+            int fingerCount,
+            int direction,
+            int gesture,
+            GestureMatcher.StateChangeListener listener) {
+        super(gesture, new Handler(context.getMainLooper()), listener);
+        mTargetFingerCount = fingerCount;
+        mPointerIds = new int[mTargetFingerCount];
+        mBase = new PointF[mTargetFingerCount];
+        mPreviousGesturePoint = new PointF[mTargetFingerCount];
+        mStrokeBuffers = new ArrayList[mTargetFingerCount];
+        mDirection = direction;
+        DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
+        mGestureDetectionThresholdPixels =
+                TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, MM_PER_CM, displayMetrics)
+                        * GESTURE_CONFIRM_CM;
+        // Calculate minimum gesture velocity
+        final float pixelsPerCmX = displayMetrics.xdpi / GestureUtils.CM_PER_INCH;
+        final float pixelsPerCmY = displayMetrics.ydpi / GestureUtils.CM_PER_INCH;
+        mMinPixelsBetweenSamplesX = MIN_CM_BETWEEN_SAMPLES * pixelsPerCmX;
+        mMinPixelsBetweenSamplesY = MIN_CM_BETWEEN_SAMPLES * pixelsPerCmY;
+        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+        clear();
+    }
+
+    @Override
+    protected void clear() {
+        mTargetFingerCountReached = false;
+        mCurrentFingerCount = 0;
+        for (int i = 0; i < mTargetFingerCount; ++i) {
+            mPointerIds[i] = INVALID_POINTER_ID;
+            if (mBase[i] == null) {
+                mBase[i] = new PointF();
+            }
+            mBase[i].x = Float.NaN;
+            mBase[i].y = Float.NaN;
+            if (mPreviousGesturePoint[i] == null) {
+                mPreviousGesturePoint[i] = new PointF();
+            }
+            mPreviousGesturePoint[i].x = Float.NaN;
+            mPreviousGesturePoint[i].y = Float.NaN;
+            if (mStrokeBuffers[i] == null) {
+                mStrokeBuffers[i] = new ArrayList<>(100);
+            }
+            mStrokeBuffers[i].clear();
+        }
+        super.clear();
+    }
+
+    @Override
+    protected void onDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        if (mCurrentFingerCount > 0) {
+            cancelGesture(event, rawEvent, policyFlags);
+            return;
+        }
+        mCurrentFingerCount = 1;
+        final int actionIndex = getActionIndex(rawEvent);
+        final int pointerId = rawEvent.getPointerId(actionIndex);
+        int pointerIndex = rawEvent.getPointerCount() - 1;
+        if (pointerId < 0 || pointerId > rawEvent.getPointerCount() - 1) {
+            // Nonsensical pointer id.
+            cancelGesture(event, rawEvent, policyFlags);
+            return;
+        }
+        if (mPointerIds[pointerIndex] != INVALID_POINTER_ID) {
+            // Inconsistent event stream.
+            cancelGesture(event, rawEvent, policyFlags);
+            return;
+        }
+        mPointerIds[pointerIndex] = pointerId;
+        cancelAfterPauseThreshold(event, rawEvent, policyFlags);
+        if (Float.isNaN(mBase[pointerIndex].x) && Float.isNaN(mBase[pointerIndex].y)) {
+            final float x = rawEvent.getX(actionIndex);
+            final float y = rawEvent.getY(actionIndex);
+            if (x < 0f || y < 0f) {
+                cancelGesture(event, rawEvent, policyFlags);
+                return;
+            }
+            mBase[pointerIndex].x = x;
+            mBase[pointerIndex].y = y;
+            mPreviousGesturePoint[pointerIndex].x = x;
+            mPreviousGesturePoint[pointerIndex].y = y;
+        } else {
+            // This  event doesn't make sense in the middle of a gesture.
+            cancelGesture(event, rawEvent, policyFlags);
+            return;
+        }
+    }
+
+    @Override
+    protected void onPointerDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        if (event.getPointerCount() > mTargetFingerCount) {
+            cancelGesture(event, rawEvent, policyFlags);
+            return;
+        }
+        mCurrentFingerCount += 1;
+        if (mCurrentFingerCount != rawEvent.getPointerCount()) {
+            cancelGesture(event, rawEvent, policyFlags);
+            return;
+        }
+        if (mCurrentFingerCount == mTargetFingerCount) {
+            mTargetFingerCountReached = true;
+        }
+        final int actionIndex = getActionIndex(rawEvent);
+        final int pointerId = rawEvent.getPointerId(actionIndex);
+        if (pointerId < 0 || pointerId > rawEvent.getPointerCount() - 1) {
+            // Nonsensical pointer id.
+            cancelGesture(event, rawEvent, policyFlags);
+            return;
+        }
+        int pointerIndex = mCurrentFingerCount - 1;
+        if (mPointerIds[pointerIndex] != INVALID_POINTER_ID) {
+            // Inconsistent event stream.
+            cancelGesture(event, rawEvent, policyFlags);
+            return;
+        }
+        mPointerIds[pointerIndex] = pointerId;
+        cancelAfterPauseThreshold(event, rawEvent, policyFlags);
+        if (Float.isNaN(mBase[pointerIndex].x) && Float.isNaN(mBase[pointerIndex].y)) {
+            final float x = rawEvent.getX(actionIndex);
+            final float y = rawEvent.getY(actionIndex);
+            if (x < 0f || y < 0f) {
+                cancelGesture(event, rawEvent, policyFlags);
+                return;
+            }
+            mBase[pointerIndex].x = x;
+            mBase[pointerIndex].y = y;
+            mPreviousGesturePoint[pointerIndex].x = x;
+            mPreviousGesturePoint[pointerIndex].y = y;
+        } else {
+            cancelGesture(event, rawEvent, policyFlags);
+            return;
+        }
+    }
+
+    @Override
+    protected void onPointerUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        if (!mTargetFingerCountReached) {
+            cancelGesture(event, rawEvent, policyFlags);
+            return;
+        }
+        mCurrentFingerCount -= 1;
+        final int actionIndex = getActionIndex(event);
+        final int pointerId = event.getPointerId(actionIndex);
+        if (pointerId < 0 || pointerId > rawEvent.getPointerCount() - 1) {
+            // Nonsensical pointer id.
+            cancelGesture(event, rawEvent, policyFlags);
+            return;
+        }
+        final int pointerIndex = Arrays.binarySearch(mPointerIds, pointerId);
+        if (pointerIndex < 0) {
+            cancelGesture(event, rawEvent, policyFlags);
+            return;
+        }
+        final float x = rawEvent.getX(actionIndex);
+        final float y = rawEvent.getY(actionIndex);
+        if (x < 0f || y < 0f) {
+            cancelGesture(event, rawEvent, policyFlags);
+            return;
+        }
+        final float dX = Math.abs(x - mPreviousGesturePoint[pointerIndex].x);
+        final float dY = Math.abs(y - mPreviousGesturePoint[pointerIndex].y);
+        if (dX >= mMinPixelsBetweenSamplesX || dY >= mMinPixelsBetweenSamplesY) {
+            mStrokeBuffers[pointerIndex].add(new PointF(x, y));
+        }
+        // We will evaluate all the paths on ACTION_UP.
+    }
+
+    @Override
+    protected void onMove(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        for (int pointerIndex = 0; pointerIndex < rawEvent.getPointerCount(); ++pointerIndex) {
+            if (DEBUG) {
+                Slog.d(getGestureName(), "Processing move on finger " + pointerIndex);
+            }
+            int index = rawEvent.findPointerIndex(mPointerIds[pointerIndex]);
+            final float x = rawEvent.getX(index);
+            final float y = rawEvent.getY(index);
+            if (x < 0f || y < 0f) {
+                cancelGesture(event, rawEvent, policyFlags);
+                return;
+            }
+            final float dX = Math.abs(x - mPreviousGesturePoint[pointerIndex].x);
+            final float dY = Math.abs(y - mPreviousGesturePoint[pointerIndex].y);
+            final double moveDelta =
+                    Math.hypot(
+                            Math.abs(x - mBase[pointerIndex].x),
+                            Math.abs(y - mBase[pointerIndex].y));
+            if (DEBUG) {
+                Slog.d(
+                        getGestureName(),
+                        "moveDelta:"
+                                + Double.toString(moveDelta)
+                                + " mGestureDetectionThreshold: "
+                                + Float.toString(mGestureDetectionThresholdPixels));
+            }
+            if (getState() == STATE_CLEAR) {
+                if (moveDelta < mTouchSlop) {
+                    // This still counts as a touch not a swipe.
+                    continue;
+                } else if (mStrokeBuffers[pointerIndex].size() == 0) {
+                    // First, make sure we have the right number of fingers down.
+                    if (mCurrentFingerCount != mTargetFingerCount) {
+                        cancelGesture(event, rawEvent, policyFlags);
+                        return;
+                    }
+                    // Then, make sure the pointer is going in the right direction.
+                    int direction =
+                            toDirection(x - mBase[pointerIndex].x, y - mBase[pointerIndex].y);
+                    if (direction != mDirection) {
+                        cancelGesture(event, rawEvent, policyFlags);
+                        return;
+                    } else {
+                        // This is confirmed to be some kind of swipe so start tracking points.
+                        cancelAfterPauseThreshold(event, rawEvent, policyFlags);
+                        for (int i = 0; i < mTargetFingerCount; ++i) {
+                            mStrokeBuffers[i].add(new PointF(mBase[i]));
+                        }
+                    }
+                }
+                if (moveDelta > mGestureDetectionThresholdPixels) {
+                    // Try to cancel if the finger starts to go the wrong way.
+                    // Note that this only works because this matcher assumes one direction.
+                    int direction =
+                            toDirection(x - mBase[pointerIndex].x, y - mBase[pointerIndex].y);
+                    if (direction != mDirection) {
+                        cancelGesture(event, rawEvent, policyFlags);
+                        return;
+                    }
+                    // If the pointer has moved more than the threshold,
+                    // update the stored values.
+                    mBase[pointerIndex].x = x;
+                    mBase[pointerIndex].y = y;
+                    mPreviousGesturePoint[pointerIndex].x = x;
+                    mPreviousGesturePoint[pointerIndex].y = y;
+                    if (getState() == STATE_CLEAR) {
+                        startGesture(event, rawEvent, policyFlags);
+                        cancelAfterPauseThreshold(event, rawEvent, policyFlags);
+                    }
+                }
+            }
+            if (getState() == STATE_GESTURE_STARTED) {
+                if (dX >= mMinPixelsBetweenSamplesX || dY >= mMinPixelsBetweenSamplesY) {
+                    // Sample every 2.5 MM in order to guard against minor variations in path.
+                    mPreviousGesturePoint[pointerIndex].x = x;
+                    mPreviousGesturePoint[pointerIndex].y = y;
+                    mStrokeBuffers[pointerIndex].add(new PointF(x, y));
+                    cancelAfterPauseThreshold(event, rawEvent, policyFlags);
+                }
+            }
+        }
+    }
+
+    @Override
+    protected void onUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        if (getState() != STATE_GESTURE_STARTED) {
+            cancelGesture(event, rawEvent, policyFlags);
+            return;
+        }
+        mCurrentFingerCount = 0;
+        final int actionIndex = getActionIndex(event);
+        final int pointerId = event.getPointerId(actionIndex);
+        final int pointerIndex = Arrays.binarySearch(mPointerIds, pointerId);
+        if (pointerIndex < 0) {
+            cancelGesture(event, rawEvent, policyFlags);
+            return;
+        }
+        final float x = rawEvent.getX(actionIndex);
+        final float y = rawEvent.getY(actionIndex);
+        if (x < 0f || y < 0f) {
+            cancelGesture(event, rawEvent, policyFlags);
+            return;
+        }
+        final float dX = Math.abs(x - mPreviousGesturePoint[pointerIndex].x);
+        final float dY = Math.abs(y - mPreviousGesturePoint[pointerIndex].y);
+        if (dX >= mMinPixelsBetweenSamplesX || dY >= mMinPixelsBetweenSamplesY) {
+            mStrokeBuffers[pointerIndex].add(new PointF(x, y));
+        }
+        recognizeGesture(event, rawEvent, policyFlags);
+    }
+
+    /**
+     * queues a transition to STATE_GESTURE_CANCEL based on the current state. If we have
+     * transitioned to STATE_GESTURE_STARTED the delay is longer.
+     */
+    private void cancelAfterPauseThreshold(
+            MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        cancelPendingTransitions();
+        switch (getState()) {
+            case STATE_CLEAR:
+                cancelAfter(CANCEL_ON_PAUSE_THRESHOLD_NOT_STARTED_MS, event, rawEvent, policyFlags);
+                break;
+            case STATE_GESTURE_STARTED:
+                cancelAfter(CANCEL_ON_PAUSE_THRESHOLD_STARTED_MS, event, rawEvent, policyFlags);
+                break;
+            default:
+                break;
+        }
+    }
+    /**
+     * Looks at the sequence of motions in mStrokeBuffer, classifies the gesture, then transitions
+     * to the complete or cancel state depending on the result.
+     */
+    private void recognizeGesture(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        // Check the path of each finger against the specified direction.
+        // Note that we sample every 2.5 MMm, and the direction matching is extremely tolerant (each
+        // direction has a 90-degree arch of tolerance) meaning that minor perpendicular movements
+        // should not create false negatives.
+        for (int i = 0; i < mTargetFingerCount; ++i) {
+            if (DEBUG) {
+                Slog.d(getGestureName(), "Recognizing finger: " + i);
+            }
+            if (mStrokeBuffers[i].size() < 2) {
+                Slog.d(getGestureName(), "Too few points.");
+                cancelGesture(event, rawEvent, policyFlags);
+                return;
+            }
+            ArrayList<PointF> path = mStrokeBuffers[i];
+
+            if (DEBUG) {
+                Slog.d(getGestureName(), "path=" + path.toString());
+            }
+            // Classify line segments, and call Listener callbacks.
+            if (!recognizeGesturePath(event, rawEvent, policyFlags, path)) {
+                cancelGesture(event, rawEvent, policyFlags);
+                return;
+            }
+        }
+        // If we reach this point then all paths match.
+        completeGesture(event, rawEvent, policyFlags);
+    }
+
+    /**
+     * Tests the path of a given finger against the direction specified in this matcher.
+     *
+     * @return True if the path matches the specified direction for this matcher, otherwise false.
+     */
+    private boolean recognizeGesturePath(
+            MotionEvent event, MotionEvent rawEvent, int policyFlags, ArrayList<PointF> path) {
+
+        final int displayId = event.getDisplayId();
+        for (int i = 0; i < path.size() - 1; ++i) {
+            PointF start = path.get(i);
+            PointF end = path.get(i + 1);
+
+            float dX = end.x - start.x;
+            float dY = end.y - start.y;
+            int direction = toDirection(dX, dY);
+            if (direction != mDirection) {
+                if (DEBUG) {
+                    Slog.d(
+                            getGestureName(),
+                            "Found direction "
+                                    + directionToString(direction)
+                                    + " when expecting "
+                                    + directionToString(mDirection));
+                }
+                return false;
+            }
+        }
+        if (DEBUG) {
+            Slog.d(getGestureName(), "Completed.");
+        }
+        return true;
+    }
+
+    private static int toDirection(float dX, float dY) {
+        if (Math.abs(dX) > Math.abs(dY)) {
+            // Horizontal
+            return (dX < 0) ? LEFT : RIGHT;
+        } else {
+            // Vertical
+            return (dY < 0) ? UP : DOWN;
+        }
+    }
+
+    public static String directionToString(int direction) {
+        switch (direction) {
+            case LEFT:
+                return "left";
+            case RIGHT:
+                return "right";
+            case UP:
+                return "up";
+            case DOWN:
+                return "down";
+            default:
+                return "Unknown Direction";
+        }
+    }
+
+    @Override
+    String getGestureName() {
+        StringBuilder builder = new StringBuilder();
+        builder.append(mTargetFingerCount).append("-finger ");
+        builder.append("Swipe ").append(directionToString(mDirection));
+        return builder.toString();
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder(super.toString());
+        if (getState() != STATE_GESTURE_CANCELED) {
+            builder.append(", mBase: ")
+                    .append(mBase.toString())
+                    .append(", mGestureDetectionThreshold:")
+                    .append(mGestureDetectionThresholdPixels)
+                    .append(", mMinPixelsBetweenSamplesX:")
+                    .append(mMinPixelsBetweenSamplesX)
+                    .append(", mMinPixelsBetweenSamplesY:")
+                    .append(mMinPixelsBetweenSamplesY);
+        }
+        return builder.toString();
+    }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/SecondFingerMultiTap.java b/services/accessibility/java/com/android/server/accessibility/gestures/SecondFingerMultiTap.java
index 8a566fc..ada251f 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/SecondFingerMultiTap.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/SecondFingerMultiTap.java
@@ -18,6 +18,8 @@
 
 import static android.view.MotionEvent.INVALID_POINTER_ID;
 
+import static com.android.server.accessibility.gestures.GestureUtils.getActionIndex;
+
 import android.content.Context;
 import android.os.Handler;
 import android.view.MotionEvent;
@@ -155,11 +157,6 @@
         return moveDelta <= slop;
     }
 
-    private int getActionIndex(MotionEvent event) {
-        return event.getAction()
-                & MotionEvent.ACTION_POINTER_INDEX_MASK << MotionEvent.ACTION_POINTER_INDEX_SHIFT;
-    }
-
     @Override
     public String toString() {
         return super.toString()
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/Swipe.java b/services/accessibility/java/com/android/server/accessibility/gestures/Swipe.java
index a285c10..9108c69 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/Swipe.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/Swipe.java
@@ -16,10 +16,10 @@
 
 package com.android.server.accessibility.gestures;
 
+import static com.android.server.accessibility.gestures.GestureUtils.MM_PER_CM;
 import static com.android.server.accessibility.gestures.TouchExplorer.DEBUG;
 
 import android.content.Context;
-import android.gesture.GesturePoint;
 import android.graphics.PointF;
 import android.os.Handler;
 import android.util.DisplayMetrics;
@@ -44,10 +44,10 @@
     public static final int DOWN = 3;
     // This is the calculated movement threshold used track if the user is still
     // moving their finger.
-    private final float mGestureDetectionThreshold;
+    private final float mGestureDetectionThresholdPixels;
 
     // Buffer for storing points for gesture detection.
-    private final ArrayList<GesturePoint> mStrokeBuffer = new ArrayList<GesturePoint>(100);
+    private final ArrayList<PointF> mStrokeBuffer = new ArrayList<>(100);
 
     // The minimal delta between moves to add a gesture point.
     private static final int TOUCH_TOLERANCE_PIX = 3;
@@ -56,7 +56,7 @@
     private static final float MIN_PREDICTION_SCORE = 2.0f;
 
     // Distance a finger must travel before we decide if it is a gesture or not.
-    private static final int GESTURE_CONFIRM_CM = 1;
+    public static final int GESTURE_CONFIRM_CM = 1;
 
     // Time threshold used to determine if an interaction is a gesture or not.
     // If the first movement of 1cm takes longer than this value, we assume it's
@@ -67,17 +67,16 @@
     // all gestures started with the initial movement taking less than 100ms.
     // When touch exploring, the first movement almost always takes longer than
     // 200ms.
-    private static final long CANCEL_ON_PAUSE_THRESHOLD_NOT_STARTED_MS = 150;
+    public static final long CANCEL_ON_PAUSE_THRESHOLD_NOT_STARTED_MS = 150;
 
     // Time threshold used to determine if a gesture should be cancelled.  If
     // the finger takes more than this time to move 1cm, the ongoing gesture is
     // cancelled.
-    private static final long CANCEL_ON_PAUSE_THRESHOLD_STARTED_MS = 300;
+    public static final long CANCEL_ON_PAUSE_THRESHOLD_STARTED_MS = 300;
 
     private int[] mDirections;
     private float mBaseX;
     private float mBaseY;
-    private long mBaseTime;
     private float mPreviousGestureX;
     private float mPreviousGestureY;
     // Constants for sampling motion event points.
@@ -119,8 +118,8 @@
         super(gesture, new Handler(context.getMainLooper()), listener);
         mDirections = directions;
         DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
-        mGestureDetectionThreshold =
-                TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 10, displayMetrics)
+        mGestureDetectionThresholdPixels =
+                TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, MM_PER_CM, displayMetrics)
                         * GESTURE_CONFIRM_CM;
         // Calculate minimum gesture velocity
         final float pixelsPerCmX = displayMetrics.xdpi / 2.54f;
@@ -135,18 +134,16 @@
     protected void clear() {
         mBaseX = Float.NaN;
         mBaseY = Float.NaN;
-        mBaseTime = 0;
         mStrokeBuffer.clear();
         super.clear();
     }
 
     @Override
     protected void onDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
-        cancelAfterDelay(event, rawEvent, policyFlags);
+        cancelAfterPauseThreshold(event, rawEvent, policyFlags);
         if (Float.isNaN(mBaseX) && Float.isNaN(mBaseY)) {
             mBaseX = rawEvent.getX();
             mBaseY = rawEvent.getY();
-            mBaseTime = event.getEventTime();
             mPreviousGestureX = mBaseX;
             mPreviousGestureY = mBaseY;
         }
@@ -157,10 +154,8 @@
     protected void onMove(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
         final float x = rawEvent.getX();
         final float y = rawEvent.getY();
-        final long time = event.getEventTime();
         final float dX = Math.abs(x - mPreviousGestureX);
         final float dY = Math.abs(y - mPreviousGestureY);
-        final long timeDelta = time - mBaseTime;
         final double moveDelta = Math.hypot(Math.abs(x - mBaseX), Math.abs(y - mBaseY));
         if (DEBUG) {
             Slog.d(
@@ -168,7 +163,7 @@
                     "moveDelta:"
                             + Double.toString(moveDelta)
                             + " mGestureDetectionThreshold: "
-                            + Float.toString(mGestureDetectionThreshold));
+                            + Float.toString(mGestureDetectionThresholdPixels));
         }
         if (getState() == STATE_CLEAR) {
             if (moveDelta < mTouchSlop) {
@@ -176,25 +171,24 @@
                 return;
             } else if (mStrokeBuffer.size() == 0) {
                 // First, make sure the pointer is going in the right direction.
-                cancelAfterDelay(event, rawEvent, policyFlags);
+                cancelAfterPauseThreshold(event, rawEvent, policyFlags);
                 int direction = toDirection(x - mBaseX, y - mBaseY);
                 if (direction != mDirections[0]) {
                     cancelGesture(event, rawEvent, policyFlags);
                     return;
                 } else {
                     // This is confirmed to be some kind of swipe so start tracking points.
-                    mStrokeBuffer.add(new GesturePoint(mBaseX, mBaseY, mBaseTime));
+                    mStrokeBuffer.add(new PointF(mBaseX, mBaseY));
                 }
             }
-            if (moveDelta > mGestureDetectionThreshold) {
+            if (moveDelta > mGestureDetectionThresholdPixels) {
                 // If the pointer has moved more than the threshold,
                 // update the stored values.
                 mBaseX = x;
                 mBaseY = y;
-                mBaseTime = time;
                 if (getState() == STATE_CLEAR) {
                     startGesture(event, rawEvent, policyFlags);
-                    cancelAfterDelay(event, rawEvent, policyFlags);
+                    cancelAfterPauseThreshold(event, rawEvent, policyFlags);
                 }
             }
         }
@@ -202,8 +196,8 @@
             if (dX >= mMinPixelsBetweenSamplesX || dY >= mMinPixelsBetweenSamplesY) {
                 mPreviousGestureX = x;
                 mPreviousGestureY = y;
-                mStrokeBuffer.add(new GesturePoint(x, y, time));
-                cancelAfterDelay(event, rawEvent, policyFlags);
+                mStrokeBuffer.add(new PointF(x, y));
+                cancelAfterPauseThreshold(event, rawEvent, policyFlags);
             }
         }
     }
@@ -217,11 +211,10 @@
 
         final float x = rawEvent.getX();
         final float y = rawEvent.getY();
-        final long time = event.getEventTime();
         final float dX = Math.abs(x - mPreviousGestureX);
         final float dY = Math.abs(y - mPreviousGestureY);
         if (dX >= mMinPixelsBetweenSamplesX || dY >= mMinPixelsBetweenSamplesY) {
-            mStrokeBuffer.add(new GesturePoint(x, y, time));
+            mStrokeBuffer.add(new PointF(x, y));
         }
         recognizeGesture(event, rawEvent, policyFlags);
     }
@@ -240,7 +233,8 @@
      * queues a transition to STATE_GESTURE_CANCEL based on the current state. If we have
      * transitioned to STATE_GESTURE_STARTED the delay is longer.
      */
-    private void cancelAfterDelay(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+    private void cancelAfterPauseThreshold(
+            MotionEvent event, MotionEvent rawEvent, int policyFlags) {
         cancelPendingTransitions();
         switch (getState()) {
             case STATE_CLEAR:
@@ -275,7 +269,7 @@
         // 90 degrees.
 
         ArrayList<PointF> path = new ArrayList<>();
-        PointF lastDelimiter = new PointF(mStrokeBuffer.get(0).x, mStrokeBuffer.get(0).y);
+        PointF lastDelimiter = mStrokeBuffer.get(0);
         path.add(lastDelimiter);
 
         float dX = 0; // Sum of unit vectors from last delimiter to each following point
@@ -283,9 +277,9 @@
         int count = 0; // Number of points since last delimiter
         float length = 0; // Vector length from delimiter to most recent point
 
-        PointF next = new PointF();
+        PointF next = null;
         for (int i = 1; i < mStrokeBuffer.size(); ++i) {
-            next = new PointF(mStrokeBuffer.get(i).x, mStrokeBuffer.get(i).y);
+            next = mStrokeBuffer.get(i);
             if (count > 0) {
                 // Average of unit vectors from delimiter to following points
                 float currentDX = dX / count;
@@ -428,7 +422,7 @@
                     .append(", mBaseY: ")
                     .append(mBaseY)
                     .append(", mGestureDetectionThreshold:")
-                    .append(mGestureDetectionThreshold)
+                    .append(mGestureDetectionThresholdPixels)
                     .append(", mMinPixelsBetweenSamplesX:")
                     .append(mMinPixelsBetweenSamplesX)
                     .append(", mMinPixelsBetweenSamplesY:")
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 4f18f35..1a4fc32 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -154,6 +154,13 @@
     @GuardedBy("mLock")
     private FillEventHistory mEventHistory;
 
+    /**
+     * The last inline augmented autofill selection. Note that we don't log the selection from the
+     * dropdown UI since the service owns the UI in that case.
+     */
+    @GuardedBy("mLock")
+    private FillEventHistory mAugmentedAutofillEventHistory;
+
     /** Shared instance, doesn't need to be logged */
     private final AutofillCompatState mAutofillCompatState;
 
@@ -707,6 +714,13 @@
         }
     }
 
+    void setLastAugmentedAutofillResponse(int sessionId) {
+        synchronized (mLock) {
+            mAugmentedAutofillEventHistory = new FillEventHistory(sessionId, /* clientState= */
+                    null);
+        }
+    }
+
     /**
      * Resets the last fill selection.
      */
@@ -716,6 +730,12 @@
         }
     }
 
+    void resetLastAugmentedAutofillResponse() {
+        synchronized (mLock) {
+            mAugmentedAutofillEventHistory = null;
+        }
+    }
+
     @GuardedBy("mLock")
     private boolean isValidEventLocked(String method, int sessionId) {
         if (mEventHistory == null) {
@@ -798,6 +818,32 @@
         }
     }
 
+    void logAugmentedAutofillSelected(int sessionId, @Nullable String suggestionId,
+            @Nullable Bundle clientState) {
+        synchronized (mLock) {
+            if (mAugmentedAutofillEventHistory == null
+                    || mAugmentedAutofillEventHistory.getSessionId() != sessionId) {
+                return;
+            }
+            mAugmentedAutofillEventHistory.addEvent(
+                    new Event(Event.TYPE_DATASET_SELECTED, suggestionId, clientState, null, null,
+                            null, null, null, null, null, null));
+        }
+    }
+
+    void logAugmentedAutofillShown(int sessionId, @Nullable Bundle clientState) {
+        synchronized (mLock) {
+            if (mAugmentedAutofillEventHistory == null
+                    || mAugmentedAutofillEventHistory.getSessionId() != sessionId) {
+                return;
+            }
+            mAugmentedAutofillEventHistory.addEvent(
+                    new Event(Event.TYPE_DATASETS_SHOWN, null, clientState, null, null, null,
+                            null, null, null, null, null));
+
+        }
+    }
+
     /**
      * Updates the last fill response when an autofill context is committed.
      */
@@ -881,8 +927,8 @@
      * Gets the fill event history.
      *
      * @param callingUid The calling uid
-     *
-     * @return The history or {@code null} if there is none.
+     * @return The history for the autofill or the augmented autofill events depending on the {@code
+     * callingUid}, or {@code null} if there is none.
      */
     FillEventHistory getFillEventHistory(int callingUid) {
         synchronized (mLock) {
@@ -890,6 +936,10 @@
                     && isCalledByServiceLocked("getFillEventHistory", callingUid)) {
                 return mEventHistory;
             }
+            if (mAugmentedAutofillEventHistory != null && isCalledByAugmentedAutofillServiceLocked(
+                    "getFillEventHistory", callingUid)) {
+                return mAugmentedAutofillEventHistory;
+            }
         }
         return null;
     }
@@ -1163,8 +1213,32 @@
                 Slog.v(TAG, "getRemoteAugmentedAutofillServiceLocked(): " + componentName);
             }
 
-            mRemoteAugmentedAutofillService = new RemoteAugmentedAutofillService(getContext(),
-                    componentName, mUserId, new RemoteAugmentedAutofillServiceCallbacks() {
+            final RemoteAugmentedAutofillServiceCallbacks callbacks =
+                    new RemoteAugmentedAutofillServiceCallbacks() {
+                        @Override
+                        public void resetLastResponse() {
+                            AutofillManagerServiceImpl.this.resetLastAugmentedAutofillResponse();
+                        }
+
+                        @Override
+                        public void setLastResponse(int sessionId) {
+                            AutofillManagerServiceImpl.this.setLastAugmentedAutofillResponse(
+                                    sessionId);
+                        }
+
+                        @Override
+                        public void logAugmentedAutofillShown(int sessionId, Bundle clientState) {
+                            AutofillManagerServiceImpl.this.logAugmentedAutofillShown(sessionId,
+                                    clientState);
+                        }
+
+                        @Override
+                        public void logAugmentedAutofillSelected(int sessionId, String suggestionId,
+                                Bundle clientState) {
+                            AutofillManagerServiceImpl.this.logAugmentedAutofillSelected(sessionId,
+                                    suggestionId, clientState);
+                        }
+
                         @Override
                         public void onServiceDied(@NonNull RemoteAugmentedAutofillService service) {
                             Slog.w(TAG, "remote augmented autofill service died");
@@ -1175,8 +1249,10 @@
                             }
                             mRemoteAugmentedAutofillService = null;
                         }
-                    }, mMaster.isInstantServiceAllowed(), mMaster.verbose,
-                    mMaster.mAugmentedServiceIdleUnbindTimeoutMs,
+                    };
+            mRemoteAugmentedAutofillService = new RemoteAugmentedAutofillService(getContext(),
+                    componentName, mUserId, callbacks, mMaster.isInstantServiceAllowed(),
+                    mMaster.verbose, mMaster.mAugmentedServiceIdleUnbindTimeoutMs,
                     mMaster.mAugmentedServiceRequestTimeoutMs);
         }
 
diff --git a/services/autofill/java/com/android/server/autofill/InlineSuggestionFactory.java b/services/autofill/java/com/android/server/autofill/InlineSuggestionFactory.java
index c7be80c..cb6c8f5 100644
--- a/services/autofill/java/com/android/server/autofill/InlineSuggestionFactory.java
+++ b/services/autofill/java/com/android/server/autofill/InlineSuggestionFactory.java
@@ -28,7 +28,6 @@
 import android.view.SurfaceControl;
 import android.view.View;
 import android.view.autofill.AutofillId;
-import android.view.autofill.IAutoFillManagerClient;
 import android.view.inputmethod.InlineSuggestion;
 import android.view.inputmethod.InlineSuggestionInfo;
 import android.view.inputmethod.InlineSuggestionsResponse;
@@ -49,15 +48,24 @@
     private static final String TAG = "InlineSuggestionFactory";
 
     /**
+     * Callback from the inline suggestion Ui.
+     */
+    public interface InlineSuggestionUiCallback {
+        /**
+         * Callback to autofill a dataset to the client app.
+         */
+        void autofill(@NonNull Dataset dataset);
+    }
+
+    /**
      * Creates an {@link InlineSuggestionsResponse} with the {@code datasets} provided by
      * augmented autofill service.
      */
     public static InlineSuggestionsResponse createAugmentedInlineSuggestionsResponse(
-            int sessionId,
             @NonNull Dataset[] datasets,
             @NonNull AutofillId autofillId,
             @NonNull Context context,
-            @NonNull IAutoFillManagerClient client) {
+            @NonNull InlineSuggestionUiCallback inlineSuggestionUiCallback) {
         if (sDebug) Slog.d(TAG, "createAugmentedInlineSuggestionsResponse called");
 
         final ArrayList<InlineSuggestion> inlineSuggestions = new ArrayList<>();
@@ -74,8 +82,8 @@
                 Slog.w(TAG, "InlinePresentation not found in dataset");
                 return null;
             }
-            InlineSuggestion inlineSuggestion = createAugmentedInlineSuggestion(sessionId, dataset,
-                    inlinePresentation, inlineSuggestionUi, client);
+            InlineSuggestion inlineSuggestion = createAugmentedInlineSuggestion(dataset,
+                    inlinePresentation, inlineSuggestionUi, inlineSuggestionUiCallback);
             inlineSuggestions.add(inlineSuggestion);
         }
         return new InlineSuggestionsResponse(inlineSuggestions);
@@ -114,22 +122,17 @@
         return new InlineSuggestionsResponse(inlineSuggestions);
     }
 
-    private static InlineSuggestion createAugmentedInlineSuggestion(int sessionId,
-            @NonNull Dataset dataset,
+    private static InlineSuggestion createAugmentedInlineSuggestion(@NonNull Dataset dataset,
             @NonNull InlinePresentation inlinePresentation,
             @NonNull InlineSuggestionUi inlineSuggestionUi,
-            @NonNull IAutoFillManagerClient client) {
+            @NonNull InlineSuggestionUiCallback inlineSuggestionUiCallback) {
         // TODO(b/146453195): fill in the autofill hint properly.
         final InlineSuggestionInfo inlineSuggestionInfo = new InlineSuggestionInfo(
                 inlinePresentation.getInlinePresentationSpec(),
                 InlineSuggestionInfo.SOURCE_PLATFORM, new String[]{""},
                 InlineSuggestionInfo.TYPE_SUGGESTION);
         final View.OnClickListener onClickListener = v -> {
-            try {
-                client.autofill(sessionId, dataset.getFieldIds(), dataset.getFieldValues());
-            } catch (RemoteException e) {
-                Slog.w(TAG, "Encounter exception autofilling the values");
-            }
+            inlineSuggestionUiCallback.autofill(dataset);
         };
         final InlineSuggestion inlineSuggestion = new InlineSuggestion(inlineSuggestionInfo,
                 createInlineContentProvider(inlinePresentation, inlineSuggestionUi,
diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
index 5fbdd25..5e6f6fea 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
@@ -69,6 +69,7 @@
     private final int mIdleUnbindTimeoutMs;
     private final int mRequestTimeoutMs;
     private final ComponentName mComponentName;
+    private final RemoteAugmentedAutofillServiceCallbacks mCallbacks;
 
     RemoteAugmentedAutofillService(Context context, ComponentName serviceName,
             int userId, RemoteAugmentedAutofillServiceCallbacks callbacks,
@@ -81,6 +82,7 @@
         mIdleUnbindTimeoutMs = idleUnbindTimeoutMs;
         mRequestTimeoutMs = requestTimeoutMs;
         mComponentName = serviceName;
+        mCallbacks = callbacks;
 
         // Bind right away.
         connect();
@@ -159,9 +161,12 @@
                             focusedId, focusedValue, requestTime, inlineSuggestionsRequest,
                             new IFillCallback.Stub() {
                                 @Override
-                                public void onSuccess(@Nullable Dataset[] inlineSuggestionsData) {
-                                    maybeHandleInlineSuggestions(sessionId, inlineSuggestionsData,
-                                            focusedId, inlineSuggestionsCallback, client);
+                                public void onSuccess(@Nullable Dataset[] inlineSuggestionsData,
+                                        @Nullable Bundle clientState) {
+                                    mCallbacks.resetLastResponse();
+                                    maybeRequestShowInlineSuggestions(sessionId,
+                                            inlineSuggestionsData, focusedId,
+                                            inlineSuggestionsCallback, client, clientState);
                                     requestAutofill.complete(null);
                                 }
 
@@ -223,20 +228,32 @@
         });
     }
 
-    private void maybeHandleInlineSuggestions(int sessionId,
+    private void maybeRequestShowInlineSuggestions(int sessionId,
             @Nullable Dataset[] inlineSuggestionsData, @NonNull AutofillId focusedId,
             @Nullable IInlineSuggestionsResponseCallback inlineSuggestionsCallback,
-            @NonNull IAutoFillManagerClient client) {
+            @NonNull IAutoFillManagerClient client, @Nullable Bundle clientState) {
         if (ArrayUtils.isEmpty(inlineSuggestionsData) || inlineSuggestionsCallback == null) {
             return;
         }
+        mCallbacks.setLastResponse(sessionId);
         try {
             inlineSuggestionsCallback.onInlineSuggestionsResponse(
-                    InlineSuggestionFactory.createAugmentedInlineSuggestionsResponse(sessionId,
-                            inlineSuggestionsData, focusedId, mContext, client));
+                    InlineSuggestionFactory.createAugmentedInlineSuggestionsResponse(
+                            inlineSuggestionsData, focusedId, mContext,
+                            dataset -> {
+                                mCallbacks.logAugmentedAutofillSelected(sessionId,
+                                        dataset.getId(), clientState);
+                                try {
+                                    client.autofill(sessionId, dataset.getFieldIds(),
+                                            dataset.getFieldValues());
+                                } catch (RemoteException e) {
+                                    Slog.w(TAG, "Encounter exception autofilling the values");
+                                }
+                            }));
         } catch (RemoteException e) {
             Slog.w(TAG, "Exception sending inline suggestions response back to IME.");
         }
+        mCallbacks.logAugmentedAutofillShown(sessionId, clientState);
     }
 
     @Override
@@ -254,8 +271,13 @@
 
     public interface RemoteAugmentedAutofillServiceCallbacks
             extends AbstractRemoteService.VultureCallback<RemoteAugmentedAutofillService> {
-        // NOTE: so far we don't need to notify the callback implementation (an inner class on
-        // AutofillManagerServiceImpl) of the request results (success, timeouts, etc..), so this
-        // callback interface is empty.
+        void resetLastResponse();
+
+        void setLastResponse(int sessionId);
+
+        void logAugmentedAutofillShown(int sessionId, @Nullable Bundle clientState);
+
+        void logAugmentedAutofillSelected(int sessionId, @Nullable String suggestionId,
+                @Nullable Bundle clientState);
     }
 }
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index ee37de5..415ecd8 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -2674,7 +2674,9 @@
 
         if (response.supportsInlineSuggestions()) {
             if (requestShowInlineSuggestions(response)) {
-                //TODO(b/137800469): Add logging instead of bypassing below logic.
+                //TODO(b/137800469): Fix it to log showed only when IME asks for inflation, rather
+                // than here where framework sends back the response.
+                mService.logDatasetShown(id, mClientState);
                 return;
             }
         }
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 2982dc9..0194a21 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -557,13 +557,17 @@
                 .asInterface(ServiceManager.getService("dnsresolver"));
     }
 
-    /** Handler thread used for both of the handlers below. */
+    /** Handler thread used for all of the handlers below. */
     @VisibleForTesting
     protected final HandlerThread mHandlerThread;
     /** Handler used for internal events. */
     final private InternalHandler mHandler;
     /** Handler used for incoming {@link NetworkStateTracker} events. */
     final private NetworkStateTrackerHandler mTrackerHandler;
+    /** Handler used for processing {@link android.net.ConnectivityDiagnosticsManager} events */
+    @VisibleForTesting
+    final ConnectivityDiagnosticsHandler mConnectivityDiagnosticsHandler;
+
     private final DnsManager mDnsManager;
 
     private boolean mSystemReady;
@@ -630,6 +634,10 @@
     @VisibleForTesting
     final MultipathPolicyTracker mMultipathPolicyTracker;
 
+    @VisibleForTesting
+    final Map<IConnectivityDiagnosticsCallback, ConnectivityDiagnosticsCallbackInfo>
+            mConnectivityDiagnosticsCallbacks = new HashMap<>();
+
     /**
      * Implements support for the legacy "one network per network type" model.
      *
@@ -962,6 +970,8 @@
         mHandlerThread.start();
         mHandler = new InternalHandler(mHandlerThread.getLooper());
         mTrackerHandler = new NetworkStateTrackerHandler(mHandlerThread.getLooper());
+        mConnectivityDiagnosticsHandler =
+                new ConnectivityDiagnosticsHandler(mHandlerThread.getLooper());
 
         mReleasePendingIntentDelayMs = Settings.Secure.getInt(context.getContentResolver(),
                 Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS, 5_000);
@@ -3391,18 +3401,7 @@
         nri.unlinkDeathRecipient();
         mNetworkRequests.remove(nri.request);
 
-        synchronized (mUidToNetworkRequestCount) {
-            int requests = mUidToNetworkRequestCount.get(nri.mUid, 0);
-            if (requests < 1) {
-                Slog.wtf(TAG, "BUG: too small request count " + requests + " for UID " +
-                        nri.mUid);
-            } else if (requests == 1) {
-                mUidToNetworkRequestCount.removeAt(
-                        mUidToNetworkRequestCount.indexOfKey(nri.mUid));
-            } else {
-                mUidToNetworkRequestCount.put(nri.mUid, requests - 1);
-            }
-        }
+        decrementNetworkRequestPerUidCount(nri);
 
         mNetworkRequestInfoLogs.log("RELEASE " + nri);
         if (nri.request.isRequest()) {
@@ -3473,6 +3472,19 @@
         }
     }
 
+    private void decrementNetworkRequestPerUidCount(final NetworkRequestInfo nri) {
+        synchronized (mUidToNetworkRequestCount) {
+            final int requests = mUidToNetworkRequestCount.get(nri.mUid, 0);
+            if (requests < 1) {
+                Slog.wtf(TAG, "BUG: too small request count " + requests + " for UID " + nri.mUid);
+            } else if (requests == 1) {
+                mUidToNetworkRequestCount.removeAt(mUidToNetworkRequestCount.indexOfKey(nri.mUid));
+            } else {
+                mUidToNetworkRequestCount.put(nri.mUid, requests - 1);
+            }
+        }
+    }
+
     @Override
     public void setAcceptUnvalidated(Network network, boolean accept, boolean always) {
         enforceNetworkStackSettingsOrSetup();
@@ -5091,6 +5103,10 @@
             }
         }
 
+        NetworkRequestInfo(NetworkRequest r) {
+            this(r, null);
+        }
+
         private void enforceRequestCountLimit() {
             synchronized (mUidToNetworkRequestCount) {
                 int networkRequests = mUidToNetworkRequestCount.get(mUid, 0) + 1;
@@ -6165,7 +6181,10 @@
     private void callCallbackForRequest(NetworkRequestInfo nri,
             NetworkAgentInfo networkAgent, int notificationType, int arg1) {
         if (nri.messenger == null) {
-            return;  // Default request has no msgr
+            // Default request has no msgr. Also prevents callbacks from being invoked for
+            // NetworkRequestInfos registered with ConnectivityDiagnostics requests. Those callbacks
+            // are Type.LISTEN, but should not have NetworkCallbacks invoked.
+            return;
         }
         Bundle bundle = new Bundle();
         // TODO: check if defensive copies of data is needed.
@@ -6319,12 +6338,34 @@
             }
         }
 
+        static class RequestReassignment {
+            @NonNull public final NetworkRequestInfo mRequest;
+            @Nullable public final NetworkAgentInfo mOldNetwork;
+            @Nullable public final NetworkAgentInfo mNewNetwork;
+            RequestReassignment(@NonNull final NetworkRequestInfo request,
+                    @Nullable final NetworkAgentInfo oldNetwork,
+                    @Nullable final NetworkAgentInfo newNetwork) {
+                mRequest = request;
+                mOldNetwork = oldNetwork;
+                mNewNetwork = newNetwork;
+            }
+        }
+
         @NonNull private final Set<NetworkBgStatePair> mRematchedNetworks = new ArraySet<>();
+        @NonNull private final List<RequestReassignment> mReassignments = new ArrayList<>();
 
         @NonNull Iterable<NetworkBgStatePair> getRematchedNetworks() {
             return mRematchedNetworks;
         }
 
+        @NonNull Iterable<RequestReassignment> getRequestReassignments() {
+            return mReassignments;
+        }
+
+        void addRequestReassignment(@NonNull final RequestReassignment reassignment) {
+            mReassignments.add(reassignment);
+        }
+
         void addRematchedNetwork(@NonNull final NetworkBgStatePair network) {
             mRematchedNetworks.add(network);
         }
@@ -6400,20 +6441,13 @@
         changes.addRematchedNetwork(new NetworkReassignment.NetworkBgStatePair(newNetwork,
                 newNetwork.isBackgroundNetwork()));
 
-        final int score = newNetwork.getCurrentScore();
-
         if (VDBG || DDBG) log("rematching " + newNetwork.name());
 
         final ArrayMap<NetworkRequestInfo, NetworkAgentInfo> reassignedRequests =
                 computeRequestReassignmentForNetwork(newNetwork);
 
-        NetworkCapabilities nc = newNetwork.networkCapabilities;
-        if (VDBG) log(" network has: " + nc);
-
         // Find and migrate to this Network any NetworkRequests for
         // which this network is now the best.
-        final ArrayList<NetworkAgentInfo> removedRequests = new ArrayList<>();
-        final ArrayList<NetworkRequestInfo> addedRequests = new ArrayList<>();
         for (final Map.Entry<NetworkRequestInfo, NetworkAgentInfo> entry :
                 reassignedRequests.entrySet()) {
             final NetworkRequestInfo nri = entry.getKey();
@@ -6427,7 +6461,6 @@
                     }
                     previousSatisfier.removeRequest(nri.request.requestId);
                     previousSatisfier.lingerRequest(nri.request, now, mLingerDelayMs);
-                    removedRequests.add(previousSatisfier);
                 } else {
                     if (VDBG || DDBG) log("   accepting network in place of null");
                 }
@@ -6436,7 +6469,8 @@
                 if (!newSatisfier.addRequest(nri.request)) {
                     Slog.wtf(TAG, "BUG: " + newSatisfier.name() + " already has " + nri.request);
                 }
-                addedRequests.add(nri);
+                changes.addRequestReassignment(new NetworkReassignment.RequestReassignment(
+                        nri, previousSatisfier, newSatisfier));
                 // Tell NetworkProviders about the new score, so they can stop
                 // trying to connect if they know they cannot match it.
                 // TODO - this could get expensive if we have a lot of requests for this
@@ -6493,21 +6527,6 @@
             // Have a new default network, release the transition wakelock in
             scheduleReleaseNetworkTransitionWakelock();
         }
-
-        if (!newNetwork.networkCapabilities.equalRequestableCapabilities(nc)) {
-            Slog.wtf(TAG, String.format(
-                    "BUG: %s changed requestable capabilities during rematch: %s -> %s",
-                    newNetwork.name(), nc, newNetwork.networkCapabilities));
-        }
-        if (newNetwork.getCurrentScore() != score) {
-            Slog.wtf(TAG, String.format(
-                    "BUG: %s changed score during rematch: %d -> %d",
-                    newNetwork.name(), score, newNetwork.getCurrentScore()));
-        }
-
-        // Notify requested networks are available after the default net is switched, but
-        // before LegacyTypeTracker sends legacy broadcasts
-        for (NetworkRequestInfo nri : addedRequests) notifyNetworkAvailable(newNetwork, nri);
     }
 
     /**
@@ -6536,6 +6555,15 @@
 
         final NetworkAgentInfo newDefaultNetwork = getDefaultNetwork();
 
+        // Notify requested networks are available after the default net is switched, but
+        // before LegacyTypeTracker sends legacy broadcasts
+        for (final NetworkReassignment.RequestReassignment event :
+                changes.getRequestReassignments()) {
+            if (null != event.mNewNetwork) {
+                notifyNetworkAvailable(event.mNewNetwork, event.mRequest);
+            }
+        }
+
         for (final NetworkReassignment.NetworkBgStatePair event : changes.getRematchedNetworks()) {
             // Process listen requests and update capabilities if the background state has
             // changed for this network. For consistency with previous behavior, send onLost
@@ -7321,19 +7349,161 @@
         }
     }
 
+    /**
+     * Handler used for managing all Connectivity Diagnostics related functions.
+     *
+     * @see android.net.ConnectivityDiagnosticsManager
+     *
+     * TODO(b/147816404): Explore moving ConnectivityDiagnosticsHandler to a separate file
+     */
+    @VisibleForTesting
+    class ConnectivityDiagnosticsHandler extends Handler {
+        /**
+         * Used to handle ConnectivityDiagnosticsCallback registration events from {@link
+         * android.net.ConnectivityDiagnosticsManager}.
+         * obj = ConnectivityDiagnosticsCallbackInfo with IConnectivityDiagnosticsCallback and
+         * NetworkRequestInfo to be registered
+         */
+        private static final int EVENT_REGISTER_CONNECTIVITY_DIAGNOSTICS_CALLBACK = 1;
+
+        /**
+         * Used to handle ConnectivityDiagnosticsCallback unregister events from {@link
+         * android.net.ConnectivityDiagnosticsManager}.
+         * obj = the IConnectivityDiagnosticsCallback to be unregistered
+         * arg1 = the uid of the caller
+         */
+        private static final int EVENT_UNREGISTER_CONNECTIVITY_DIAGNOSTICS_CALLBACK = 2;
+
+        private ConnectivityDiagnosticsHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case EVENT_REGISTER_CONNECTIVITY_DIAGNOSTICS_CALLBACK: {
+                    handleRegisterConnectivityDiagnosticsCallback(
+                            (ConnectivityDiagnosticsCallbackInfo) msg.obj);
+                    break;
+                }
+                case EVENT_UNREGISTER_CONNECTIVITY_DIAGNOSTICS_CALLBACK: {
+                    handleUnregisterConnectivityDiagnosticsCallback(
+                            (IConnectivityDiagnosticsCallback) msg.obj, msg.arg1);
+                    break;
+                }
+            }
+        }
+    }
+
+    /** Class used for cleaning up IConnectivityDiagnosticsCallback instances after their death. */
+    @VisibleForTesting
+    class ConnectivityDiagnosticsCallbackInfo implements Binder.DeathRecipient {
+        @NonNull private final IConnectivityDiagnosticsCallback mCb;
+        @NonNull private final NetworkRequestInfo mRequestInfo;
+
+        @VisibleForTesting
+        ConnectivityDiagnosticsCallbackInfo(
+                @NonNull IConnectivityDiagnosticsCallback cb, @NonNull NetworkRequestInfo nri) {
+            mCb = cb;
+            mRequestInfo = nri;
+        }
+
+        @Override
+        public void binderDied() {
+            log("ConnectivityDiagnosticsCallback IBinder died.");
+            unregisterConnectivityDiagnosticsCallback(mCb);
+        }
+    }
+
+    private void handleRegisterConnectivityDiagnosticsCallback(
+            @NonNull ConnectivityDiagnosticsCallbackInfo cbInfo) {
+        ensureRunningOnConnectivityServiceThread();
+
+        final IConnectivityDiagnosticsCallback cb = cbInfo.mCb;
+        final NetworkRequestInfo nri = cbInfo.mRequestInfo;
+
+        // This means that the client registered the same callback multiple times. Do
+        // not override the previous entry, and exit silently.
+        if (mConnectivityDiagnosticsCallbacks.containsKey(cb)) {
+            if (VDBG) log("Diagnostics callback is already registered");
+
+            // Decrement the reference count for this NetworkRequestInfo. The reference count is
+            // incremented when the NetworkRequestInfo is created as part of
+            // enforceRequestCountLimit().
+            decrementNetworkRequestPerUidCount(nri);
+            return;
+        }
+
+        mConnectivityDiagnosticsCallbacks.put(cb, cbInfo);
+
+        try {
+            cb.asBinder().linkToDeath(cbInfo, 0);
+        } catch (RemoteException e) {
+            cbInfo.binderDied();
+        }
+    }
+
+    private void handleUnregisterConnectivityDiagnosticsCallback(
+            @NonNull IConnectivityDiagnosticsCallback cb, int uid) {
+        ensureRunningOnConnectivityServiceThread();
+
+        if (!mConnectivityDiagnosticsCallbacks.containsKey(cb)) {
+            if (VDBG) log("Removing diagnostics callback that is not currently registered");
+            return;
+        }
+
+        final NetworkRequestInfo nri = mConnectivityDiagnosticsCallbacks.get(cb).mRequestInfo;
+
+        if (uid != nri.mUid) {
+            if (VDBG) loge("Different uid than registrant attempting to unregister cb");
+            return;
+        }
+
+        cb.asBinder().unlinkToDeath(mConnectivityDiagnosticsCallbacks.remove(cb), 0);
+    }
+
     @Override
     public void registerConnectivityDiagnosticsCallback(
             @NonNull IConnectivityDiagnosticsCallback callback, @NonNull NetworkRequest request) {
-        // TODO(b/146444622): implement register IConnectivityDiagnosticsCallback functionality
-        throw new UnsupportedOperationException(
-                "registerConnectivityDiagnosticsCallback not yet implemented");
+        if (request.legacyType != TYPE_NONE) {
+            throw new IllegalArgumentException("ConnectivityManager.TYPE_* are deprecated."
+                    + " Please use NetworkCapabilities instead.");
+        }
+
+        // This NetworkCapabilities is only used for matching to Networks. Clear out its owner uid
+        // and administrator uids to be safe.
+        final NetworkCapabilities nc = new NetworkCapabilities(request.networkCapabilities);
+        restrictRequestUidsForCaller(nc);
+
+        final NetworkRequest requestWithId =
+                new NetworkRequest(
+                        nc, TYPE_NONE, nextNetworkRequestId(), NetworkRequest.Type.LISTEN);
+
+        // NetworkRequestInfos created here count towards MAX_NETWORK_REQUESTS_PER_UID limit.
+        //
+        // nri is not bound to the death of callback. Instead, callback.bindToDeath() is set in
+        // handleRegisterConnectivityDiagnosticsCallback(). nri will be cleaned up as part of the
+        // callback's binder death.
+        final NetworkRequestInfo nri = new NetworkRequestInfo(requestWithId);
+        final ConnectivityDiagnosticsCallbackInfo cbInfo =
+                new ConnectivityDiagnosticsCallbackInfo(callback, nri);
+
+        mConnectivityDiagnosticsHandler.sendMessage(
+                mConnectivityDiagnosticsHandler.obtainMessage(
+                        ConnectivityDiagnosticsHandler
+                                .EVENT_REGISTER_CONNECTIVITY_DIAGNOSTICS_CALLBACK,
+                        cbInfo));
     }
 
     @Override
     public void unregisterConnectivityDiagnosticsCallback(
             @NonNull IConnectivityDiagnosticsCallback callback) {
-        // TODO(b/146444622): implement register IConnectivityDiagnosticsCallback functionality
-        throw new UnsupportedOperationException(
-                "unregisterConnectivityDiagnosticsCallback not yet implemented");
+        mConnectivityDiagnosticsHandler.sendMessage(
+                mConnectivityDiagnosticsHandler.obtainMessage(
+                        ConnectivityDiagnosticsHandler
+                                .EVENT_UNREGISTER_CONNECTIVITY_DIAGNOSTICS_CALLBACK,
+                        Binder.getCallingUid(),
+                        0,
+                        callback));
     }
 }
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index bacffb6..3e5c278 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -393,8 +393,8 @@
         @Override
         public int onHealthCheckFailed(@Nullable VersionedPackage failedPackage,
                 @FailureReasons int failureReason) {
-            if (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH
-                    || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING) {
+            if (!isDisabled() && (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH
+                    || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING)) {
                 return mapRescueLevelToUserImpact(getNextRescueLevel());
             } else {
                 return PackageHealthObserverImpact.USER_IMPACT_NONE;
diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
index d5fc14b..39f79ca 100644
--- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
@@ -550,7 +550,6 @@
                     return data;
                 }
             }
-            Slog.e(TAG, "no controller energy info supplied for " + receiver.getName());
         } catch (TimeoutException e) {
             Slog.w(TAG, "timeout reading " + receiver.getName() + " stats");
         }
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index bb8b12e..2fc9d04 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -25,7 +25,6 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.Slog;
-import android.util.StatsLog;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.compat.AndroidBuildClassifier;
@@ -54,7 +53,7 @@
     public PlatformCompat(Context context) {
         mContext = context;
         mChangeReporter = new ChangeReporter(
-                StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__SOURCE__SYSTEM_SERVER);
+                ChangeReporter.SOURCE_SYSTEM_SERVER);
         mCompatConfig = CompatConfig.create(new AndroidBuildClassifier(), mContext);
     }
 
@@ -62,14 +61,14 @@
     PlatformCompat(Context context, CompatConfig compatConfig) {
         mContext = context;
         mChangeReporter = new ChangeReporter(
-                StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__SOURCE__SYSTEM_SERVER);
+                ChangeReporter.SOURCE_SYSTEM_SERVER);
         mCompatConfig = compatConfig;
     }
 
     @Override
     public void reportChange(long changeId, ApplicationInfo appInfo) {
         reportChange(changeId, appInfo.uid,
-                StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__LOGGED);
+                ChangeReporter.STATE_LOGGED);
     }
 
     @Override
@@ -83,18 +82,18 @@
 
     @Override
     public void reportChangeByUid(long changeId, int uid) {
-        reportChange(changeId, uid, StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__LOGGED);
+        reportChange(changeId, uid, ChangeReporter.STATE_LOGGED);
     }
 
     @Override
     public boolean isChangeEnabled(long changeId, ApplicationInfo appInfo) {
         if (mCompatConfig.isChangeEnabled(changeId, appInfo)) {
             reportChange(changeId, appInfo.uid,
-                    StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__ENABLED);
+                    ChangeReporter.STATE_ENABLED);
             return true;
         }
         reportChange(changeId, appInfo.uid,
-                StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__DISABLED);
+                ChangeReporter.STATE_DISABLED);
         return false;
     }
 
diff --git a/services/core/java/com/android/server/media/BluetoothRouteProvider.java b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
index 8179c32..669f1ac 100644
--- a/services/core/java/com/android/server/media/BluetoothRouteProvider.java
+++ b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
@@ -186,6 +186,7 @@
                 .setConnectionState(MediaRoute2Info.CONNECTION_STATE_DISCONNECTED)
                 .setDescription(mContext.getResources().getText(
                         R.string.bluetooth_a2dp_audio_route_name).toString())
+                .setDeviceType(MediaRoute2Info.DEVICE_TYPE_BLUETOOTH)
                 .build();
         newBtRoute.connectedProfiles = new SparseBooleanArray();
         return newBtRoute;
diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java
index 1cd8aad..5123362 100644
--- a/services/core/java/com/android/server/media/MediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java
@@ -36,6 +36,7 @@
     final Object mLock = new Object();
 
     Callback mCallback;
+    boolean mIsSystemRouteProvider;
     private volatile MediaRoute2ProviderInfo mProviderInfo;
 
     @GuardedBy("mLock")
@@ -85,6 +86,7 @@
         } else {
             mProviderInfo = new MediaRoute2ProviderInfo.Builder(providerInfo)
                     .setUniqueId(mUniqueId)
+                    .setSystemRouteProvider(mIsSystemRouteProvider)
                     .build();
         }
     }
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 558eb8d..924a9b7 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -83,6 +83,8 @@
         super(sComponentName);
         setCallback(callback);
 
+        mIsSystemRouteProvider = true;
+
         mContext = context;
         mHandler = new Handler(Looper.getMainLooper());
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 93a0506..148c5ad 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2728,7 +2728,7 @@
         t.traceBegin("get system config");
         SystemConfig systemConfig = SystemConfig.getInstance();
         mAvailableFeatures = systemConfig.getAvailableFeatures();
-        ApplicationPackageManager.invalidateSysFeatureCache();
+        ApplicationPackageManager.invalidateHasSystemFeatureCache();
         t.traceEnd();
 
         mProtectedPackages = new ProtectedPackages(mContext);
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 4bab224..4425c0a 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -52,6 +52,7 @@
 import android.provider.DeviceConfig;
 import android.util.ArraySet;
 import android.util.IntArray;
+import android.util.Log;
 import android.util.LongArrayQueue;
 import android.util.Slog;
 import android.util.SparseBooleanArray;
@@ -94,7 +95,7 @@
 class RollbackManagerServiceImpl extends IRollbackManager.Stub {
 
     private static final String TAG = "RollbackManager";
-    private static final boolean LOCAL_LOGV = false;
+    private static final boolean LOCAL_LOGV = Log.isLoggable(TAG, Log.VERBOSE);
 
     // Rollbacks expire after 14 days.
     private static final long DEFAULT_ROLLBACK_LIFETIME_DURATION_MILLIS =
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 0f79a11..96f1219 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -899,7 +899,6 @@
                     return data;
                 }
             }
-            Slog.e(TAG, "no controller energy info supplied for " + receiver.getName());
         } catch (TimeoutException e) {
             Slog.w(TAG, "timeout reading " + receiver.getName() + " stats");
         }
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
index e95fc4a..a1e643f 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
@@ -101,11 +101,12 @@
     private TimestampedValue<Long> mLastAutoSystemClockTimeSet;
 
     /**
-     * A mapping from phoneId to a time suggestion. We typically expect one or two mappings: devices
-     * will have a small number of telephony devices and phoneIds are assumed to be stable.
+     * A mapping from slotIndex to a time suggestion. We typically expect one or two mappings:
+     * devices will have a small number of telephony devices and slotIndexs are assumed to be
+     * stable.
      */
     @GuardedBy("this")
-    private final ArrayMapWithHistory<Integer, PhoneTimeSuggestion> mSuggestionByPhoneId =
+    private final ArrayMapWithHistory<Integer, PhoneTimeSuggestion> mSuggestionBySlotIndex =
             new ArrayMapWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE);
 
     @GuardedBy("this")
@@ -155,7 +156,7 @@
         }
 
         // Perform validation / input filtering and record the validated suggestion against the
-        // phoneId.
+        // slotIndex.
         if (!validateAndStorePhoneSuggestion(timeSuggestion)) {
             return;
         }
@@ -202,7 +203,7 @@
 
         ipw.println("Phone suggestion history:");
         ipw.increaseIndent(); // level 2
-        mSuggestionByPhoneId.dump(ipw);
+        mSuggestionBySlotIndex.dump(ipw);
         ipw.decreaseIndent(); // level 2
 
         ipw.println("Network suggestion history:");
@@ -223,8 +224,8 @@
             return false;
         }
 
-        int phoneId = suggestion.getPhoneId();
-        PhoneTimeSuggestion previousSuggestion = mSuggestionByPhoneId.get(phoneId);
+        int slotIndex = suggestion.getSlotIndex();
+        PhoneTimeSuggestion previousSuggestion = mSuggestionBySlotIndex.get(slotIndex);
         if (previousSuggestion != null) {
             // We can log / discard suggestions with obvious issues with the reference time clock.
             if (previousSuggestion.getUtcTime() == null
@@ -249,7 +250,7 @@
         }
 
         // Store the latest suggestion.
-        mSuggestionByPhoneId.put(phoneId, suggestion);
+        mSuggestionBySlotIndex.put(slotIndex, suggestion);
         return true;
     }
 
@@ -323,15 +324,15 @@
         //
         // [1] https://en.wikipedia.org/wiki/NITZ
         //
-        // Generally, when there are suggestions from multiple phoneIds they should usually
+        // Generally, when there are suggestions from multiple slotIndexs they should usually
         // approximately agree. In cases where signals *are* inaccurate we don't want to vacillate
-        // between signals from two phoneIds. However, it is known for NITZ signals to be incorrect
-        // occasionally, which means we also don't want to stick forever with one phoneId. Without
-        // cross-referencing across sources (e.g. the current device time, NTP), or doing some kind
-        // of statistical analysis of consistency within and across phoneIds, we can't know which
-        // suggestions are more correct.
+        // between signals from two slotIndexs. However, it is known for NITZ signals to be
+        // incorrect occasionally, which means we also don't want to stick forever with one
+        // slotIndex. Without cross-referencing across sources (e.g. the current device time, NTP),
+        // or doing some kind of statistical analysis of consistency within and across slotIndexs,
+        // we can't know which suggestions are more correct.
         //
-        // For simplicity, we try to value recency, then consistency of phoneId.
+        // For simplicity, we try to value recency, then consistency of slotIndex.
         //
         // The heuristic works as follows:
         // Recency: The most recent suggestion from each phone is scored. The score is based on a
@@ -339,20 +340,20 @@
         // bucket, thus applying a loose reference time ordering. The suggestion with the highest
         // score is used.
         // Consistency: If there a multiple suggestions with the same score, the suggestion with the
-        // lowest phoneId is always taken.
+        // lowest slotIndex is always taken.
         //
         // In the trivial case with a single ID this will just mean that the latest received
         // suggestion is used.
 
         PhoneTimeSuggestion bestSuggestion = null;
         int bestScore = PHONE_INVALID_SCORE;
-        for (int i = 0; i < mSuggestionByPhoneId.size(); i++) {
-            Integer phoneId = mSuggestionByPhoneId.keyAt(i);
-            PhoneTimeSuggestion candidateSuggestion = mSuggestionByPhoneId.valueAt(i);
+        for (int i = 0; i < mSuggestionBySlotIndex.size(); i++) {
+            Integer slotIndex = mSuggestionBySlotIndex.keyAt(i);
+            PhoneTimeSuggestion candidateSuggestion = mSuggestionBySlotIndex.valueAt(i);
             if (candidateSuggestion == null) {
                 // Unexpected - null suggestions should never be stored.
-                Slog.w(LOG_TAG, "Latest suggestion unexpectedly null for phoneId."
-                        + " phoneId=" + phoneId);
+                Slog.w(LOG_TAG, "Latest suggestion unexpectedly null for slotIndex."
+                        + " slotIndex=" + slotIndex);
                 continue;
             } else if (candidateSuggestion.getUtcTime() == null) {
                 // Unexpected - we do not store empty suggestions.
@@ -372,10 +373,10 @@
                 bestSuggestion = candidateSuggestion;
                 bestScore = candidateScore;
             } else if (bestScore == candidateScore) {
-                // Tie! Use the suggestion with the lowest phoneId.
-                int candidatePhoneId = candidateSuggestion.getPhoneId();
-                int bestPhoneId = bestSuggestion.getPhoneId();
-                if (candidatePhoneId < bestPhoneId) {
+                // Tie! Use the suggestion with the lowest slotIndex.
+                int candidateSlotIndex = candidateSuggestion.getSlotIndex();
+                int bestSlotIndex = bestSuggestion.getSlotIndex();
+                if (candidateSlotIndex < bestSlotIndex) {
                     bestSuggestion = candidateSuggestion;
                 }
             }
@@ -396,7 +397,7 @@
         }
 
         // The score is based on the age since receipt. Suggestions are bucketed so two
-        // suggestions in the same bucket from different phoneIds are scored the same.
+        // suggestions in the same bucket from different slotIndexs are scored the same.
         long ageMillis = elapsedRealtimeMillis - utcTime.getReferenceTimeMillis();
 
         // Turn the age into a discrete value: 0 <= bucketIndex < PHONE_BUCKET_COUNT.
@@ -560,8 +561,8 @@
      */
     @VisibleForTesting
     @Nullable
-    public synchronized PhoneTimeSuggestion getLatestPhoneSuggestion(int phoneId) {
-        return mSuggestionByPhoneId.get(phoneId);
+    public synchronized PhoneTimeSuggestion getLatestPhoneSuggestion(int slotIndex) {
+        return mSuggestionBySlotIndex.get(slotIndex);
     }
 
     /**
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
index b4a4399..b0e0069 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
@@ -172,12 +172,12 @@
     private final LocalLog mTimeZoneChangesLog = new LocalLog(30, false /* useLocalTimestamps */);
 
     /**
-     * A mapping from phoneId to a phone time zone suggestion. We typically expect one or two
-     * mappings: devices will have a small number of telephony devices and phoneIds are assumed to
+     * A mapping from slotIndex to a phone time zone suggestion. We typically expect one or two
+     * mappings: devices will have a small number of telephony devices and slotIndexs are assumed to
      * be stable.
      */
     @GuardedBy("this")
-    private ArrayMapWithHistory<Integer, QualifiedPhoneTimeZoneSuggestion> mSuggestionByPhoneId =
+    private ArrayMapWithHistory<Integer, QualifiedPhoneTimeZoneSuggestion> mSuggestionBySlotIndex =
             new ArrayMapWithHistory<>(KEEP_PHONE_SUGGESTION_HISTORY_SIZE);
 
     /**
@@ -205,7 +205,7 @@
     /**
      * Suggests a time zone for the device, or withdraws a previous suggestion if
      * {@link PhoneTimeZoneSuggestion#getZoneId()} is {@code null}. The suggestion is scoped to a
-     * specific {@link PhoneTimeZoneSuggestion#getPhoneId() phone}.
+     * specific {@link PhoneTimeZoneSuggestion#getSlotIndex() phone}.
      * See {@link PhoneTimeZoneSuggestion} for an explanation of the metadata associated with a
      * suggestion. The strategy uses suggestions to decide whether to modify the device's time zone
      * setting and what to set it to.
@@ -221,8 +221,8 @@
         QualifiedPhoneTimeZoneSuggestion scoredSuggestion =
                 new QualifiedPhoneTimeZoneSuggestion(suggestion, score);
 
-        // Store the suggestion against the correct phoneId.
-        mSuggestionByPhoneId.put(suggestion.getPhoneId(), scoredSuggestion);
+        // Store the suggestion against the correct slotIndex.
+        mSuggestionBySlotIndex.put(suggestion.getSlotIndex(), scoredSuggestion);
 
         // Now perform auto time zone detection. The new suggestion may be used to modify the time
         // zone setting.
@@ -384,8 +384,9 @@
         // and find the best. Note that we deliberately do not look at age: the caller can
         // rate-limit so age is not a strong indicator of confidence. Instead, the callers are
         // expected to withdraw suggestions they no longer have confidence in.
-        for (int i = 0; i < mSuggestionByPhoneId.size(); i++) {
-            QualifiedPhoneTimeZoneSuggestion candidateSuggestion = mSuggestionByPhoneId.valueAt(i);
+        for (int i = 0; i < mSuggestionBySlotIndex.size(); i++) {
+            QualifiedPhoneTimeZoneSuggestion candidateSuggestion =
+                    mSuggestionBySlotIndex.valueAt(i);
             if (candidateSuggestion == null) {
                 // Unexpected
                 continue;
@@ -396,10 +397,10 @@
             } else if (candidateSuggestion.score > bestSuggestion.score) {
                 bestSuggestion = candidateSuggestion;
             } else if (candidateSuggestion.score == bestSuggestion.score) {
-                // Tie! Use the suggestion with the lowest phoneId.
-                int candidatePhoneId = candidateSuggestion.suggestion.getPhoneId();
-                int bestPhoneId = bestSuggestion.suggestion.getPhoneId();
-                if (candidatePhoneId < bestPhoneId) {
+                // Tie! Use the suggestion with the lowest slotIndex.
+                int candidateSlotIndex = candidateSuggestion.suggestion.getSlotIndex();
+                int bestSlotIndex = bestSuggestion.suggestion.getSlotIndex();
+                if (candidateSlotIndex < bestSlotIndex) {
                     bestSuggestion = candidateSuggestion;
                 }
             }
@@ -455,7 +456,7 @@
 
         ipw.println("Phone suggestion history:");
         ipw.increaseIndent(); // level 2
-        mSuggestionByPhoneId.dump(ipw);
+        mSuggestionBySlotIndex.dump(ipw);
         ipw.decreaseIndent(); // level 2
         ipw.decreaseIndent(); // level 1
         ipw.flush();
@@ -465,8 +466,8 @@
      * A method used to inspect strategy state during tests. Not intended for general use.
      */
     @VisibleForTesting
-    public synchronized QualifiedPhoneTimeZoneSuggestion getLatestPhoneSuggestion(int phoneId) {
-        return mSuggestionByPhoneId.get(phoneId);
+    public synchronized QualifiedPhoneTimeZoneSuggestion getLatestPhoneSuggestion(int slotIndex) {
+        return mSuggestionBySlotIndex.get(slotIndex);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index b05c250..7af097b 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -196,6 +196,7 @@
 import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
 import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
 import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
 import static com.android.server.wm.TaskPersister.DEBUG;
 import static com.android.server.wm.TaskPersister.IMAGE_EXTENSION;
 import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
@@ -311,6 +312,8 @@
 import com.android.server.uri.UriPermissionOwner;
 import com.android.server.wm.ActivityMetricsLogger.TransitionInfoSnapshot;
 import com.android.server.wm.ActivityStack.ActivityState;
+import com.android.server.wm.SurfaceAnimator.AnimationType;
+import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
 import com.android.server.wm.WindowManagerService.H;
 import com.android.server.wm.utils.InsetUtils;
 
@@ -4080,7 +4083,8 @@
 
     @Override
     boolean applyAnimation(WindowManager.LayoutParams lp, int transit, boolean enter,
-            boolean isVoiceInteraction, @Nullable Runnable animationFinishedCallback) {
+            boolean isVoiceInteraction,
+            @Nullable OnAnimationFinishedCallback animationFinishedCallback) {
         if (mUseTransferredAnimation) {
             return false;
         }
@@ -4158,7 +4162,7 @@
             // We aren't delayed anything, but exiting windows rely on the animation finished
             // callback being called in case the ActivityRecord was pretending to be delayed,
             // which we might have done because we were in closing/opening apps list.
-            onAnimationFinished();
+            onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION, null /* AnimationAdapter */);
             if (visible) {
                 // The token was made immediately visible, there will be no entrance animation.
                 // We need to inform the client the enter animation was finished.
@@ -6054,8 +6058,8 @@
     }
 
     @Override
-    protected void onAnimationFinished() {
-        super.onAnimationFinished();
+    protected void onAnimationFinished(@AnimationType int type, AnimationAdapter anim) {
+        super.onAnimationFinished(type, anim);
 
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "AR#onAnimationFinished");
         mTransit = TRANSIT_UNSET;
diff --git a/services/core/java/com/android/server/wm/AnimationAdapter.java b/services/core/java/com/android/server/wm/AnimationAdapter.java
index 1be3d61..0519b80 100644
--- a/services/core/java/com/android/server/wm/AnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/AnimationAdapter.java
@@ -21,6 +21,7 @@
 import android.view.SurfaceControl.Transaction;
 import android.view.animation.Animation;
 
+import com.android.server.wm.SurfaceAnimator.AnimationType;
 import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
 
 import java.io.PrintWriter;
@@ -47,9 +48,10 @@
      *                       component running the animation after {@code finishCallback} has been
      *                       invoked, or after the animation was cancelled.
      * @param t The Transaction to apply the initial frame of the animation.
+     * @param type The type of the animation.
      * @param finishCallback The callback to be invoked when the animation has finished.
      */
-    void startAnimation(SurfaceControl animationLeash, Transaction t,
+    void startAnimation(SurfaceControl animationLeash, Transaction t, @AnimationType int type,
             OnAnimationFinishedCallback finishCallback);
 
     /**
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 0798a91..f72020e 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -377,9 +377,9 @@
                     transitioningDecendants.add(app);
                 }
             }
-            wc.applyAnimation(animLp, transit, visible, voiceInteraction, () -> {
+            wc.applyAnimation(animLp, transit, visible, voiceInteraction, (type, anim) -> {
                 for (int j = 0; j < transitioningDecendants.size(); ++j) {
-                    transitioningDecendants.get(j).onAnimationFinished();
+                    transitioningDecendants.get(j).onAnimationFinished(type, anim);
                 }
             });
         }
diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java
index 16aff9c..537ca08 100644
--- a/services/core/java/com/android/server/wm/Dimmer.java
+++ b/services/core/java/com/android/server/wm/Dimmer.java
@@ -20,6 +20,7 @@
 import static com.android.server.wm.AlphaAnimationSpecProto.FROM;
 import static com.android.server.wm.AlphaAnimationSpecProto.TO;
 import static com.android.server.wm.AnimationSpecProto.ALPHA;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER;
 
 import android.annotation.Nullable;
 import android.graphics.Rect;
@@ -29,6 +30,8 @@
 import android.view.SurfaceControl;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.wm.SurfaceAnimator.AnimationType;
+import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
 
 import java.io.PrintWriter;
 
@@ -135,7 +138,7 @@
             mDimLayer = dimLayer;
             mDimming = true;
             final DimAnimatable dimAnimatable = new DimAnimatable(dimLayer);
-            mSurfaceAnimator = new SurfaceAnimator(dimAnimatable, () -> {
+            mSurfaceAnimator = new SurfaceAnimator(dimAnimatable, (type, anim) -> {
                 if (!mDimming) {
                     dimAnimatable.removeSurface();
                 }
@@ -157,8 +160,8 @@
     @VisibleForTesting
     interface SurfaceAnimatorStarter {
         void startAnimation(SurfaceAnimator surfaceAnimator, SurfaceControl.Transaction t,
-                AnimationAdapter anim, boolean hidden,
-                @Nullable Runnable animationFinishedCallback);
+                AnimationAdapter anim, boolean hidden, @AnimationType int type,
+                @Nullable OnAnimationFinishedCallback animationFinishedCallback);
     }
 
     Dimmer(WindowContainer host) {
@@ -345,7 +348,7 @@
         mSurfaceAnimatorStarter.startAnimation(animator, t, new LocalAnimationAdapter(
                 new AlphaAnimationSpec(startAlpha, endAlpha, getDimDuration(container)),
                 mHost.mWmService.mSurfaceAnimationRunner), false /* hidden */,
-                null /* animationFinishedCallback */);
+                ANIMATION_TYPE_DIMMER, null /* animationFinishedCallback */);
     }
 
     private long getDimDuration(WindowContainer container) {
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
new file mode 100644
index 0000000..b3edc91
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
+import static android.view.WindowManagerPolicyConstants.APPLICATION_LAYER;
+
+import static com.android.internal.util.Preconditions.checkState;
+import static com.android.server.wm.DisplayAreaChildProto.DISPLAY_AREA;
+import static com.android.server.wm.DisplayAreaChildProto.UNKNOWN;
+import static com.android.server.wm.DisplayAreaChildProto.WINDOW;
+import static com.android.server.wm.DisplayAreaProto.CHILDREN;
+import static com.android.server.wm.DisplayAreaProto.NAME;
+import static com.android.server.wm.DisplayAreaProto.WINDOW_CONTAINER;
+import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
+
+import android.graphics.Rect;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.protolog.common.ProtoLog;
+
+import java.util.Comparator;
+import java.util.function.Predicate;
+
+/**
+ * Container for grouping WindowContainer below DisplayContent.
+ *
+ * DisplayAreas are managed by a {@link DisplayAreaPolicy}, and can override configurations and
+ * can be leashed.
+ *
+ * DisplayAreas can contain nested DisplayAreas.
+ *
+ * DisplayAreas come in three flavors, to ensure that windows have the right Z-Order:
+ * - BELOW_TASKS: Can only contain BELOW_TASK DisplayAreas and WindowTokens that go below tasks.
+ * - ABOVE_TASKS: Can only contain ABOVE_TASK DisplayAreas and WindowTokens that go above tasks.
+ * - ANY: Can contain any kind of DisplayArea, and any kind of WindowToken or the Task container.
+ *        Cannot have a sibling that is of type ANY.
+ *
+ * @param <T> type of the children of the DisplayArea.
+ */
+public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> {
+
+    protected final Type mType;
+    private final String mName;
+
+    DisplayArea(WindowManagerService wms, Type type, String name) {
+        super(wms);
+        // TODO(display-area): move this up to ConfigurationContainer
+        mOrientation = SCREEN_ORIENTATION_UNSET;
+        mType = type;
+        mName = name;
+    }
+
+    @Override
+    void onChildPositionChanged(WindowContainer child) {
+        super.onChildPositionChanged(child);
+
+        // Verify that we have proper ordering
+        Type.checkChild(mType, Type.typeOf(child));
+
+        if (child instanceof ActivityStack) {
+            // TODO(display-area): ActivityStacks are type ANY, but are allowed to have siblings.
+            //                     They might need a separate type.
+            return;
+        }
+
+        for (int i = 1; i < getChildCount(); i++) {
+            final WindowContainer top = getChildAt(i - 1);
+            final WindowContainer bottom = getChildAt(i);
+            if (child == top || child == bottom) {
+                Type.checkSiblings(Type.typeOf(top), Type.typeOf(bottom));
+            }
+        }
+    }
+
+    @Override
+    boolean fillsParent() {
+        return true;
+    }
+
+    @Override
+    String getName() {
+        return mName;
+    }
+
+    @Override
+    public final void dumpDebug(ProtoOutputStream proto, long fieldId, int logLevel) {
+        final long token = proto.start(fieldId);
+        super.dumpDebug(proto, WINDOW_CONTAINER, logLevel);
+        proto.write(NAME, mName);
+        for (int i = 0; i < getChildCount(); i++) {
+            final long childToken = proto.start(CHILDREN);
+            final T child = getChildAt(i);
+            if (child instanceof ActivityStack) {
+                // TODO(display-area): Dump stacks & tasks here, instead of in DisplayContent's
+                //  dumpDebug. For now, skip them here to avoid dumping them as UNKNOWN.
+            } else if (child instanceof WindowToken) {
+                ((WindowToken) child).dumpDebug(proto, WINDOW, logLevel);
+            } else if (child instanceof DisplayArea) {
+                child.dumpDebug(proto, DISPLAY_AREA, logLevel);
+            } else {
+                proto.write(UNKNOWN, child.getClass().getSimpleName());
+            }
+            proto.end(childToken);
+        }
+        proto.end(token);
+    }
+
+    /**
+     * DisplayArea that contains WindowTokens, and orders them according to their type.
+     */
+    public static class Tokens extends DisplayArea<WindowToken> {
+        int mLastKeyguardForcedOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
+
+        private final Comparator<WindowToken> mWindowComparator =
+                Comparator.comparingInt(WindowToken::getWindowLayerFromType);
+
+        private final Predicate<WindowState> mGetOrientingWindow = w -> {
+            final WindowManagerPolicy policy = mWmService.mPolicy;
+            if (policy.isKeyguardHostWindow(w.mAttrs)) {
+                if (mWmService.mKeyguardGoingAway) {
+                    return false;
+                }
+                // Consider unoccluding only when all unknown visibilities have been
+                // resolved, as otherwise we just may be starting another occluding activity.
+                final boolean isUnoccluding =
+                        mDisplayContent.mAppTransition.getAppTransition()
+                                == TRANSIT_KEYGUARD_UNOCCLUDE
+                                && mDisplayContent.mUnknownAppVisibilityController.allResolved();
+                // If keyguard is showing, or we're unoccluding, force the keyguard's orientation,
+                // even if SystemUI hasn't updated the attrs yet.
+                if (policy.isKeyguardShowingAndNotOccluded() || isUnoccluding) {
+                    return true;
+                }
+            }
+            final int req = w.mAttrs.screenOrientation;
+            if (req == SCREEN_ORIENTATION_UNSPECIFIED || req == SCREEN_ORIENTATION_BEHIND
+                    || req == SCREEN_ORIENTATION_UNSET) {
+                return false;
+            }
+            return true;
+        };
+
+        Tokens(WindowManagerService wms, Type type, String name) {
+            super(wms, type, name);
+        }
+
+        void addChild(WindowToken token) {
+            addChild(token, mWindowComparator);
+        }
+
+        @Override
+        int getOrientation(int candidate) {
+            // Find a window requesting orientation.
+            final WindowState win = getWindow(mGetOrientingWindow);
+
+            if (win == null) {
+                return candidate;
+            }
+            int req = win.mAttrs.screenOrientation;
+            ProtoLog.v(WM_DEBUG_ORIENTATION, "%s forcing orientation to %d for display id=%d",
+                    win, req, mDisplayContent.getDisplayId());
+            if (mWmService.mPolicy.isKeyguardHostWindow(win.mAttrs)) {
+                // SystemUI controls the Keyguard orientation asynchronously, and mAttrs may be
+                // stale. We record / use the last known override.
+                if (req != SCREEN_ORIENTATION_UNSET && req != SCREEN_ORIENTATION_UNSPECIFIED) {
+                    mLastKeyguardForcedOrientation = req;
+                } else {
+                    req = mLastKeyguardForcedOrientation;
+                }
+            }
+            return req;
+        }
+    }
+
+    /**
+     * Top-most DisplayArea under DisplayContent.
+     */
+    public static class Root extends DisplayArea<DisplayArea> {
+        private final Dimmer mDimmer = new Dimmer(this);
+        private final Rect mTmpDimBoundsRect = new Rect();
+
+        Root(WindowManagerService wms) {
+            super(wms, Type.ANY, "DisplayArea.Root");
+        }
+
+        @Override
+        Dimmer getDimmer() {
+            return mDimmer;
+        }
+
+        @Override
+        void prepareSurfaces() {
+            mDimmer.resetDimStates();
+            super.prepareSurfaces();
+            getBounds(mTmpDimBoundsRect);
+
+            if (mDimmer.updateDims(getPendingTransaction(), mTmpDimBoundsRect)) {
+                scheduleAnimation();
+            }
+        }
+    }
+
+    enum Type {
+        /** Can only contain WindowTokens above the APPLICATION_LAYER. */
+        ABOVE_TASKS,
+        /** Can only contain WindowTokens below the APPLICATION_LAYER. */
+        BELOW_TASKS,
+        /** Can contain anything. */
+        ANY;
+
+        static void checkSiblings(Type bottom, Type top) {
+            checkState(!(bottom == ANY && top == ANY), "ANY cannot be a sibling of ANY");
+            checkState(!(bottom != BELOW_TASKS && top == BELOW_TASKS),
+                    bottom + " must be above BELOW_TASKS");
+            checkState(!(bottom == ABOVE_TASKS && top != ABOVE_TASKS),
+                    top + " must be below ABOVE_TASKS");
+        }
+
+        static void checkChild(Type parent, Type child) {
+            switch (parent) {
+                case ABOVE_TASKS:
+                    checkState(child == ABOVE_TASKS, "ABOVE_TASKS can only contain ABOVE_TASKS");
+                    break;
+                case BELOW_TASKS:
+                    checkState(child == BELOW_TASKS, "BELOW_TASKS can only contain BELOW_TASKS");
+                    break;
+            }
+        }
+
+        static Type typeOf(WindowContainer c) {
+            if (c instanceof DisplayArea) {
+                return ((DisplayArea) c).mType;
+            } else if (c instanceof WindowToken && !(c instanceof ActivityRecord)) {
+                return typeOf((WindowToken) c);
+            } else if (c instanceof ActivityStack) {
+                return ANY;
+            } else {
+                throw new IllegalArgumentException("Unknown container: " + c);
+            }
+        }
+
+        private static Type typeOf(WindowToken c) {
+            return c.getWindowLayerFromType() < APPLICATION_LAYER ? BELOW_TASKS : ABOVE_TASKS;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
new file mode 100644
index 0000000..06e7b48
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+
+import com.android.server.wm.DisplayContent.TaskContainers;
+
+/**
+ * Policy that manages DisplayAreas.
+ */
+public abstract class DisplayAreaPolicy {
+    protected final WindowManagerService mWmService;
+    protected final DisplayContent mContent;
+
+    /**
+     * The root DisplayArea. Attach all DisplayAreas to this area (directly or indirectly).
+     */
+    protected final DisplayArea.Root mRoot;
+
+    /**
+     * The IME container. The IME's windows are automatically added to this container.
+     */
+    protected final DisplayArea<? extends WindowContainer> mImeContainer;
+
+    /**
+     * The Tasks container. Tasks etc. are automatically added to this container.
+     */
+    protected final TaskContainers mTaskContainers;
+
+    DisplayAreaPolicy(WindowManagerService wmService,
+            DisplayContent content, DisplayArea.Root root,
+            DisplayArea<? extends WindowContainer> imeContainer, TaskContainers taskContainers) {
+        mWmService = wmService;
+        mContent = content;
+        mRoot = root;
+        mImeContainer = imeContainer;
+        mTaskContainers = taskContainers;
+    }
+
+    /**
+     * Called to ask the policy to set up the DisplayArea hierarchy. At a minimum this must:
+     *
+     * - attach mImeContainer to mRoot (or one of its descendants)
+     * - attach mTaskStacks to mRoot (or one of its descendants)
+     *
+     * Additionally, this is the right place to set up any other DisplayAreas as desired.
+     */
+    public abstract void attachDisplayAreas();
+
+    /**
+     * Called to ask the policy to attach the given WindowToken to the DisplayArea hierarchy.
+     *
+     * This must attach the token to mRoot (or one of its descendants).
+     */
+    public abstract void addWindow(WindowToken token);
+
+    /**
+     * Default policy that has no special features.
+     */
+    public static class Default extends DisplayAreaPolicy {
+
+        public Default(WindowManagerService wmService, DisplayContent content,
+                DisplayArea.Root root,
+                DisplayArea<? extends WindowContainer> imeContainer,
+                TaskContainers taskContainers) {
+            super(wmService, content, root, imeContainer, taskContainers);
+        }
+
+        private final DisplayArea.Tokens mBelow = new DisplayArea.Tokens(mWmService,
+                DisplayArea.Type.BELOW_TASKS, "BelowTasks");
+        private final DisplayArea<DisplayArea> mAbove = new DisplayArea<>(mWmService,
+                DisplayArea.Type.ABOVE_TASKS, "AboveTasks");
+        private final DisplayArea.Tokens mAboveBelowIme = new DisplayArea.Tokens(mWmService,
+                DisplayArea.Type.ABOVE_TASKS, "AboveTasksBelowIme");
+        private final DisplayArea.Tokens mAboveAboveIme = new DisplayArea.Tokens(mWmService,
+                DisplayArea.Type.ABOVE_TASKS, "AboveTasksAboveIme");
+
+        @Override
+        public void attachDisplayAreas() {
+            mRoot.addChild(mBelow, 0);
+            mRoot.addChild(mTaskContainers, 1);
+            mRoot.addChild(mAbove, 2);
+
+            mAbove.addChild(mAboveBelowIme, 0);
+            mAbove.addChild(mImeContainer, 1);
+            mAbove.addChild(mAboveAboveIme, 2);
+        }
+
+        @Override
+        public void addWindow(WindowToken token) {
+            switch (DisplayArea.Type.typeOf(token)) {
+                case ABOVE_TASKS:
+                    if (token.getWindowLayerFromType()
+                            < mWmService.mPolicy.getWindowLayerFromTypeLw(TYPE_INPUT_METHOD)) {
+                        mAboveBelowIme.addChild(token);
+                    } else {
+                        mAboveAboveIme.addChild(token);
+                    }
+                    break;
+                case BELOW_TASKS:
+                    mBelow.addChild(token);
+                    break;
+                default:
+                    throw new IllegalArgumentException("don't know how to sort " + token);
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 9f4cd88..81af0fe 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -80,7 +80,6 @@
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 import static android.view.WindowManager.TRANSIT_ACTIVITY_OPEN;
-import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
 import static android.view.WindowManager.TRANSIT_TASK_OPEN;
 import static android.view.WindowManager.TRANSIT_TASK_TO_FRONT;
 
@@ -98,9 +97,7 @@
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_STATES;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TASKS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_STACK;
-import static com.android.server.wm.DisplayContentProto.ABOVE_APP_WINDOWS;
 import static com.android.server.wm.DisplayContentProto.APP_TRANSITION;
-import static com.android.server.wm.DisplayContentProto.BELOW_APP_WINDOWS;
 import static com.android.server.wm.DisplayContentProto.CHANGING_APPS;
 import static com.android.server.wm.DisplayContentProto.CLOSING_APPS;
 import static com.android.server.wm.DisplayContentProto.DISPLAY_FRAMES;
@@ -109,9 +106,9 @@
 import static com.android.server.wm.DisplayContentProto.DPI;
 import static com.android.server.wm.DisplayContentProto.FOCUSED_APP;
 import static com.android.server.wm.DisplayContentProto.ID;
-import static com.android.server.wm.DisplayContentProto.IME_WINDOWS;
 import static com.android.server.wm.DisplayContentProto.OPENING_APPS;
 import static com.android.server.wm.DisplayContentProto.OVERLAY_WINDOWS;
+import static com.android.server.wm.DisplayContentProto.ROOT_DISPLAY_AREA;
 import static com.android.server.wm.DisplayContentProto.ROTATION;
 import static com.android.server.wm.DisplayContentProto.SCREEN_ROTATION_ANIMATION;
 import static com.android.server.wm.DisplayContentProto.STACKS;
@@ -296,14 +293,7 @@
     /** The containers below are the only child containers {@link #mWindowContainers} can have. */
     // Contains all window containers that are related to apps (Activities)
     private final TaskContainers mTaskContainers = new TaskContainers(mWmService);
-    // Contains all non-app window containers that should be displayed above the app containers
-    // (e.g. Status bar)
-    private final AboveAppWindowContainers mAboveAppWindowsContainers =
-            new AboveAppWindowContainers("mAboveAppWindowsContainers", mWmService);
-    // Contains all non-app window containers that should be displayed below the app containers
-    // (e.g. Wallpaper).
-    private final NonAppWindowContainers mBelowAppWindowsContainers =
-            new NonAppWindowContainers("mBelowAppWindowsContainers", mWmService);
+
     // Contains all IME window containers. Note that the z-ordering of the IME windows will depend
     // on the IME target. We mainly have this container grouping so we can keep track of all the IME
     // window containers together and move them in-sync if/when needed. We use a subclass of
@@ -311,6 +301,11 @@
     // TODO(display-area): is "no magnification" in the comment still true?
     private final ImeContainer mImeWindowsContainers = new ImeContainer(mWmService);
 
+    private final DisplayArea.Root mRootDisplayArea = new DisplayArea.Root(mWmService);
+
+    private final DisplayAreaPolicy mDisplayAreaPolicy = new DisplayAreaPolicy.Default(
+            mWmService, this, mRootDisplayArea, mImeWindowsContainers, mTaskContainers);
+
     private WindowState mTmpWindow;
     private WindowState mTmpWindow2;
     private boolean mUpdateImeTarget;
@@ -402,14 +397,6 @@
     private int mCurrentOverrideConfigurationChanges;
 
     /**
-     * Last orientation forced by the keyguard. It is applied when keyguard is shown and is not
-     * occluded.
-     *
-     * @see NonAppWindowContainers#getOrientation()
-     */
-    private int mLastKeyguardForcedOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
-
-    /**
      * The maximum aspect ratio (longerSide/shorterSide) that is treated as close-to-square. The
      * orientation requests from apps would be ignored if the display is close-to-square.
      */
@@ -1108,18 +1095,16 @@
             // Add non-app token to container hierarchy on the display. App tokens are added through
             // the parent container managing them (e.g. Tasks).
             switch (token.windowType) {
-                case TYPE_WALLPAPER:
-                    mBelowAppWindowsContainers.addChild(token);
-                    break;
                 case TYPE_INPUT_METHOD:
                 case TYPE_INPUT_METHOD_DIALOG:
                     mImeWindowsContainers.addChild(token);
                     break;
                 case TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY:
+                    // TODO(display-area): Migrate to DisplayArea
                     mOverlayContainers.addChild(token);
                     break;
                 default:
-                    mAboveAppWindowsContainers.addChild(token);
+                    mDisplayAreaPolicy.addWindow(token);
                     break;
             }
         }
@@ -2133,13 +2118,7 @@
                 return getLastOrientation();
             }
         }
-        final int orientation = mAboveAppWindowsContainers.getOrientation();
-        if (orientation != SCREEN_ORIENTATION_UNSET) {
-            return orientation;
-        }
-
-        // Top system windows are not requesting an orientation. Start searching from apps.
-        return mTaskContainers.getOrientation();
+        return mRootDisplayArea.getOrientation();
     }
 
     void updateDisplayInfo() {
@@ -2786,23 +2765,12 @@
         final long token = proto.start(fieldId);
         super.dumpDebug(proto, WINDOW_CONTAINER, logLevel);
         proto.write(ID, mDisplayId);
+        mRootDisplayArea.dumpDebug(proto, ROOT_DISPLAY_AREA, logLevel);
         for (int stackNdx = mTaskContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
             final ActivityStack stack = mTaskContainers.getChildAt(stackNdx);
             stack.dumpDebugInnerStackOnly(proto, STACKS, logLevel);
         }
         mDividerControllerLocked.dumpDebug(proto, DOCKED_STACK_DIVIDER_CONTROLLER);
-        for (int i = mAboveAppWindowsContainers.getChildCount() - 1; i >= 0; --i) {
-            final WindowToken windowToken = mAboveAppWindowsContainers.getChildAt(i);
-            windowToken.dumpDebug(proto, ABOVE_APP_WINDOWS, logLevel);
-        }
-        for (int i = mBelowAppWindowsContainers.getChildCount() - 1; i >= 0; --i) {
-            final WindowToken windowToken = mBelowAppWindowsContainers.getChildAt(i);
-            windowToken.dumpDebug(proto, BELOW_APP_WINDOWS, logLevel);
-        }
-        for (int i = mImeWindowsContainers.getChildCount() - 1; i >= 0; --i) {
-            final WindowToken windowToken = mImeWindowsContainers.getChildAt(i);
-            windowToken.dumpDebug(proto, IME_WINDOWS, logLevel);
-        }
         for (int i = mOverlayContainers.getChildCount() - 1; i >= 0; --i) {
             final WindowToken windowToken = mOverlayContainers.getChildAt(i);
             windowToken.dumpDebug(proto, OVERLAY_WINDOWS, logLevel);
@@ -4216,7 +4184,7 @@
      * Window container class that contains all containers on this display relating to Apps.
      * I.e Activities.
      */
-    private final class TaskContainers extends DisplayChildWindowContainer<ActivityStack> {
+    final class TaskContainers extends DisplayArea<ActivityStack> {
         /**
          * A control placed at the appropriate level for transitions to occur.
          */
@@ -4226,16 +4194,15 @@
 
         /**
          * Given that the split-screen divider does not have an AppWindowToken, it
-         * will have to live inside of a "NonAppWindowContainer", in particular
-         * {@link DisplayContent#mAboveAppWindowsContainers}. However, in visual Z order
+         * will have to live inside of a "NonAppWindowContainer". However, in visual Z order
          * it will need to be interleaved with some of our children, appearing on top of
          * both docked stacks but underneath any assistant stacks.
          *
          * To solve this problem we have this anchor control, which will always exist so
          * we can always assign it the correct value in our {@link #assignChildLayers}.
-         * Likewise since it always exists, {@link AboveAppWindowContainers} can always
+         * Likewise since it always exists, we can always
          * assign the divider a layer relative to it. This way we prevent linking lifecycle
-         * events between the two containers.
+         * events between tasks and the divider window.
          */
         SurfaceControl mSplitScreenDividerAnchor = null;
 
@@ -4246,7 +4213,7 @@
         private ActivityStack mRootSplitScreenPrimaryTask = null;
 
         TaskContainers(WindowManagerService service) {
-            super(service);
+            super(service, Type.ANY, "TaskContainers");
         }
 
         /**
@@ -4763,32 +4730,6 @@
         }
     }
 
-    private final class AboveAppWindowContainers extends NonAppWindowContainers {
-        AboveAppWindowContainers(String name, WindowManagerService service) {
-            super(name, service);
-        }
-
-        @Override
-        void assignChildLayers(SurfaceControl.Transaction t) {
-            boolean needAssignIme = mImeWindowsContainers.getSurfaceControl() != null;
-            for (int j = 0; j < mChildren.size(); ++j) {
-                final WindowToken wt = mChildren.get(j);
-
-                wt.assignLayer(t, j);
-                wt.assignChildLayers(t);
-
-                int layer = mWmService.mPolicy.getWindowLayerFromTypeLw(
-                        wt.windowType, wt.mOwnerCanManageAppTokens);
-
-                if (needAssignIme && layer >= mWmService.mPolicy.getWindowLayerFromTypeLw(
-                        TYPE_INPUT_METHOD_DIALOG, true)) {
-                    mImeWindowsContainers.assignRelativeLayer(t, wt.getSurfaceControl(), -1);
-                    needAssignIme = false;
-                }
-            }
-        }
-    }
-
     private class WindowContainers extends DisplayChildWindowContainer<WindowContainer> {
         private final String mName;
 
@@ -4800,13 +4741,10 @@
         @Override
         void assignChildLayers(SurfaceControl.Transaction t) {
             mImeWindowsContainers.setNeedsLayer();
-            mBelowAppWindowsContainers.assignLayer(t, 0);
-            mTaskContainers.assignLayer(t, 1);
-            mAboveAppWindowsContainers.assignLayer(t, 2);
+
+            mRootDisplayArea.assignLayer(t, 0);
 
             final WindowState imeTarget = mInputMethodTarget;
-            boolean needAssignIme = true;
-
             // In the case where we have an IME target that is not in split-screen mode IME
             // assignment is easy. We just need the IME to go directly above the target. This way
             // children of the target will naturally go above the IME and everyone is happy.
@@ -4839,11 +4777,7 @@
 
             // Above we have assigned layers to our children, now we ask them to assign
             // layers to their children.
-            mBelowAppWindowsContainers.assignChildLayers(t);
-            mTaskContainers.assignChildLayers(t);
-            mAboveAppWindowsContainers.assignChildLayers(t);
-            mImeWindowsContainers.assignRelativeLayer(t, getSurfaceControl(), Integer.MAX_VALUE);
-            mImeWindowsContainers.assignChildLayers(t);
+            mRootDisplayArea.assignChildLayers(t);
         }
 
         @Override
@@ -4852,10 +4786,8 @@
         }
 
         void addChildren() {
-            addChild(mBelowAppWindowsContainers, null);
-            addChild(mTaskContainers, null);
-            addChild(mAboveAppWindowsContainers, null);
-            addChild(mImeWindowsContainers, null);
+            addChild(mRootDisplayArea, 0);
+            mDisplayAreaPolicy.attachDisplayAreas();
         }
 
         @Override
@@ -4882,32 +4814,6 @@
                         < mWmService.mPolicy.getWindowLayerFromTypeLw(token2.windowType,
                         token2.mOwnerCanManageAppTokens) ? -1 : 1;
 
-        private final Predicate<WindowState> mGetOrientingWindow = w -> {
-            final WindowManagerPolicy policy = mWmService.mPolicy;
-            if (policy.isKeyguardHostWindow(w.mAttrs)) {
-                if (mWmService.mKeyguardGoingAway) {
-                    return false;
-                }
-                // Consider unoccluding only when all unknown visibilities have been
-                // resolved, as otherwise we just may be starting another occluding activity.
-                final boolean isUnoccluding =
-                        mDisplayContent.mAppTransition.getAppTransition()
-                                == TRANSIT_KEYGUARD_UNOCCLUDE
-                                && mDisplayContent.mUnknownAppVisibilityController.allResolved();
-                // If keyguard is showing, or we're unoccluding, force the keyguard's orientation,
-                // even if SystemUI hasn't updated the attrs yet.
-                if (policy.isKeyguardShowingAndNotOccluded() || isUnoccluding) {
-                    return true;
-                }
-            }
-            final int req = w.mAttrs.screenOrientation;
-            if (req == SCREEN_ORIENTATION_UNSPECIFIED || req == SCREEN_ORIENTATION_BEHIND
-                    || req == SCREEN_ORIENTATION_UNSET) {
-                return false;
-            }
-            return true;
-        };
-
         private final String mName;
         private final Dimmer mDimmer = new Dimmer(this);
         private final Rect mTmpDimBoundsRect = new Rect();
@@ -4929,26 +4835,9 @@
 
         @Override
         int getOrientation(int candidate) {
-            // Find a window requesting orientation.
-            final WindowState win = getWindow(mGetOrientingWindow);
-
-            if (win != null) {
-                int req = win.mAttrs.screenOrientation;
-                ProtoLog.v(WM_DEBUG_ORIENTATION,
-                        "%s forcing orientation to %d for display id=%d", win, req,
-                        mDisplayId);
-                if (mWmService.mPolicy.isKeyguardHostWindow(win.mAttrs)) {
-                    // SystemUI controls the Keyguard orientation asynchronously, and mAttrs may be
-                    // stale. We record / use the last known override.
-                    if (req != SCREEN_ORIENTATION_UNSET && req != SCREEN_ORIENTATION_UNSPECIFIED) {
-                        mDisplayContent.mLastKeyguardForcedOrientation = req;
-                    } else {
-                        req = mDisplayContent.mLastKeyguardForcedOrientation;
-                    }
-                }
-                return req;
-            }
-            return candidate;
+            ProtoLog.w(WM_DEBUG_ORIENTATION, "NonAppWindowContainer cannot set orientation: %s",
+                    this);
+            return SCREEN_ORIENTATION_UNSET;
         }
 
         @Override
@@ -4983,11 +4872,11 @@
      * - the container doesn't always participate in window traversal, according to
      *   {@link #skipImeWindowsDuringTraversal()}
      */
-    private class ImeContainer extends NonAppWindowContainers {
+    private static class ImeContainer extends DisplayArea.Tokens {
         boolean mNeedsLayer = false;
 
         ImeContainer(WindowManagerService wms) {
-            super("ImeContainer", wms);
+            super(wms, Type.ABOVE_TASKS, "ImeContainer");
         }
 
         public void setNeedsLayer() {
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 2d4211a..d0179ad 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -20,17 +20,31 @@
 import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_HIDDEN;
+import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
+import static android.view.SyncRtSurfaceTransactionApplier.applyParams;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
 
 import android.annotation.Nullable;
 import android.app.StatusBarManager;
 import android.util.IntArray;
+import android.util.SparseArray;
+import android.view.InsetsAnimationControlCallbacks;
+import android.view.InsetsAnimationControlImpl;
+import android.view.InsetsController;
+import android.view.InsetsSourceControl;
 import android.view.InsetsState;
 import android.view.InsetsState.InternalInsetsType;
+import android.view.SurfaceControl;
+import android.view.SyncRtSurfaceTransactionApplier;
 import android.view.ViewRootImpl;
+import android.view.WindowInsetsAnimationCallback;
+import android.view.WindowInsetsAnimationControlListener;
+
+import com.android.server.DisplayThread;
 
 /**
  * Policy that implements who gets control over the windows generating insets.
@@ -46,6 +60,8 @@
     private WindowState mFocusedWin;
     private BarWindow mStatusBar = new BarWindow(StatusBarManager.WINDOW_STATUS_BAR);
     private BarWindow mNavBar = new BarWindow(StatusBarManager.WINDOW_NAVIGATION_BAR);
+    private boolean mAnimatingShown;
+    private final float[] mTmpFloat9 = new float[9];
 
     InsetsPolicy(InsetsStateController stateController, DisplayContent displayContent) {
         mStateController = stateController;
@@ -91,11 +107,14 @@
             changed = true;
         }
         if (changed) {
-            updateBarControlTarget(mFocusedWin);
-            mPolicy.getStatusBarManagerInternal().showTransient(mDisplayContent.getDisplayId(),
-                    mShowingTransientTypes.toArray());
-            mStateController.notifyInsetsChanged();
-            // TODO(b/118118435): Animation
+            startAnimation(mShowingTransientTypes, true, () -> {
+                synchronized (mDisplayContent.mWmService.mGlobalLock) {
+                    mPolicy.getStatusBarManagerInternal().showTransient(
+                            mDisplayContent.getDisplayId(),
+                            mShowingTransientTypes.toArray());
+                    mStateController.notifyInsetsChanged();
+                }
+            });
         }
     }
 
@@ -103,11 +122,13 @@
         if (mShowingTransientTypes.size() == 0) {
             return;
         }
-
-        // TODO(b/118118435): Animation
-        mShowingTransientTypes.clear();
-        updateBarControlTarget(mFocusedWin);
-        mStateController.notifyInsetsChanged();
+        startAnimation(mShowingTransientTypes, false, () -> {
+            synchronized (mDisplayContent.mWmService.mGlobalLock) {
+                mShowingTransientTypes.clear();
+                mStateController.notifyInsetsChanged();
+                updateBarControlTarget(mFocusedWin);
+            }
+        });
     }
 
     boolean isTransient(@InternalInsetsType int type) {
@@ -247,6 +268,29 @@
         return isDockedStackVisible || isFreeformStackVisible || isResizing;
     }
 
+    private void startAnimation(IntArray internalTypes, boolean show, Runnable callback) {
+        int typesReady = 0;
+        final SparseArray<InsetsSourceControl> controls = new SparseArray<>();
+        updateBarControlTarget(mFocusedWin);
+        for (int i = internalTypes.size() - 1; i >= 0; i--) {
+            InsetsSourceProvider provider =
+                    mStateController.getSourceProvider(internalTypes.get(i));
+            if (provider == null) continue;
+            InsetsSourceControl control = provider.getControl(provider.getControlTarget());
+            if (control == null || control.getLeash() == null) continue;
+            typesReady |= InsetsState.toPublicType(internalTypes.get(i));
+            controls.put(control.getType(), control);
+        }
+        controlAnimationUnchecked(typesReady, controls, show, callback);
+    }
+
+    private void controlAnimationUnchecked(int typesReady,
+            SparseArray<InsetsSourceControl> controls, boolean show, Runnable callback) {
+        InsetsPolicyAnimationControlListener listener =
+                new InsetsPolicyAnimationControlListener(show, callback);
+        listener.mControlCallbacks.controlAnimationUnchecked(typesReady, controls, show);
+    }
+
     private class BarWindow {
 
         private final int mId;
@@ -267,7 +311,103 @@
         }
     }
 
-    // TODO(b/118118435): Implement animations for it (with SurfaceAnimator)
+    private class InsetsPolicyAnimationControlListener extends
+            InsetsController.InternalAnimationControlListener {
+        Runnable mFinishCallback;
+        InsetsPolicyAnimationControlCallbacks mControlCallbacks;
+
+        InsetsPolicyAnimationControlListener(boolean show, Runnable finishCallback) {
+            super(show);
+            mFinishCallback = finishCallback;
+            mControlCallbacks = new InsetsPolicyAnimationControlCallbacks(this);
+        }
+
+        @Override
+        protected void onAnimationFinish() {
+            super.onAnimationFinish();
+            mControlCallbacks.mAnimationControl.finish(mAnimatingShown);
+            DisplayThread.getHandler().post(mFinishCallback);
+        }
+
+        private class InsetsPolicyAnimationControlCallbacks implements
+                InsetsAnimationControlCallbacks {
+            private InsetsAnimationControlImpl mAnimationControl = null;
+            private InsetsPolicyAnimationControlListener mListener;
+
+            InsetsPolicyAnimationControlCallbacks(InsetsPolicyAnimationControlListener listener) {
+                super();
+                mListener = listener;
+            }
+
+            private void controlAnimationUnchecked(int typesReady,
+                    SparseArray<InsetsSourceControl> controls, boolean show) {
+                if (typesReady == 0) {
+                    // nothing to animate.
+                    return;
+                }
+                mAnimatingShown = show;
+
+                mAnimationControl = new InsetsAnimationControlImpl(controls,
+                        mFocusedWin.getDisplayContent().getBounds(), getState(),
+                        mListener, typesReady, this, mListener.getDurationMs(),
+                        InsetsController.INTERPOLATOR, true,
+                        show ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN
+                                : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN);
+            }
+
+            /** Called on SurfaceAnimationThread lock without global WM lock held. */
+            @Override
+            public void scheduleApplyChangeInsets() {
+                InsetsState state = getState();
+                if (mAnimationControl.applyChangeInsets(state)) {
+                    mAnimationControl.finish(mAnimatingShown);
+                }
+            }
+
+            @Override
+            public void notifyFinished(InsetsAnimationControlImpl controller, boolean shown) {
+                // Nothing's needed here. Finish steps is handled in the listener
+                // onAnimationFinished callback.
+            }
+
+            /**
+             * This method will return a state with fullscreen frame override. No need to make copy
+             * after getting state from this method.
+             * @return The client insets state with full display frame override.
+             */
+            private InsetsState getState() {
+                // To animate the transient animation correctly, we need to let the state hold
+                // the full display frame.
+                InsetsState overrideState = new InsetsState(mFocusedWin.getRequestedInsetsState(),
+                        true);
+                overrideState.setDisplayFrame(mFocusedWin.getDisplayContent().getBounds());
+                return overrideState;
+            }
+
+            /** Called on SurfaceAnimationThread lock without global WM lock held. */
+            @Override
+            public void applySurfaceParams(
+                    final SyncRtSurfaceTransactionApplier.SurfaceParams... params) {
+                SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+                for (int i = params.length - 1; i >= 0; i--) {
+                    SyncRtSurfaceTransactionApplier.SurfaceParams surfaceParams = params[i];
+                    applyParams(t, surfaceParams, mTmpFloat9);
+                }
+                t.apply();
+            }
+
+            /** Called on SurfaceAnimationThread lock without global WM lock held. */
+            @Override
+            public void startAnimation(InsetsAnimationControlImpl controller,
+                    WindowInsetsAnimationControlListener listener, int types,
+                    WindowInsetsAnimationCallback.InsetsAnimation animation,
+                    WindowInsetsAnimationCallback.AnimationBounds bounds,
+                    int layoutDuringAnimation) {
+                SurfaceAnimationThread.getHandler().post(() -> listener.onReady(controller, types));
+            }
+        }
+    }
+
     private class TransientControlTarget implements InsetsControlTarget {
 
         @Override
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index db43480..e6c1969 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -24,6 +24,7 @@
 import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE;
 import static android.view.ViewRootImpl.sNewInsetsMode;
 
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_INSETS_CONTROL;
 import static com.android.server.wm.WindowManagerService.H.LAYOUT_AND_ASSIGN_WINDOW_LAYERS_IF_NEEDED;
 
 import android.annotation.NonNull;
@@ -34,11 +35,11 @@
 import android.view.InsetsSource;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
-import android.view.InsetsState.InternalInsetsType;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
 
 import com.android.internal.util.function.TriConsumer;
+import com.android.server.wm.SurfaceAnimator.AnimationType;
 import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
 
 import java.io.PrintWriter;
@@ -243,7 +244,8 @@
         mAdapter = new ControlAdapter();
         setClientVisible(InsetsState.getDefaultVisibility(mSource.getType()));
         final Transaction t = mDisplayContent.getPendingTransaction();
-        mWin.startAnimation(t, mAdapter, !mClientVisible /* hidden */);
+        mWin.startAnimation(t, mAdapter, !mClientVisible /* hidden */,
+                ANIMATION_TYPE_INSETS_CONTROL, null /* animationFinishedCallback */);
         final SurfaceControl leash = mAdapter.mCapturedLeash;
         final long frameNumber = mFinishSeamlessRotateFrameNumber;
         mFinishSeamlessRotateFrameNumber = -1;
@@ -348,7 +350,7 @@
 
         @Override
         public void startAnimation(SurfaceControl animationLeash, Transaction t,
-                OnAnimationFinishedCallback finishCallback) {
+                @AnimationType int type, OnAnimationFinishedCallback finishCallback) {
             // TODO(b/118118435): We can remove the type check when implementing the transient bar
             //                    animation.
             if (mSource.getType() == ITYPE_IME) {
diff --git a/services/core/java/com/android/server/wm/LocalAnimationAdapter.java b/services/core/java/com/android/server/wm/LocalAnimationAdapter.java
index 5892239..7c1a616 100644
--- a/services/core/java/com/android/server/wm/LocalAnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/LocalAnimationAdapter.java
@@ -24,6 +24,7 @@
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
 
+import com.android.server.wm.SurfaceAnimator.AnimationType;
 import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
 
 import java.io.PrintWriter;
@@ -50,9 +51,9 @@
 
     @Override
     public void startAnimation(SurfaceControl animationLeash, Transaction t,
-            OnAnimationFinishedCallback finishCallback) {
+            @AnimationType int type, OnAnimationFinishedCallback finishCallback) {
         mAnimator.startAnimation(mSpec, animationLeash, t,
-                () -> finishCallback.onAnimationFinished(this));
+                () -> finishCallback.onAnimationFinished(type, this));
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index da9d074..944e0ae 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -30,6 +30,7 @@
 import static com.android.server.wm.BoundsAnimationController.FADE_IN;
 import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS;
 import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
 import static com.android.server.wm.WindowManagerInternal.AppTransitionListener;
 
 import android.annotation.IntDef;
@@ -61,6 +62,7 @@
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.protolog.common.ProtoLog;
 import com.android.server.statusbar.StatusBarManagerInternal;
+import com.android.server.wm.SurfaceAnimator.AnimationType;
 import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
 import com.android.server.wm.utils.InsetUtils;
 
@@ -432,7 +434,8 @@
         ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "addAnimation(%s)", task.getName());
         final TaskAnimationAdapter taskAdapter = new TaskAnimationAdapter(task,
                 isRecentTaskInvisible);
-        task.startAnimation(task.getPendingTransaction(), taskAdapter, false /* hidden */);
+        task.startAnimation(task.getPendingTransaction(), taskAdapter, false /* hidden */,
+                ANIMATION_TYPE_RECENTS);
         task.commitPendingTransaction();
         mPendingAnimations.add(taskAdapter);
         return taskAdapter;
@@ -443,14 +446,16 @@
         ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
                 "removeAnimation(%d)", taskAdapter.mTask.mTaskId);
         taskAdapter.mTask.setCanAffectSystemUiFlags(true);
-        taskAdapter.mCapturedFinishCallback.onAnimationFinished(taskAdapter);
+        taskAdapter.mCapturedFinishCallback.onAnimationFinished(taskAdapter.mLastAnimationType,
+                taskAdapter);
         mPendingAnimations.remove(taskAdapter);
     }
 
     @VisibleForTesting
     void removeWallpaperAnimation(WallpaperAnimationAdapter wallpaperAdapter) {
         ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "removeWallpaperAnimation()");
-        wallpaperAdapter.getLeashFinishedCallback().onAnimationFinished(wallpaperAdapter);
+        wallpaperAdapter.getLeashFinishedCallback().onAnimationFinished(
+                wallpaperAdapter.getLastAnimationType(), wallpaperAdapter);
         mPendingWallpaperAnimations.remove(wallpaperAdapter);
     }
 
@@ -638,7 +643,7 @@
                         taskSnapshot.getColorSpace(), false /* containsSecureLayers */));
         mRecentScreenshotAnimator = new SurfaceAnimator(
                 animatable,
-                () -> {
+                (type, anim) -> {
                     ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "mRecentScreenshotAnimator finish");
                     mCallbacks.onAnimationFinished(reorderMode, false /* sendUserLeaveHint */);
                 }, mService);
@@ -827,6 +832,7 @@
         private final Task mTask;
         private SurfaceControl mCapturedLeash;
         private OnAnimationFinishedCallback mCapturedFinishCallback;
+        private @AnimationType int mLastAnimationType;
         private final boolean mIsRecentTaskInvisible;
         private RemoteAnimationTarget mTarget;
         private final Point mPosition = new Point();
@@ -868,7 +874,7 @@
 
         @Override
         public void startAnimation(SurfaceControl animationLeash, Transaction t,
-                OnAnimationFinishedCallback finishCallback) {
+                @AnimationType int type, OnAnimationFinishedCallback finishCallback) {
             // Restore z-layering, position and stack crop until client has a chance to modify it.
             t.setLayer(animationLeash, mTask.getPrefixOrderIndex());
             t.setPosition(animationLeash, mPosition.x, mPosition.y);
@@ -877,6 +883,7 @@
             t.setWindowCrop(animationLeash, mTmpRect);
             mCapturedLeash = animationLeash;
             mCapturedFinishCallback = finishCallback;
+            mLastAnimationType = type;
         }
 
         @Override
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index 6f7eeab..d2dbab8 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -40,6 +40,7 @@
 import com.android.internal.util.FastPrintWriter;
 import com.android.server.protolog.ProtoLogImpl;
 import com.android.server.protolog.common.ProtoLog;
+import com.android.server.wm.SurfaceAnimator.AnimationType;
 import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
 
 import java.io.PrintWriter;
@@ -180,12 +181,14 @@
                 if (wrappers.mAdapter != null
                         && wrappers.mAdapter.mCapturedFinishCallback != null) {
                     wrappers.mAdapter.mCapturedFinishCallback
-                            .onAnimationFinished(wrappers.mAdapter);
+                            .onAnimationFinished(wrappers.mAdapter.mAnimationType,
+                                    wrappers.mAdapter);
                 }
                 if (wrappers.mThumbnailAdapter != null
                         && wrappers.mThumbnailAdapter.mCapturedFinishCallback != null) {
                     wrappers.mThumbnailAdapter.mCapturedFinishCallback
-                            .onAnimationFinished(wrappers.mThumbnailAdapter);
+                            .onAnimationFinished(wrappers.mAdapter.mAnimationType,
+                                    wrappers.mThumbnailAdapter);
                 }
                 mPendingAnimations.remove(i);
             }
@@ -221,11 +224,13 @@
                     final RemoteAnimationRecord adapters = mPendingAnimations.get(i);
                     if (adapters.mAdapter != null) {
                         adapters.mAdapter.mCapturedFinishCallback
-                                .onAnimationFinished(adapters.mAdapter);
+                                .onAnimationFinished(adapters.mAdapter.mAnimationType,
+                                        adapters.mAdapter);
                     }
                     if (adapters.mThumbnailAdapter != null) {
                         adapters.mThumbnailAdapter.mCapturedFinishCallback
-                                .onAnimationFinished(adapters.mThumbnailAdapter);
+                                .onAnimationFinished(adapters.mAdapter.mAnimationType,
+                                        adapters.mThumbnailAdapter);
                     }
                     mPendingAnimations.remove(i);
                     ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "\tcontainer=%s",
@@ -234,7 +239,8 @@
 
                 for (int i = mPendingWallpaperAnimations.size() - 1; i >= 0; i--) {
                     final WallpaperAnimationAdapter adapter = mPendingWallpaperAnimations.get(i);
-                    adapter.getLeashFinishedCallback().onAnimationFinished(adapter);
+                    adapter.getLeashFinishedCallback().onAnimationFinished(
+                            adapter.getLastAnimationType(), adapter);
                     mPendingWallpaperAnimations.remove(i);
                     ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "\twallpaper=%s", adapter.getToken());
                 }
@@ -393,6 +399,7 @@
         private final RemoteAnimationRecord mRecord;
         SurfaceControl mCapturedLeash;
         private OnAnimationFinishedCallback mCapturedFinishCallback;
+        private @AnimationType int mAnimationType;
         final Point mPosition = new Point();
         final Rect mStackBounds = new Rect();
 
@@ -410,7 +417,7 @@
 
         @Override
         public void startAnimation(SurfaceControl animationLeash, Transaction t,
-                OnAnimationFinishedCallback finishCallback) {
+                @AnimationType int type, OnAnimationFinishedCallback finishCallback) {
             ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "startAnimation");
 
             // Restore z-layering, position and stack crop until client has a chance to modify it.
@@ -425,6 +432,7 @@
             }
             mCapturedLeash = animationLeash;
             mCapturedFinishCallback = finishCallback;
+            mAnimationType = type;
         }
 
         @Override
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index e7aca89..f6cdac5 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -24,6 +24,7 @@
 import static com.android.server.wm.RotationAnimationSpecProto.START_LUMA;
 import static com.android.server.wm.ScreenRotationAnimationProto.ANIMATION_RUNNING;
 import static com.android.server.wm.ScreenRotationAnimationProto.STARTED;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_SCREEN_ROTATION;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.WindowManagerService.TYPE_LAYER_MULTIPLIER;
@@ -48,6 +49,8 @@
 
 import com.android.internal.R;
 import com.android.server.protolog.common.ProtoLog;
+import com.android.server.wm.SurfaceAnimator.AnimationType;
+import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
 import com.android.server.wm.utils.RotationAnimationUtils;
 
 import java.io.PrintWriter;
@@ -670,33 +673,35 @@
          * Start an animation defined by animationSpec on a new {@link SurfaceAnimator}.
          *
          * @param animatable The animatable used for the animation.
-         * @param animationSpec                The spec of the animation.
-         * @param animationFinishedCallback    Callback passed to the {@link SurfaceAnimator} and
-         *                                    called when the animation finishes.
+         * @param animationSpec The spec of the animation.
+         * @param animationFinishedCallback Callback passed to the {@link SurfaceAnimator}
+         *                                    and called when the animation finishes.
          * @return The newly created {@link SurfaceAnimator} that as been started.
          */
         private SurfaceAnimator startAnimation(
                 SurfaceAnimator.Animatable animatable,
                 LocalAnimationAdapter.AnimationSpec animationSpec,
-                Runnable animationFinishedCallback) {
+                OnAnimationFinishedCallback animationFinishedCallback) {
             SurfaceAnimator animator = new SurfaceAnimator(
                     animatable, animationFinishedCallback, mService);
 
             LocalAnimationAdapter localAnimationAdapter = new LocalAnimationAdapter(
                     animationSpec, mService.mSurfaceAnimationRunner);
             animator.startAnimation(mDisplayContent.getPendingTransaction(),
-                    localAnimationAdapter, false);
+                    localAnimationAdapter, false, ANIMATION_TYPE_SCREEN_ROTATION);
             return animator;
         }
 
-        private void onAnimationEnd() {
+        private void onAnimationEnd(@AnimationType int type, AnimationAdapter anim) {
             synchronized (mService.mGlobalLock) {
                 if (isAnimating()) {
                     ProtoLog.v(WM_DEBUG_ORIENTATION,
-                            "ScreenRotation sill animating: mDisplayAnimator: %s\n"
-                                    + "mEnterBlackFrameAnimator: "
-                                    + "%s\nmRotateScreenAnimator: %s\n"
+                            "ScreenRotation still animating: type: %d\n"
+                                    + "mDisplayAnimator: %s\n"
+                                    + "mEnterBlackFrameAnimator: %s\n"
+                                    + "mRotateScreenAnimator: %s\n"
                                     + "mScreenshotRotationAnimator: %s",
+                            type,
                             mDisplayAnimator != null
                                     ? mDisplayAnimator.isAnimating() : null,
                             mEnterBlackFrameAnimator != null
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index de7f9e4..ab1f34a 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -238,16 +238,12 @@
 
     @Override
     public void setInTouchMode(boolean mode) {
-        synchronized (mService.mGlobalLock) {
-            mService.mInTouchMode = mode;
-        }
+        mService.setInTouchMode(mode);
     }
 
     @Override
     public boolean getInTouchMode() {
-        synchronized (mService.mGlobalLock) {
-            return mService.mInTouchMode;
-        }
+        return mService.getInTouchMode();
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index cb1676f..7164cd8 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -23,6 +23,7 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.util.Slog;
@@ -33,6 +34,8 @@
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 
 /**
  * A class that can run animations on objects that have a set of child surfaces. We do this by
@@ -47,6 +50,7 @@
     private static final String TAG = TAG_WITH_CLASS_NAME ? "SurfaceAnimator" : TAG_WM;
     private final WindowManagerService mService;
     private AnimationAdapter mAnimation;
+    private @AnimationType int mAnimationType;
 
     @VisibleForTesting
     SurfaceControl mLeash;
@@ -55,30 +59,32 @@
     private final OnAnimationFinishedCallback mInnerAnimationFinishedCallback;
     @VisibleForTesting
     @Nullable
-    final Runnable mStaticAnimationFinishedCallback;
+    final OnAnimationFinishedCallback mStaticAnimationFinishedCallback;
     @Nullable
-    private Runnable mAnimationFinishedCallback;
+    private OnAnimationFinishedCallback mAnimationFinishedCallback;
     private boolean mAnimationStartDelayed;
 
     /**
      * @param animatable The object to animate.
-     * @param animationFinishedCallback Callback to invoke when an animation has finished running.
+     * @param staticAnimationFinishedCallback Callback to invoke when an animation has finished
+     *                                         running.
      */
-    SurfaceAnimator(Animatable animatable, @Nullable Runnable animationFinishedCallback,
+    SurfaceAnimator(Animatable animatable,
+            @Nullable OnAnimationFinishedCallback staticAnimationFinishedCallback,
             WindowManagerService service) {
         mAnimatable = animatable;
         mService = service;
-        mStaticAnimationFinishedCallback = animationFinishedCallback;
-        mInnerAnimationFinishedCallback = getFinishedCallback(animationFinishedCallback);
+        mStaticAnimationFinishedCallback = staticAnimationFinishedCallback;
+        mInnerAnimationFinishedCallback = getFinishedCallback(staticAnimationFinishedCallback);
     }
 
     private OnAnimationFinishedCallback getFinishedCallback(
-            @Nullable Runnable animationFinishedCallback) {
-        return anim -> {
+            @Nullable OnAnimationFinishedCallback staticAnimationFinishedCallback) {
+        return (type, anim) -> {
             synchronized (mService.mGlobalLock) {
                 final SurfaceAnimator target = mService.mAnimationTransferMap.remove(anim);
                 if (target != null) {
-                    target.mInnerAnimationFinishedCallback.onAnimationFinished(anim);
+                    target.mInnerAnimationFinishedCallback.onAnimationFinished(type, anim);
                     return;
                 }
 
@@ -91,13 +97,14 @@
                     if (anim != mAnimation) {
                         return;
                     }
-                    final Runnable animationFinishCallback = mAnimationFinishedCallback;
+                    final OnAnimationFinishedCallback animationFinishCallback =
+                            mAnimationFinishedCallback;
                     reset(mAnimatable.getPendingTransaction(), true /* destroyLeash */);
-                    if (animationFinishedCallback != null) {
-                        animationFinishedCallback.run();
+                    if (staticAnimationFinishedCallback != null) {
+                        staticAnimationFinishedCallback.onAnimationFinished(type, anim);
                     }
                     if (animationFinishCallback != null) {
-                        animationFinishCallback.run();
+                        animationFinishCallback.onAnimationFinished(type, anim);
                     }
                 };
                 if (!mAnimatable.shouldDeferAnimationFinish(resetAndInvokeFinish)) {
@@ -120,9 +127,11 @@
      * @param animationFinishedCallback The callback being triggered when the animation finishes.
      */
     void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
-            @Nullable Runnable animationFinishedCallback) {
+            @AnimationType int type,
+            @Nullable OnAnimationFinishedCallback animationFinishedCallback) {
         cancelAnimation(t, true /* restarting */, true /* forwardCancel */);
         mAnimation = anim;
+        mAnimationType = type;
         mAnimationFinishedCallback = animationFinishedCallback;
         final SurfaceControl surface = mAnimatable.getSurfaceControl();
         if (surface == null) {
@@ -137,11 +146,12 @@
             if (DEBUG_ANIM) Slog.i(TAG, "Animation start delayed");
             return;
         }
-        mAnimation.startAnimation(mLeash, t, mInnerAnimationFinishedCallback);
+        mAnimation.startAnimation(mLeash, t, type, mInnerAnimationFinishedCallback);
     }
 
-    void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden) {
-        startAnimation(t, anim, hidden, null /* animationFinishedCallback */);
+    void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
+            @AnimationType int type) {
+        startAnimation(t, anim, hidden, type, null /* animationFinishedCallback */);
     }
 
     /**
@@ -165,7 +175,7 @@
         mAnimationStartDelayed = false;
         if (delayed && mAnimation != null) {
             mAnimation.startAnimation(mLeash, mAnimatable.getPendingTransaction(),
-                    mInnerAnimationFinishedCallback);
+                    mAnimationType, mInnerAnimationFinishedCallback);
             mAnimatable.commitPendingTransaction();
         }
     }
@@ -245,6 +255,7 @@
         cancelAnimation(t, true /* restarting */, true /* forwardCancel */);
         mLeash = from.mLeash;
         mAnimation = from.mAnimation;
+        mAnimationType = from.mAnimationType;
         mAnimationFinishedCallback = from.mAnimationFinishedCallback;
 
         // Cancel source animation, but don't let animation runner cancel the animation.
@@ -272,7 +283,8 @@
         if (DEBUG_ANIM) Slog.i(TAG, "Cancelling animation restarting=" + restarting);
         final SurfaceControl leash = mLeash;
         final AnimationAdapter animation = mAnimation;
-        final Runnable animationFinishedCallback = mAnimationFinishedCallback;
+        final @AnimationType int animationType = mAnimationType;
+        final OnAnimationFinishedCallback animationFinishedCallback = mAnimationFinishedCallback;
         reset(t, false);
         if (animation != null) {
             if (!mAnimationStartDelayed && forwardCancel) {
@@ -280,10 +292,10 @@
             }
             if (!restarting) {
                 if (mStaticAnimationFinishedCallback != null) {
-                    mStaticAnimationFinishedCallback.run();
+                    mStaticAnimationFinishedCallback.onAnimationFinished(animationType, animation);
                 }
                 if (animationFinishedCallback != null) {
-                    animationFinishedCallback.run();
+                    animationFinishedCallback.onAnimationFinished(animationType, animation);
                 }
             }
         }
@@ -325,6 +337,7 @@
         mLeash = null;
         mAnimation = null;
         mAnimationFinishedCallback = null;
+        mAnimationType = ANIMATION_TYPE_NONE;
 
         if (reparent) {
             // Make sure to inform the animatable after the surface was reparented (or reparent
@@ -392,12 +405,72 @@
         }
     }
 
+
+    /**
+     * No animation is specified.
+     * @hide
+     */
+    static final int ANIMATION_TYPE_NONE = 0;
+
+    /**
+     * Animation for an app transition.
+     * @hide
+     */
+    static final int ANIMATION_TYPE_APP_TRANSITION = 1;
+
+    /**
+     * Animation for screen rotation.
+     * @hide
+     */
+    static final int ANIMATION_TYPE_SCREEN_ROTATION = 2;
+
+    /**
+     * Animation for dimming.
+     * @hide
+     */
+    static final int ANIMATION_TYPE_DIMMER = 3;
+
+    /**
+     * Animation for recent apps.
+     * @hide
+     */
+    static final int ANIMATION_TYPE_RECENTS = 4;
+
+    /**
+     * Animation for a {@link WindowState} without animating the activity.
+     * @hide
+     */
+    static final int ANIMATION_TYPE_WINDOW_ANIMATION = 5;
+
+    /**
+     * Animation to control insets. This is actually not an animation, but is used to give the
+     * client a leash over the system window causing insets.
+     * @hide
+     */
+    static final int ANIMATION_TYPE_INSETS_CONTROL = 6;
+
+    /**
+     * The type of the animation.
+     * @hide
+     */
+    @IntDef(flag = true, prefix = { "ANIMATION_TYPE_" }, value = {
+            ANIMATION_TYPE_NONE,
+            ANIMATION_TYPE_APP_TRANSITION,
+            ANIMATION_TYPE_SCREEN_ROTATION,
+            ANIMATION_TYPE_DIMMER,
+            ANIMATION_TYPE_RECENTS,
+            ANIMATION_TYPE_WINDOW_ANIMATION,
+            ANIMATION_TYPE_INSETS_CONTROL
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface AnimationType {}
+
     /**
      * Callback to be passed into {@link AnimationAdapter#startAnimation} to be invoked by the
      * component that is running the animation when the animation is finished.
      */
     interface OnAnimationFinishedCallback {
-        void onAnimationFinished(AnimationAdapter anim);
+        void onAnimationFinished(@AnimationType int type, AnimationAdapter anim);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 271f559..28dc2a4 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3215,7 +3215,9 @@
         info.displayId = getDisplayId();
         info.isRunning = getTopNonFinishingActivity() != null;
         final Intent baseIntent = getBaseIntent();
-        info.baseIntent = baseIntent == null ? new Intent() : baseIntent;
+        // Make a copy of base intent because this is like a snapshot info.
+        // Besides, {@link RecentTasks#getRecentTasksImpl} may modify it.
+        info.baseIntent = baseIntent == null ? new Intent() : new Intent(baseIntent);
         info.baseActivity = mReuseActivitiesReport.base != null
                 ? mReuseActivitiesReport.base.intent.getComponent()
                 : null;
diff --git a/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java b/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java
index 801e521..bd70599 100644
--- a/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java
@@ -18,6 +18,7 @@
 import static com.android.server.wm.AnimationAdapterProto.REMOTE;
 import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_REMOTE_ANIMATIONS;
 import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
 
 import android.graphics.Point;
 import android.os.SystemClock;
@@ -26,6 +27,7 @@
 import android.view.SurfaceControl;
 
 import com.android.server.protolog.common.ProtoLog;
+import com.android.server.wm.SurfaceAnimator.AnimationType;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -40,6 +42,7 @@
     private final WallpaperWindowToken mWallpaperToken;
     private SurfaceControl mCapturedLeash;
     private SurfaceAnimator.OnAnimationFinishedCallback mCapturedLeashFinishCallback;
+    private @AnimationType int mLastAnimationType;
 
     private long mDurationHint;
     private long mStatusBarTransitionDelay;
@@ -77,7 +80,7 @@
                     wallpaperWindow, durationHint, statusBarTransitionDelay,
                     animationCanceledRunnable);
             wallpaperWindow.startAnimation(wallpaperWindow.getPendingTransaction(),
-                    wallpaperAdapter, false /* hidden */);
+                    wallpaperAdapter, false /* hidden */, ANIMATION_TYPE_WINDOW_ANIMATION);
             targets.add(wallpaperAdapter.createRemoteAnimationTarget());
             adaptersOut.add(wallpaperAdapter);
         });
@@ -110,6 +113,13 @@
     }
 
     /**
+     * @return the type of animation.
+     */
+    @AnimationType int getLastAnimationType() {
+        return mLastAnimationType;
+    }
+
+    /**
      * @return the wallpaper window
      */
     WallpaperWindowToken getToken() {
@@ -124,13 +134,14 @@
 
     @Override
     public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t,
-            SurfaceAnimator.OnAnimationFinishedCallback finishCallback) {
+            @AnimationType int type, SurfaceAnimator.OnAnimationFinishedCallback finishCallback) {
         ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "startAnimation");
 
         // Restore z-layering until client has a chance to modify it.
         t.setLayer(animationLeash, mWallpaperToken.getPrefixOrderIndex());
         mCapturedLeash = animationLeash;
         mCapturedLeashFinishCallback = finishCallback;
+        mLastAnimationType = type;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index d828ca6..8672315 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -31,6 +31,7 @@
 import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
 import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM;
 import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
 import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
 import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
@@ -74,6 +75,8 @@
 import com.android.internal.util.ToBooleanFunction;
 import com.android.server.protolog.common.ProtoLog;
 import com.android.server.wm.SurfaceAnimator.Animatable;
+import com.android.server.wm.SurfaceAnimator.AnimationType;
+import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
 
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
@@ -1851,19 +1854,24 @@
      * @param anim The animation to run.
      * @param hidden Whether our container is currently hidden. TODO This should use isVisible at
      *               some point but the meaning is too weird to work for all containers.
+     * @param type The type of animation defined as {@link AnimationType}.
      * @param animationFinishedCallback The callback being triggered when the animation finishes.
      */
     void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
-            @Nullable Runnable animationFinishedCallback) {
-        if (DEBUG_ANIM) Slog.v(TAG, "Starting animation on " + this + ": " + anim);
+            @AnimationType int type,
+            @Nullable OnAnimationFinishedCallback animationFinishedCallback) {
+        if (DEBUG_ANIM) {
+            Slog.v(TAG, "Starting animation on " + this + ": type=" + type + ", anim=" + anim);
+        }
 
         // TODO: This should use isVisible() but because isVisible has a really weird meaning at
         // the moment this doesn't work for all animatable window containers.
-        mSurfaceAnimator.startAnimation(t, anim, hidden, animationFinishedCallback);
+        mSurfaceAnimator.startAnimation(t, anim, hidden, type, animationFinishedCallback);
     }
 
-    void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden) {
-        startAnimation(t, anim, hidden, null /* animationFinishedCallback */);
+    void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
+            @AnimationType int type) {
+        startAnimation(t, anim, hidden, type, null /* animationFinishedCallback */);
     }
 
     void transferAnimation(WindowContainer from) {
@@ -1916,7 +1924,8 @@
      * @see #getAnimationAdapter
      */
     boolean applyAnimation(WindowManager.LayoutParams lp, int transit, boolean enter,
-            boolean isVoiceInteraction, @Nullable Runnable animationFinishedCallback) {
+            boolean isVoiceInteraction,
+            @Nullable OnAnimationFinishedCallback animationFinishedCallback) {
         if (mWmService.mDisableTransitionAnimation) {
             ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
                     "applyAnimation: transition animation is disabled or skipped. "
@@ -1937,7 +1946,7 @@
                 AnimationAdapter thumbnailAdapter = adapters.second;
                 if (adapter != null) {
                     startAnimation(getPendingTransaction(), adapter, !isVisible(),
-                            animationFinishedCallback);
+                            ANIMATION_TYPE_APP_TRANSITION, animationFinishedCallback);
                     if (adapter.getShowWallpaper()) {
                         getDisplayContent().pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
                     }
@@ -2128,7 +2137,7 @@
     /**
      * Called when an animation has finished running.
      */
-    protected void onAnimationFinished() {
+    protected void onAnimationFinished(@AnimationType int type, AnimationAdapter anim) {
         mWmService.onAnimationFinished();
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowContainerThumbnail.java b/services/core/java/com/android/server/wm/WindowContainerThumbnail.java
index 8948f6f..90e3be7 100644
--- a/services/core/java/com/android/server/wm/WindowContainerThumbnail.java
+++ b/services/core/java/com/android/server/wm/WindowContainerThumbnail.java
@@ -20,6 +20,7 @@
 import static android.view.SurfaceControl.METADATA_WINDOW_TYPE;
 
 import static com.android.server.wm.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
 import static com.android.server.wm.WindowContainerThumbnailProto.HEIGHT;
 import static com.android.server.wm.WindowContainerThumbnailProto.SURFACE_ANIMATOR;
 import static com.android.server.wm.WindowContainerThumbnailProto.WIDTH;
@@ -40,6 +41,7 @@
 
 import com.android.server.protolog.common.ProtoLog;
 import com.android.server.wm.SurfaceAnimator.Animatable;
+import com.android.server.wm.SurfaceAnimator.AnimationType;
 
 import java.util.function.Supplier;
 
@@ -85,7 +87,7 @@
             // We can't use a delegating constructor since we need to
             // reference this::onAnimationFinished
             mSurfaceAnimator =
-                new SurfaceAnimator(this, null /* animationFinishedCallback */,
+                new SurfaceAnimator(this, this::onAnimationFinished /* animationFinishedCallback */,
                         container.mWmService);
         }
         mWidth = thumbnailHeader.getWidth();
@@ -130,14 +132,19 @@
                 new WindowAnimationSpec(anim, position,
                         mWindowContainer.getDisplayContent().mAppTransition.canSkipFirstFrame(),
                         mWindowContainer.getDisplayContent().getWindowCornerRadius()),
-                mWindowContainer.mWmService.mSurfaceAnimationRunner), false /* hidden */);
+                mWindowContainer.mWmService.mSurfaceAnimationRunner), false /* hidden */,
+                ANIMATION_TYPE_RECENTS, null /* animationFinishedCallback */);
     }
 
     /**
      * Start animation with existing adapter.
      */
     void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden) {
-        mSurfaceAnimator.startAnimation(t, anim, hidden);
+        mSurfaceAnimator.startAnimation(t, anim, hidden, ANIMATION_TYPE_RECENTS,
+                null /* animationFinishedCallback */);
+    }
+
+    private void onAnimationFinished(@AnimationType int type, AnimationAdapter anim) {
     }
 
     void setShowing(Transaction pendingTransaction, boolean show) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 7f84567..563bb26 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -397,7 +397,7 @@
      * @see #HIERARCHICAL_ANIMATIONS_PROPERTY
      */
     static boolean sHierarchicalAnimations =
-            SystemProperties.getBoolean(HIERARCHICAL_ANIMATIONS_PROPERTY, false);
+            SystemProperties.getBoolean(HIERARCHICAL_ANIMATIONS_PROPERTY, true);
 
     // Enums for animation scale update types.
     @Retention(RetentionPolicy.SOURCE)
@@ -938,7 +938,7 @@
      * Whether the UI is currently running in touch mode (not showing
      * navigational focus because the user is directly pressing the screen).
      */
-    boolean mInTouchMode;
+    private boolean mInTouchMode;
 
     private ViewServer mViewServer;
     final ArrayList<WindowChangeListener> mWindowChangeListeners = new ArrayList<>();
@@ -3461,6 +3461,12 @@
         mInputManager.setInTouchMode(mode);
     }
 
+    boolean getInTouchMode() {
+        synchronized (mGlobalLock) {
+            return mInTouchMode;
+        }
+    }
+
     public void showEmulatorDisplayOverlayIfNeeded() {
         if (mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_windowEnableCircularEmulatorDisplayOverlay)
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index fc04126..1faf48f 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -115,6 +115,7 @@
 import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
 import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_RESIZE;
 import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
 import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
@@ -231,6 +232,7 @@
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.protolog.common.ProtoLog;
 import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
+import com.android.server.wm.SurfaceAnimator.AnimationType;
 import com.android.server.wm.utils.WmDisplayCutout;
 
 import java.io.PrintWriter;
@@ -5027,12 +5029,13 @@
     }
 
     private void startAnimation(Transaction t, AnimationAdapter adapter) {
-        startAnimation(t, adapter, mWinAnimator.mLastHidden, null /* animationFinishedCallback */);
+        startAnimation(t, adapter, mWinAnimator.mLastHidden, ANIMATION_TYPE_WINDOW_ANIMATION,
+                null /* animationFinishedCallback */);
     }
 
     @Override
-    protected void onAnimationFinished() {
-        super.onAnimationFinished();
+    protected void onAnimationFinished(@AnimationType int type, AnimationAdapter anim) {
+        super.onAnimationFinished(type, anim);
         mWinAnimator.onAnimationFinished();
     }
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index ec3ef78..65cabad 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -15077,9 +15077,16 @@
         return packages;
     }
 
-    private List<String> getDefaultCrossProfilePackages() {
-        return Arrays.asList(mContext.getResources()
+    @Override
+    public List<String> getDefaultCrossProfilePackages() {
+        Set<String> crossProfilePackages = new HashSet<>();
+
+        Collections.addAll(crossProfilePackages, mContext.getResources()
                 .getStringArray(R.array.cross_profile_apps));
+        Collections.addAll(crossProfilePackages, mContext.getResources()
+                .getStringArray(R.array.vendor_cross_profile_apps));
+
+        return new ArrayList<>(crossProfilePackages);
     }
 
     private List<ActiveAdmin> getProfileOwnerAdminsForCurrentProfileGroup() {
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index f7a9e54..b193a34 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -5817,6 +5817,7 @@
         mContext.packageName = admin1.getPackageName();
 
         setCrossProfileAppsList();
+        setVendorCrossProfileAppsList();
 
         assertTrue(dpm.getAllCrossProfilePackages().isEmpty());
     }
@@ -5827,6 +5828,7 @@
         mContext.packageName = admin1.getPackageName();
 
         setCrossProfileAppsList();
+        setVendorCrossProfileAppsList();
         initializeDpms();
 
         assertTrue(dpm.getAllCrossProfilePackages().isEmpty());
@@ -5839,9 +5841,11 @@
 
         dpm.setCrossProfilePackages(admin1, packages);
         setCrossProfileAppsList("TEST_DEFAULT_PACKAGE", "TEST_COMMON_PACKAGE");
+        setVendorCrossProfileAppsList("TEST_VENDOR_DEFAULT_PACKAGE");
 
         assertEquals(Sets.newSet(
-                        "TEST_PACKAGE", "TEST_DEFAULT_PACKAGE", "TEST_COMMON_PACKAGE"),
+                        "TEST_PACKAGE", "TEST_DEFAULT_PACKAGE", "TEST_COMMON_PACKAGE",
+                        "TEST_VENDOR_DEFAULT_PACKAGE"),
                 dpm.getAllCrossProfilePackages());
 
     }
@@ -5854,13 +5858,31 @@
 
         dpm.setCrossProfilePackages(admin1, packages);
         setCrossProfileAppsList("TEST_DEFAULT_PACKAGE", "TEST_COMMON_PACKAGE");
+        setVendorCrossProfileAppsList("TEST_VENDOR_DEFAULT_PACKAGE");
         initializeDpms();
 
         assertEquals(Sets.newSet(
-                "TEST_PACKAGE", "TEST_DEFAULT_PACKAGE", "TEST_COMMON_PACKAGE"),
+                "TEST_PACKAGE", "TEST_DEFAULT_PACKAGE", "TEST_COMMON_PACKAGE",
+                "TEST_VENDOR_DEFAULT_PACKAGE"),
                 dpm.getAllCrossProfilePackages());
     }
 
+    public void testGetDefaultCrossProfilePackages_noPackagesSet_returnsEmpty() {
+        setCrossProfileAppsList();
+        setVendorCrossProfileAppsList();
+
+        assertThat(dpm.getDefaultCrossProfilePackages()).isEmpty();
+    }
+
+    public void testGetDefaultCrossProfilePackages_packagesSet_returnsCombinedSet() {
+        setCrossProfileAppsList("TEST_DEFAULT_PACKAGE", "TEST_COMMON_PACKAGE");
+        setVendorCrossProfileAppsList("TEST_VENDOR_DEFAULT_PACKAGE");
+
+        assertThat(dpm.getDefaultCrossProfilePackages()).isEqualTo(Sets.newSet(
+                "TEST_DEFAULT_PACKAGE", "TEST_COMMON_PACKAGE", "TEST_VENDOR_DEFAULT_PACKAGE"
+        ));
+    }
+
     public void testSetCommonCriteriaMode_asDeviceOwner() throws Exception {
         setDeviceOwner();
 
@@ -5892,6 +5914,12 @@
                 .thenReturn(packages);
     }
 
+    private void setVendorCrossProfileAppsList(String... packages) {
+        when(mContext.getResources()
+                .getStringArray(eq(R.array.vendor_cross_profile_apps)))
+                .thenReturn(packages);
+    }
+
     // admin1 is the outgoing DPC, adminAnotherPakcage is the incoming one.
     private void assertDeviceOwnershipRevertedWithFakeTransferMetadata() throws Exception {
         writeFakeTransferMetadataFile(UserHandle.USER_SYSTEM,
diff --git a/services/tests/wmtests/src/com/android/server/wm/AnimatingActivityRegistryTest.java b/services/tests/wmtests/src/com/android/server/wm/AnimatingActivityRegistryTest.java
index 71390db..0f54895 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AnimatingActivityRegistryTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AnimatingActivityRegistryTest.java
@@ -21,6 +21,7 @@
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyZeroInteractions;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
 
 import static org.junit.Assert.assertFalse;
@@ -69,8 +70,10 @@
         final AnimatingActivityRegistry registry =
                 activity1.getStack().getAnimatingActivityRegistry();
 
-        activity1.startAnimation(activity1.getPendingTransaction(), mAdapter, false /* hidden */);
-        activity2.startAnimation(activity1.getPendingTransaction(), mAdapter, false /* hidden */);
+        activity1.startAnimation(activity1.getPendingTransaction(), mAdapter, false /* hidden */,
+                ANIMATION_TYPE_APP_TRANSITION);
+        activity2.startAnimation(activity1.getPendingTransaction(), mAdapter, false /* hidden */,
+                ANIMATION_TYPE_APP_TRANSITION);
         assertTrue(activity1.isAnimating(TRANSITION));
         assertTrue(activity2.isAnimating(TRANSITION));
 
@@ -91,8 +94,10 @@
         final AnimatingActivityRegistry registry =
                 window1.getStack().getAnimatingActivityRegistry();
 
-        window1.startAnimation(window1.getPendingTransaction(), mAdapter, false /* hidden */);
-        window2.startAnimation(window1.getPendingTransaction(), mAdapter, false /* hidden */);
+        window1.startAnimation(window1.getPendingTransaction(), mAdapter, false /* hidden */,
+                ANIMATION_TYPE_APP_TRANSITION);
+        window2.startAnimation(window1.getPendingTransaction(), mAdapter, false /* hidden */,
+                ANIMATION_TYPE_APP_TRANSITION);
         assertTrue(window1.isAnimating(TRANSITION));
         assertTrue(window2.isAnimating(TRANSITION));
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java
index b0f3b42..6e78a27 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java
@@ -20,6 +20,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -70,7 +71,8 @@
     public void clipAfterAnim_boundsLayerIsCreated() {
         mActivity.mNeedsAnimationBoundsLayer = true;
 
-        mActivity.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
+        mActivity.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */,
+                ANIMATION_TYPE_APP_TRANSITION);
         verify(mTransaction).reparent(eq(mActivity.getSurfaceControl()),
                 eq(mActivity.mSurfaceAnimator.mLeash));
         verify(mTransaction).reparent(eq(mActivity.mSurfaceAnimator.mLeash),
@@ -82,7 +84,8 @@
         mActivity.mNeedsAnimationBoundsLayer = true;
         mActivity.mNeedsZBoost = true;
 
-        mActivity.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
+        mActivity.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */,
+                ANIMATION_TYPE_APP_TRANSITION);
         verify(mTransaction).setLayer(eq(mActivity.mAnimationBoundsLayer),
                 intThat(layer -> layer >= ActivityRecord.Z_BOOST_BASE));
     }
@@ -91,15 +94,18 @@
     @FlakyTest(bugId = 131005232)
     public void clipAfterAnim_boundsLayerIsDestroyed() {
         mActivity.mNeedsAnimationBoundsLayer = true;
-        mActivity.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
+        mActivity.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */,
+                ANIMATION_TYPE_APP_TRANSITION);
         final SurfaceControl leash = mActivity.mSurfaceAnimator.mLeash;
         final SurfaceControl animationBoundsLayer = mActivity.mAnimationBoundsLayer;
         final ArgumentCaptor<SurfaceAnimator.OnAnimationFinishedCallback> callbackCaptor =
                 ArgumentCaptor.forClass(
                         SurfaceAnimator.OnAnimationFinishedCallback.class);
-        verify(mSpec).startAnimation(any(), any(), callbackCaptor.capture());
+        verify(mSpec).startAnimation(any(), any(), eq(ANIMATION_TYPE_APP_TRANSITION),
+                callbackCaptor.capture());
 
-        callbackCaptor.getValue().onAnimationFinished(mSpec);
+        callbackCaptor.getValue().onAnimationFinished(
+                ANIMATION_TYPE_APP_TRANSITION, mSpec);
         verify(mTransaction).remove(eq(leash));
         verify(mTransaction).remove(eq(animationBoundsLayer));
         assertThat(mActivity.mNeedsAnimationBoundsLayer).isFalse();
@@ -108,7 +114,8 @@
     @Test
     public void clipAfterAnimCancelled_boundsLayerIsDestroyed() {
         mActivity.mNeedsAnimationBoundsLayer = true;
-        mActivity.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
+        mActivity.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */,
+                ANIMATION_TYPE_APP_TRANSITION);
         final SurfaceControl leash = mActivity.mSurfaceAnimator.mLeash;
         final SurfaceControl animationBoundsLayer = mActivity.mAnimationBoundsLayer;
 
@@ -123,7 +130,8 @@
     public void clipNoneAnim_boundsLayerIsNotCreated() {
         mActivity.mNeedsAnimationBoundsLayer = false;
 
-        mActivity.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
+        mActivity.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */,
+                ANIMATION_TYPE_APP_TRANSITION);
         verify(mTransaction).reparent(eq(mActivity.getSurfaceControl()),
                 eq(mActivity.mSurfaceAnimator.mLeash));
         assertThat(mActivity.mAnimationBoundsLayer).isNull();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
index 7344fa4..77ceeed 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
@@ -22,11 +22,13 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.when;
@@ -37,6 +39,9 @@
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
 
+import com.android.server.wm.SurfaceAnimator.AnimationType;
+import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -114,11 +119,11 @@
     private static class SurfaceAnimatorStarterImpl implements Dimmer.SurfaceAnimatorStarter {
         @Override
         public void startAnimation(SurfaceAnimator surfaceAnimator, SurfaceControl.Transaction t,
-                AnimationAdapter anim, boolean hidden,
-                @Nullable Runnable animationFinishedCallback) {
-            surfaceAnimator.mStaticAnimationFinishedCallback.run();
+                AnimationAdapter anim, boolean hidden, @AnimationType int type,
+                @Nullable OnAnimationFinishedCallback animationFinishedCallback) {
+            surfaceAnimator.mStaticAnimationFinishedCallback.onAnimationFinished(type, anim);
             if (animationFinishedCallback != null) {
-                animationFinishedCallback.run();
+                animationFinishedCallback.onAnimationFinished(type, anim);
             }
         }
     }
@@ -224,7 +229,7 @@
         mDimmer.updateDims(mTransaction, new Rect());
         verify(mSurfaceAnimatorStarter).startAnimation(any(SurfaceAnimator.class), any(
                 SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean(),
-                isNull());
+                eq(ANIMATION_TYPE_DIMMER), isNull());
         verify(mHost.getPendingTransaction()).remove(dimLayer);
     }
 
@@ -282,7 +287,7 @@
         mDimmer.updateDims(mTransaction, new Rect());
         verify(mSurfaceAnimatorStarter, never()).startAnimation(any(SurfaceAnimator.class), any(
                 SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean(),
-                isNull());
+                eq(ANIMATION_TYPE_DIMMER), isNull());
         verify(mTransaction).remove(dimLayer);
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
new file mode 100644
index 0000000..618e608
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION;
+import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+
+import static com.android.server.wm.DisplayArea.Type.ABOVE_TASKS;
+import static com.android.server.wm.DisplayArea.Type.ANY;
+import static com.android.server.wm.DisplayArea.Type.BELOW_TASKS;
+import static com.android.server.wm.DisplayArea.Type.checkChild;
+import static com.android.server.wm.DisplayArea.Type.checkSiblings;
+import static com.android.server.wm.DisplayArea.Type.typeOf;
+import static com.android.server.wm.testing.Assert.assertThrows;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+
+import android.os.Binder;
+import android.platform.test.annotations.Presubmit;
+import android.view.SurfaceControl;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+@Presubmit
+public class DisplayAreaTest {
+
+    @Rule
+    public SystemServicesTestRule mWmsRule = new SystemServicesTestRule();
+
+    @Test
+    public void testDisplayArea_positionChanged_throwsIfIncompatibleChild() {
+        WindowManagerService wms = mWmsRule.getWindowManagerService();
+        DisplayArea<WindowContainer> parent = new DisplayArea<>(wms, BELOW_TASKS, "Parent");
+        DisplayArea<WindowContainer> child = new DisplayArea<>(wms, ANY, "Child");
+
+        assertThrows(IllegalStateException.class, () -> parent.addChild(child, 0));
+    }
+
+    @Test
+    public void testDisplayArea_positionChanged_throwsIfIncompatibleSibling() {
+        WindowManagerService wms = mWmsRule.getWindowManagerService();
+        DisplayArea<WindowContainer> parent = new SurfacelessDisplayArea<>(wms, ANY, "Parent");
+        DisplayArea<WindowContainer> child1 = new DisplayArea<>(wms, ANY, "Child1");
+        DisplayArea<WindowContainer> child2 = new DisplayArea<>(wms, ANY, "Child2");
+
+        parent.addChild(child1, 0);
+        assertThrows(IllegalStateException.class, () -> parent.addChild(child2, 0));
+    }
+
+    @Test
+    public void testType_typeOf() {
+        WindowManagerService wms = mWmsRule.getWindowManagerService();
+
+        assertEquals(ABOVE_TASKS, typeOf(new DisplayArea<>(wms, ABOVE_TASKS, "test")));
+        assertEquals(ANY, typeOf(new DisplayArea<>(wms, ANY, "test")));
+        assertEquals(BELOW_TASKS, typeOf(new DisplayArea<>(wms, BELOW_TASKS, "test")));
+
+        assertEquals(ABOVE_TASKS, typeOf(createWindowToken(TYPE_APPLICATION_OVERLAY)));
+        assertEquals(ABOVE_TASKS, typeOf(createWindowToken(TYPE_PRESENTATION)));
+        assertEquals(BELOW_TASKS, typeOf(createWindowToken(TYPE_WALLPAPER)));
+
+        assertThrows(IllegalArgumentException.class, () -> typeOf(mock(ActivityRecord.class)));
+        assertThrows(IllegalArgumentException.class, () -> typeOf(mock(WindowContainer.class)));
+    }
+
+    @Test
+    public void testType_checkSiblings() {
+        checkSiblings(BELOW_TASKS, BELOW_TASKS);
+        checkSiblings(BELOW_TASKS, ANY);
+        checkSiblings(BELOW_TASKS, ABOVE_TASKS);
+        checkSiblings(ANY, ABOVE_TASKS);
+        checkSiblings(ABOVE_TASKS, ABOVE_TASKS);
+
+        assertThrows(IllegalStateException.class, () -> checkSiblings(ABOVE_TASKS, BELOW_TASKS));
+        assertThrows(IllegalStateException.class, () -> checkSiblings(ABOVE_TASKS, ANY));
+        assertThrows(IllegalStateException.class, () -> checkSiblings(ANY, ANY));
+        assertThrows(IllegalStateException.class, () -> checkSiblings(ANY, BELOW_TASKS));
+    }
+
+    @Test
+    public void testType_checkChild() {
+        checkChild(ANY, ANY);
+        checkChild(ANY, ABOVE_TASKS);
+        checkChild(ANY, BELOW_TASKS);
+        checkChild(ABOVE_TASKS, ABOVE_TASKS);
+        checkChild(BELOW_TASKS, BELOW_TASKS);
+
+        assertThrows(IllegalStateException.class, () -> checkChild(ABOVE_TASKS, BELOW_TASKS));
+        assertThrows(IllegalStateException.class, () -> checkChild(ABOVE_TASKS, ANY));
+        assertThrows(IllegalStateException.class, () -> checkChild(BELOW_TASKS, ABOVE_TASKS));
+        assertThrows(IllegalStateException.class, () -> checkChild(BELOW_TASKS, ANY));
+    }
+
+    private WindowToken createWindowToken(int type) {
+        return new WindowToken(mWmsRule.getWindowManagerService(), new Binder(),
+                type, false /* persist */, null /* displayContent */,
+                false /* canManageTokens */);
+    }
+
+    private static class SurfacelessDisplayArea<T extends WindowContainer> extends DisplayArea<T> {
+
+        SurfacelessDisplayArea(WindowManagerService wms, Type type, String name) {
+            super(wms, type, name);
+        }
+
+        @Override
+        SurfaceControl.Builder makeChildSurface(WindowContainer child) {
+            return new MockSurfaceControlBuilder();
+        }
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 1637370..2ea00ce 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -139,11 +139,11 @@
                 mAppWindow,
                 mChildAppWindowAbove,
                 mDockedDividerWindow,
+                mImeWindow,
+                mImeDialogWindow,
                 mStatusBarWindow,
                 mNotificationShadeWindow,
-                mNavBarWindow,
-                mImeWindow,
-                mImeDialogWindow));
+                mNavBarWindow));
     }
 
     @Test
@@ -232,11 +232,11 @@
                 mChildAppWindowAbove,
                 mDockedDividerWindow,
                 voiceInteractionWindow,
+                mImeWindow,
+                mImeDialogWindow,
                 mStatusBarWindow,
                 mNotificationShadeWindow,
-                mNavBarWindow,
-                mImeWindow,
-                mImeDialogWindow));
+                mNavBarWindow));
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index 945928d..7753a32 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -37,6 +37,7 @@
 import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE;
 import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_ORIGINAL_POSITION;
 import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_TOP;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -103,14 +104,15 @@
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
         AnimationAdapter adapter = mController.addAnimation(activity.getTask(),
                 false /* isRecentTaskInvisible */);
-        adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
+        adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_RECENTS,
+                mFinishedCallback);
 
         // Remove the app window so that the animation target can not be created
         activity.removeImmediately();
         mController.startAnimation();
 
         // Verify that the finish callback to reparent the leash is called
-        verify(mFinishedCallback).onAnimationFinished(eq(adapter));
+        verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_RECENTS), eq(adapter));
         // Verify the animation canceled callback to the app was made
         verify(mMockRunner).onAnimationCanceled(null /* taskSnapshot */);
         verifyNoMoreInteractionsExceptAsBinder(mMockRunner);
@@ -122,7 +124,8 @@
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
         AnimationAdapter adapter = mController.addAnimation(activity.getTask(),
                 false /* isRecentTaskInvisible */);
-        adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
+        adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_RECENTS,
+                mFinishedCallback);
 
         // Remove the app window so that the animation target can not be created
         activity.removeImmediately();
diff --git a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
index a23425f..be25597 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
@@ -18,6 +18,8 @@
 
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
+
 import static org.junit.Assert.assertEquals;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -108,7 +110,7 @@
         overrideWindow.mAttrs.preferredDisplayModeId = LOW_MODE_ID;
         overrideWindow.mActivityRecord.mSurfaceAnimator.startAnimation(
                 overrideWindow.getPendingTransaction(), mock(AnimationAdapter.class),
-                false /* hidden */);
+                false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
         mPolicy.addNonHighRefreshRatePackage("com.android.test");
         assertEquals(0, mPolicy.getPreferredModeId(overrideWindow));
     }
@@ -124,7 +126,7 @@
 
         cameraUsingWindow.mActivityRecord.mSurfaceAnimator.startAnimation(
                 cameraUsingWindow.getPendingTransaction(), mock(AnimationAdapter.class),
-                false /* hidden */);
+                false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
         assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index 1a57596..3a724a1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -25,6 +25,8 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -97,7 +99,8 @@
         try {
             final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mActivityRecord,
                     new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter;
-            adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
+            adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
+                    mFinishedCallback);
             mController.goodToGo();
             mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
             final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
@@ -122,7 +125,8 @@
             verify(mMockTransaction).setWindowCrop(mMockLeash, 100, 50);
 
             finishedCaptor.getValue().onAnimationFinished();
-            verify(mFinishedCallback).onAnimationFinished(eq(adapter));
+            verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_APP_TRANSITION),
+                    eq(adapter));
         } finally {
             mDisplayContent.mOpeningApps.clear();
         }
@@ -133,7 +137,8 @@
         final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
         final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mActivityRecord,
                 new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter;
-        adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
+        adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
+                mFinishedCallback);
         mController.goodToGo();
 
         adapter.onAnimationCancelled(mMockLeash);
@@ -146,14 +151,16 @@
         final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
         final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mActivityRecord,
                 new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter;
-        adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
+        adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
+                mFinishedCallback);
         mController.goodToGo();
 
         mClock.fastForward(2500);
         mHandler.timeAdvance();
 
         verify(mMockRunner).onAnimationCancelled();
-        verify(mFinishedCallback).onAnimationFinished(eq(adapter));
+        verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_APP_TRANSITION),
+                eq(adapter));
     }
 
     @Test
@@ -164,7 +171,8 @@
                     "testWin");
             final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
                     win.mActivityRecord, new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter;
-            adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
+            adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
+                    mFinishedCallback);
             mController.goodToGo();
 
             mClock.fastForward(2500);
@@ -176,7 +184,8 @@
             mHandler.timeAdvance();
 
             verify(mMockRunner).onAnimationCancelled();
-            verify(mFinishedCallback).onAnimationFinished(eq(adapter));
+            verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_APP_TRANSITION),
+                    eq(adapter));
         } finally {
             mWm.setAnimationScale(2, 1.0f);
         }
@@ -205,7 +214,8 @@
                 new Point(50, 100), new Rect(50, 100, 150, 150), null);
         final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win2.mActivityRecord,
                 new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter;
-        adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
+        adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
+                mFinishedCallback);
         mController.goodToGo();
         mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
         final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
@@ -225,11 +235,13 @@
         final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
         final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mActivityRecord,
                 new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter;
-        adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
+        adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
+                mFinishedCallback);
         win.mActivityRecord.removeImmediately();
         mController.goodToGo();
         verifyNoMoreInteractionsExceptAsBinder(mMockRunner);
-        verify(mFinishedCallback).onAnimationFinished(eq(adapter));
+        verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_APP_TRANSITION),
+                eq(adapter));
     }
 
     @Test
@@ -242,9 +254,10 @@
                     new Rect(0, 0, 200, 200));
             assertNotNull(record.mThumbnailAdapter);
             ((AnimationAdapter) record.mAdapter)
-                    .startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
+                    .startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_WINDOW_ANIMATION,
+                            mFinishedCallback);
             ((AnimationAdapter) record.mThumbnailAdapter).startAnimation(mMockThumbnailLeash,
-                    mMockTransaction, mThumbnailFinishedCallback);
+                    mMockTransaction, ANIMATION_TYPE_WINDOW_ANIMATION, mThumbnailFinishedCallback);
             mController.goodToGo();
             mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
             final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
@@ -272,8 +285,10 @@
             verify(mMockTransaction).setPosition(mMockThumbnailLeash, 0, 0);
 
             finishedCaptor.getValue().onAnimationFinished();
-            verify(mFinishedCallback).onAnimationFinished(eq(record.mAdapter));
-            verify(mThumbnailFinishedCallback).onAnimationFinished(eq(record.mThumbnailAdapter));
+            verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_WINDOW_ANIMATION),
+                    eq(record.mAdapter));
+            verify(mThumbnailFinishedCallback).onAnimationFinished(
+                    eq(ANIMATION_TYPE_WINDOW_ANIMATION), eq(record.mThumbnailAdapter));
         } finally {
             mDisplayContent.mChangingApps.clear();
         }
@@ -290,7 +305,8 @@
         try {
             final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mActivityRecord,
                     new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter;
-            adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
+            adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
+                    mFinishedCallback);
             mController.goodToGo();
             mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
             final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
@@ -318,7 +334,8 @@
         try {
             final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mActivityRecord,
                     new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter;
-            adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
+            adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
+                    mFinishedCallback);
             mController.goodToGo();
             mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
             final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
index 2894241..552c476 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
@@ -22,6 +22,8 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyZeroInteractions;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -39,6 +41,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.server.wm.SurfaceAnimator.Animatable;
+import com.android.server.wm.SurfaceAnimator.AnimationType;
 import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
 
 import org.junit.After;
@@ -89,25 +92,30 @@
 
     @Test
     public void testRunAnimation() {
-        mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
+        mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */,
+                ANIMATION_TYPE_RECENTS);
         final ArgumentCaptor<OnAnimationFinishedCallback> callbackCaptor = ArgumentCaptor.forClass(
                 OnAnimationFinishedCallback.class);
         assertAnimating(mAnimatable);
         verify(mTransaction).reparent(eq(mAnimatable.mSurface), eq(mAnimatable.mLeash));
-        verify(mSpec).startAnimation(any(), any(), callbackCaptor.capture());
+        verify(mSpec).startAnimation(any(), any(), eq(ANIMATION_TYPE_RECENTS),
+                callbackCaptor.capture());
 
-        callbackCaptor.getValue().onAnimationFinished(mSpec);
+        callbackCaptor.getValue().onAnimationFinished(ANIMATION_TYPE_RECENTS, mSpec);
         assertNotAnimating(mAnimatable);
         assertTrue(mAnimatable.mFinishedCallbackCalled);
+        assertEquals(ANIMATION_TYPE_RECENTS, mAnimatable.mFinishedAnimationType);
         verify(mTransaction).remove(eq(mAnimatable.mLeash));
         // TODO: Verify reparenting once we use mPendingTransaction to reparent it back
     }
 
     @Test
     public void testOverrideAnimation() {
-        mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
+        mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */,
+                ANIMATION_TYPE_APP_TRANSITION);
         final SurfaceControl firstLeash = mAnimatable.mLeash;
-        mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec2, true /* hidden */);
+        mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec2, true /* hidden */,
+                ANIMATION_TYPE_APP_TRANSITION);
 
         verify(mTransaction).remove(eq(firstLeash));
         assertFalse(mAnimatable.mFinishedCallbackCalled);
@@ -115,34 +123,40 @@
         final ArgumentCaptor<OnAnimationFinishedCallback> callbackCaptor = ArgumentCaptor.forClass(
                 OnAnimationFinishedCallback.class);
         assertAnimating(mAnimatable);
-        verify(mSpec).startAnimation(any(), any(), callbackCaptor.capture());
+        verify(mSpec).startAnimation(any(), any(), eq(ANIMATION_TYPE_APP_TRANSITION),
+                callbackCaptor.capture());
 
         // First animation was finished, but this shouldn't cancel the second animation
-        callbackCaptor.getValue().onAnimationFinished(mSpec);
+        callbackCaptor.getValue().onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION, mSpec);
         assertTrue(mAnimatable.mSurfaceAnimator.isAnimating());
 
         // Second animation was finished
-        verify(mSpec2).startAnimation(any(), any(), callbackCaptor.capture());
-        callbackCaptor.getValue().onAnimationFinished(mSpec2);
+        verify(mSpec2).startAnimation(any(), any(), eq(ANIMATION_TYPE_APP_TRANSITION),
+                callbackCaptor.capture());
+        callbackCaptor.getValue().onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION, mSpec2);
         assertNotAnimating(mAnimatable);
         assertTrue(mAnimatable.mFinishedCallbackCalled);
+        assertEquals(ANIMATION_TYPE_APP_TRANSITION, mAnimatable.mFinishedAnimationType);
     }
 
     @Test
     public void testCancelAnimation() {
-        mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
+        mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */,
+                ANIMATION_TYPE_APP_TRANSITION);
         assertAnimating(mAnimatable);
         mAnimatable.mSurfaceAnimator.cancelAnimation();
         assertNotAnimating(mAnimatable);
         verify(mSpec).onAnimationCancelled(any());
         assertTrue(mAnimatable.mFinishedCallbackCalled);
+        assertEquals(ANIMATION_TYPE_APP_TRANSITION, mAnimatable.mFinishedAnimationType);
         verify(mTransaction).remove(eq(mAnimatable.mLeash));
     }
 
     @Test
     public void testCancelWithNullFinishCallbackAnimation() {
         SurfaceAnimator animator = new SurfaceAnimator(mAnimatable, null, mWm);
-        animator.startAnimation(mTransaction, mSpec, true /* hidden */);
+        animator.startAnimation(mTransaction, mSpec, true /* hidden */,
+                ANIMATION_TYPE_APP_TRANSITION);
         assertTrue(animator.isAnimating());
         assertNotNull(animator.getAnimation());
         animator.cancelAnimation();
@@ -155,32 +169,37 @@
     @Test
     public void testDelayingAnimationStart() {
         mAnimatable.mSurfaceAnimator.startDelayingAnimationStart();
-        mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
+        mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */,
+                ANIMATION_TYPE_APP_TRANSITION);
         verifyZeroInteractions(mSpec);
         assertAnimating(mAnimatable);
         assertTrue(mAnimatable.mSurfaceAnimator.isAnimationStartDelayed());
         mAnimatable.mSurfaceAnimator.endDelayingAnimationStart();
-        verify(mSpec).startAnimation(any(), any(), any());
+        verify(mSpec).startAnimation(any(), any(), eq(ANIMATION_TYPE_APP_TRANSITION), any());
     }
 
     @Test
     public void testDelayingAnimationStartAndCancelled() {
         mAnimatable.mSurfaceAnimator.startDelayingAnimationStart();
-        mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
+        mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */,
+                ANIMATION_TYPE_APP_TRANSITION);
         mAnimatable.mSurfaceAnimator.cancelAnimation();
         verifyZeroInteractions(mSpec);
         assertNotAnimating(mAnimatable);
         assertTrue(mAnimatable.mFinishedCallbackCalled);
+        assertEquals(ANIMATION_TYPE_APP_TRANSITION, mAnimatable.mFinishedAnimationType);
         verify(mTransaction).remove(eq(mAnimatable.mLeash));
     }
 
     @Test
     public void testTransferAnimation() {
-        mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
+        mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */,
+                ANIMATION_TYPE_APP_TRANSITION);
 
         final ArgumentCaptor<OnAnimationFinishedCallback> callbackCaptor = ArgumentCaptor.forClass(
                 OnAnimationFinishedCallback.class);
-        verify(mSpec).startAnimation(any(), any(), callbackCaptor.capture());
+        verify(mSpec).startAnimation(any(), any(), eq(ANIMATION_TYPE_APP_TRANSITION),
+                callbackCaptor.capture());
         final SurfaceControl leash = mAnimatable.mLeash;
 
         mAnimatable2.mSurfaceAnimator.transferAnimation(mAnimatable.mSurfaceAnimator);
@@ -188,15 +207,17 @@
         assertAnimating(mAnimatable2);
         assertEquals(leash, mAnimatable2.mSurfaceAnimator.mLeash);
         verify(mTransaction, never()).remove(eq(leash));
-        callbackCaptor.getValue().onAnimationFinished(mSpec);
+        callbackCaptor.getValue().onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION, mSpec);
         assertNotAnimating(mAnimatable2);
         assertTrue(mAnimatable2.mFinishedCallbackCalled);
+        assertEquals(ANIMATION_TYPE_APP_TRANSITION, mAnimatable.mFinishedAnimationType);
         verify(mTransaction).remove(eq(leash));
     }
 
     @Test
     public void testOnAnimationLeashLostWhenAnimatableParentSurfaceControlNull() {
-        mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
+        mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */,
+                ANIMATION_TYPE_APP_TRANSITION);
         spyOn(mAnimatable);
 
         // Verify onAnimationLeashLost will be called even animatable's parent surface control lost.
@@ -215,13 +236,14 @@
         final OnAnimationFinishedCallback onFinishedCallback = startDeferFinishAnimatable(mSpec);
 
         // Finish the animation but then make sure we are deferring.
-        onFinishedCallback.onAnimationFinished(mSpec);
+        onFinishedCallback.onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION, mSpec);
         assertAnimating(mDeferFinishAnimatable);
 
         // Now end defer finishing.
         mDeferFinishAnimatable.mEndDeferFinishCallback.run();
         assertNotAnimating(mAnimatable2);
         assertTrue(mDeferFinishAnimatable.mFinishedCallbackCalled);
+        assertEquals(ANIMATION_TYPE_APP_TRANSITION, mDeferFinishAnimatable.mFinishedAnimationType);
         verify(mTransaction).remove(eq(mDeferFinishAnimatable.mLeash));
     }
 
@@ -229,7 +251,7 @@
     public void testDeferFinishDoNotFinishNextAnimation() {
         // Start the first animation.
         final OnAnimationFinishedCallback onFinishedCallback = startDeferFinishAnimatable(mSpec);
-        onFinishedCallback.onAnimationFinished(mSpec);
+        onFinishedCallback.onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION, mSpec);
         // The callback is the resetAndInvokeFinish in {@link SurfaceAnimator#getFinishedCallback}.
         final Runnable firstDeferFinishCallback = mDeferFinishAnimatable.mEndDeferFinishCallback;
 
@@ -247,11 +269,12 @@
 
     private OnAnimationFinishedCallback startDeferFinishAnimatable(AnimationAdapter anim) {
         mDeferFinishAnimatable.mSurfaceAnimator.startAnimation(mTransaction, anim,
-                true /* hidden */);
+                true /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
         final ArgumentCaptor<OnAnimationFinishedCallback> callbackCaptor = ArgumentCaptor.forClass(
                 OnAnimationFinishedCallback.class);
         assertAnimating(mDeferFinishAnimatable);
-        verify(anim).startAnimation(any(), any(), callbackCaptor.capture());
+        verify(anim).startAnimation(any(), any(), eq(ANIMATION_TYPE_APP_TRANSITION),
+                callbackCaptor.capture());
         return callbackCaptor.getValue();
     }
 
@@ -274,6 +297,7 @@
         final SurfaceAnimator mSurfaceAnimator;
         SurfaceControl mLeash;
         boolean mFinishedCallbackCalled;
+        @AnimationType int mFinishedAnimationType;
 
         MyAnimatable(WindowManagerService wm, SurfaceSession session, Transaction transaction) {
             mSession = session;
@@ -343,7 +367,11 @@
             return 1;
         }
 
-        private final Runnable mFinishedCallback = () -> mFinishedCallbackCalled = true;
+        private final SurfaceAnimator.OnAnimationFinishedCallback mFinishedCallback = (
+                type, anim) -> {
+            mFinishedCallbackCalled = true;
+            mFinishedAnimationType = type;
+        };
     }
 
     private static class DeferFinishAnimatable extends MyAnimatable {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index 05d048d3..4a87701 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -34,6 +34,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
 import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
 import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
@@ -61,6 +62,8 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mockito;
@@ -829,7 +832,8 @@
         wc.getDisplayContent().mAppTransition.overridePendingAppTransitionRemote(adapter);
         spyOn(wc);
         doReturn(true).when(wc).okToAnimate();
-        final Runnable onAnimationFinishedCallback = mock(Runnable.class);
+        final OnAnimationFinishedCallback onAnimationFinishedCallback =
+                mock(OnAnimationFinishedCallback.class);
 
         // Make sure animating state is as expected after applied animation.
         assertTrue(wc.applyAnimation(null, TRANSIT_TASK_OPEN, true, false,
@@ -837,16 +841,18 @@
         assertEquals(wc.getTopMostActivity(), act);
         assertTrue(wc.isAnimating());
         assertTrue(act.isAnimating(PARENTS));
-        verify(onAnimationFinishedCallback, times(0)).run();
+        verify(onAnimationFinishedCallback, times(0)).onAnimationFinished(
+                eq(ANIMATION_TYPE_APP_TRANSITION), any());
 
         // Make sure animation finish callback will be received and reset animating state after
         // animation finish.
         wc.getDisplayContent().mAppTransition.goodToGo(TRANSIT_TASK_OPEN, act,
                 mDisplayContent.mOpeningApps);
-        verify(wc).onAnimationFinished();
+        verify(wc).onAnimationFinished(eq(ANIMATION_TYPE_APP_TRANSITION), any());
         assertFalse(wc.isAnimating());
         assertFalse(act.isAnimating(PARENTS));
-        verify(onAnimationFinishedCallback, times(1)).run();
+        verify(onAnimationFinishedCallback, times(1)).onAnimationFinished(
+                eq(ANIMATION_TYPE_APP_TRANSITION), any());
     }
 
     /* Used so we can gain access to some protected members of the {@link WindowContainer} class */
diff --git a/services/tests/wmtests/src/com/android/server/wm/testing/Assert.java b/services/tests/wmtests/src/com/android/server/wm/testing/Assert.java
new file mode 100644
index 0000000..1e98277
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/testing/Assert.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.testing;
+
+/**
+ * Assertions for WM tests.
+ */
+public class Assert {
+
+    /**
+     * Runs {@code r} and asserts that an exception of type {@code expectedThrowable} is thrown.
+     * @param expectedThrowable the type of throwable that is expected to be thrown
+     * @param r the {@link Runnable} which is run and expected to throw.
+     * @throws AssertionError if {@code r} does not throw, or throws a runnable that is not an
+     *                        instance of {@code expectedThrowable}.
+     */
+    // TODO: remove once Android migrates to JUnit 4.13, which provides assertThrows
+    public static void assertThrows(Class<? extends Throwable> expectedThrowable, Runnable r) {
+        try {
+            r.run();
+        } catch (Throwable t) {
+            if (expectedThrowable.isInstance(t)) {
+                return;
+            } else if (t instanceof Exception) {
+                throw new AssertionError("Expected " + expectedThrowable
+                        + ", but got " + t.getClass(), t);
+            } else {
+                // Re-throw Errors and other non-Exception throwables.
+                throw t;
+            }
+        }
+        throw new AssertionError("Expected " + expectedThrowable + ", but nothing was thrown");
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/testing/AssertTest.java b/services/tests/wmtests/src/com/android/server/wm/testing/AssertTest.java
new file mode 100644
index 0000000..df12761
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/testing/AssertTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.testing;
+
+import static com.android.server.wm.testing.Assert.assertThrows;
+
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+public class AssertTest {
+
+    @Rule
+    public ExpectedException mExpectedException = ExpectedException.none();
+
+    @Test
+    public void assertThrows_runsRunnable() {
+        boolean[] ran = new boolean[] { false };
+        assertThrows(TestException.class, () -> {
+            ran[0] = true;
+            throw new TestException();
+        });
+        assertTrue(ran[0]);
+    }
+
+    @Test
+    public void assertThrows_failsIfNothingThrown() {
+        mExpectedException.expect(AssertionError.class);
+        assertThrows(TestException.class, () -> {
+        });
+    }
+
+    @Test
+    public void assertThrows_failsIfWrongExceptionThrown() {
+        mExpectedException.expect(AssertionError.class);
+        assertThrows(TestException.class, () -> {
+            throw new RuntimeException();
+        });
+    }
+
+    @Test
+    public void assertThrows_succeedsIfGivenExceptionThrown() {
+        assertThrows(TestException.class, () -> {
+            throw new TestException();
+        });
+    }
+
+    @Test
+    public void assertThrows_succeedsIfSubExceptionThrown() {
+        assertThrows(RuntimeException.class, () -> {
+            throw new TestException();
+        });
+    }
+
+    @Test
+    public void assertThrows_rethrowsUnexpectedErrors() {
+        mExpectedException.expect(TestError.class);
+        assertThrows(TestException.class, () -> {
+            throw new TestError();
+        });
+    }
+
+    static class TestException extends RuntimeException {
+    }
+
+    static class TestError extends Error {
+    }
+
+}
diff --git a/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java b/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java
index 7ab4b56..2d5df4f 100644
--- a/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java
+++ b/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java
@@ -27,12 +27,18 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 
+import android.content.Context;
 import android.os.PersistableBundle;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -52,15 +58,27 @@
 
     private static final Executor INLINE_EXECUTOR = x -> x.run();
 
+    @Mock private Context mContext;
+    @Mock private IConnectivityManager mService;
     @Mock private ConnectivityDiagnosticsCallback mCb;
 
     private ConnectivityDiagnosticsBinder mBinder;
+    private ConnectivityDiagnosticsManager mManager;
 
     @Before
     public void setUp() {
+        mContext = mock(Context.class);
+        mService = mock(IConnectivityManager.class);
         mCb = mock(ConnectivityDiagnosticsCallback.class);
 
         mBinder = new ConnectivityDiagnosticsBinder(mCb, INLINE_EXECUTOR);
+        mManager = new ConnectivityDiagnosticsManager(mContext, mService);
+    }
+
+    @After
+    public void tearDown() {
+        // clear ConnectivityDiagnosticsManager callbacks map
+        ConnectivityDiagnosticsManager.sCallbacks.clear();
     }
 
     private ConnectivityReport createSampleConnectivityReport() {
@@ -245,4 +263,53 @@
         // latch without waiting.
         verify(mCb).onNetworkConnectivityReported(eq(n), eq(connectivity));
     }
+
+    @Test
+    public void testRegisterConnectivityDiagnosticsCallback() throws Exception {
+        final NetworkRequest request = new NetworkRequest.Builder().build();
+
+        mManager.registerConnectivityDiagnosticsCallback(request, INLINE_EXECUTOR, mCb);
+
+        verify(mService).registerConnectivityDiagnosticsCallback(
+                any(ConnectivityDiagnosticsBinder.class), eq(request));
+        assertTrue(ConnectivityDiagnosticsManager.sCallbacks.containsKey(mCb));
+    }
+
+    @Test
+    public void testRegisterDuplicateConnectivityDiagnosticsCallback() throws Exception {
+        final NetworkRequest request = new NetworkRequest.Builder().build();
+
+        mManager.registerConnectivityDiagnosticsCallback(request, INLINE_EXECUTOR, mCb);
+
+        try {
+            mManager.registerConnectivityDiagnosticsCallback(request, INLINE_EXECUTOR, mCb);
+            fail("Duplicate callback registration should fail");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    @Test
+    public void testUnregisterConnectivityDiagnosticsCallback() throws Exception {
+        final NetworkRequest request = new NetworkRequest.Builder().build();
+        mManager.registerConnectivityDiagnosticsCallback(request, INLINE_EXECUTOR, mCb);
+
+        mManager.unregisterConnectivityDiagnosticsCallback(mCb);
+
+        verify(mService).unregisterConnectivityDiagnosticsCallback(
+                any(ConnectivityDiagnosticsBinder.class));
+        assertFalse(ConnectivityDiagnosticsManager.sCallbacks.containsKey(mCb));
+
+        // verify that re-registering is successful
+        mManager.registerConnectivityDiagnosticsCallback(request, INLINE_EXECUTOR, mCb);
+        verify(mService, times(2)).registerConnectivityDiagnosticsCallback(
+                any(ConnectivityDiagnosticsBinder.class), eq(request));
+        assertTrue(ConnectivityDiagnosticsManager.sCallbacks.containsKey(mCb));
+    }
+
+    @Test
+    public void testUnregisterUnknownConnectivityDiagnosticsCallback() throws Exception {
+        mManager.unregisterConnectivityDiagnosticsCallback(mCb);
+
+        verifyNoMoreInteractions(mService);
+    }
 }
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index a0a1352..5592cd7 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -139,6 +139,7 @@
 import android.net.ConnectivityManager.PacketKeepaliveCallback;
 import android.net.ConnectivityManager.TooManyRequestsException;
 import android.net.ConnectivityThread;
+import android.net.IConnectivityDiagnosticsCallback;
 import android.net.IDnsResolver;
 import android.net.IIpConnectivityMetrics;
 import android.net.INetd;
@@ -180,6 +181,7 @@
 import android.os.ConditionVariable;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.IBinder;
 import android.os.INetworkManagementService;
 import android.os.Looper;
 import android.os.Parcel;
@@ -210,6 +212,7 @@
 import com.android.internal.util.WakeupMessage;
 import com.android.internal.util.test.BroadcastInterceptingContext;
 import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.server.ConnectivityService.ConnectivityDiagnosticsCallbackInfo;
 import com.android.server.connectivity.ConnectivityConstants;
 import com.android.server.connectivity.DefaultNetworkMetrics;
 import com.android.server.connectivity.IpConnectivityMetrics;
@@ -322,6 +325,8 @@
     @Mock UserManager mUserManager;
     @Mock NotificationManager mNotificationManager;
     @Mock AlarmManager mAlarmManager;
+    @Mock IConnectivityDiagnosticsCallback mConnectivityDiagnosticsCallback;
+    @Mock IBinder mIBinder;
 
     private ArgumentCaptor<ResolverParamsParcel> mResolverParamsParcelCaptor =
             ArgumentCaptor.forClass(ResolverParamsParcel.class);
@@ -6355,4 +6360,70 @@
                 UserHandle.getAppId(uid));
         return packageInfo;
     }
+
+    @Test
+    public void testRegisterConnectivityDiagnosticsCallbackInvalidRequest() throws Exception {
+        final NetworkRequest request =
+                new NetworkRequest(
+                        new NetworkCapabilities(), TYPE_ETHERNET, 0, NetworkRequest.Type.NONE);
+        try {
+            mService.registerConnectivityDiagnosticsCallback(
+                    mConnectivityDiagnosticsCallback, request);
+            fail("registerConnectivityDiagnosticsCallback should throw on invalid NetworkRequest");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    @Test
+    public void testRegisterUnregisterConnectivityDiagnosticsCallback() throws Exception {
+        final NetworkRequest wifiRequest =
+                new NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI).build();
+
+        when(mConnectivityDiagnosticsCallback.asBinder()).thenReturn(mIBinder);
+
+        mService.registerConnectivityDiagnosticsCallback(
+                mConnectivityDiagnosticsCallback, wifiRequest);
+
+        verify(mIBinder, timeout(TIMEOUT_MS))
+                .linkToDeath(any(ConnectivityDiagnosticsCallbackInfo.class), anyInt());
+        assertTrue(
+                mService.mConnectivityDiagnosticsCallbacks.containsKey(
+                        mConnectivityDiagnosticsCallback));
+
+        mService.unregisterConnectivityDiagnosticsCallback(mConnectivityDiagnosticsCallback);
+        verify(mIBinder, timeout(TIMEOUT_MS))
+                .unlinkToDeath(any(ConnectivityDiagnosticsCallbackInfo.class), anyInt());
+        assertFalse(
+                mService.mConnectivityDiagnosticsCallbacks.containsKey(
+                        mConnectivityDiagnosticsCallback));
+        verify(mConnectivityDiagnosticsCallback, atLeastOnce()).asBinder();
+    }
+
+    @Test
+    public void testRegisterDuplicateConnectivityDiagnosticsCallback() throws Exception {
+        final NetworkRequest wifiRequest =
+                new NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI).build();
+        when(mConnectivityDiagnosticsCallback.asBinder()).thenReturn(mIBinder);
+
+        mService.registerConnectivityDiagnosticsCallback(
+                mConnectivityDiagnosticsCallback, wifiRequest);
+
+        verify(mIBinder, timeout(TIMEOUT_MS))
+                .linkToDeath(any(ConnectivityDiagnosticsCallbackInfo.class), anyInt());
+        verify(mConnectivityDiagnosticsCallback).asBinder();
+        assertTrue(
+                mService.mConnectivityDiagnosticsCallbacks.containsKey(
+                        mConnectivityDiagnosticsCallback));
+
+        // Register the same callback again
+        mService.registerConnectivityDiagnosticsCallback(
+                mConnectivityDiagnosticsCallback, wifiRequest);
+
+        // Block until all other events are done processing.
+        HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
+
+        assertTrue(
+                mService.mConnectivityDiagnosticsCallbacks.containsKey(
+                        mConnectivityDiagnosticsCallback));
+    }
 }