Merge "Ported TextClassificationManagerPerfTest from Settings to DeviceConfig."
diff --git a/Android.bp b/Android.bp
index f48e6fe..12bc906 100644
--- a/Android.bp
+++ b/Android.bp
@@ -402,7 +402,7 @@
         "ext",
         "unsupportedappusage",
         "framework-media-stubs-systemapi",
-        "framework_mediaprovider_stubs",
+        "framework-mediaprovider-stubs-systemapi",
         "framework-tethering",
         "framework-telephony-stubs",
     ],
@@ -491,14 +491,11 @@
     apex_available: ["//apex_available:platform"],
     visibility: [
         "//frameworks/base",
-        // TODO(b/144149403) remove the below lines
+        // TODO(b/147128803) remove the below lines
         "//frameworks/base/apex/appsearch/framework",
         "//frameworks/base/apex/blobstore/framework",
         "//frameworks/base/apex/jobscheduler/framework",
-        "//frameworks/base/apex/permission/framework",
         "//frameworks/base/apex/statsd/service",
-        "//frameworks/base/telephony",
-        "//frameworks/opt/net/wifi/service",
     ],
 }
 
@@ -517,9 +514,9 @@
     installable: false, // this lib is a build-only library
     static_libs: [
         "framework-minus-apex",
-        "framework-media-stubs-systemapi",
-        "framework_mediaprovider_stubs",
         "framework-appsearch", // TODO(b/146218515): should be framework-appsearch-stubs
+        "framework-media-stubs-systemapi",
+        "framework-mediaprovider-stubs-systemapi",
         "framework-permission-stubs-systemapi",
         "framework-sdkextensions-stubs-systemapi",
         // TODO(b/146167933): Use framework-statsd-stubs instead.
@@ -656,6 +653,33 @@
     output_extension: "srcjar",
 }
 
+gensrcs {
+    name: "framework-cppstream-protos",
+    depfile: true,
+
+    tools: [
+        "aprotoc",
+        "protoc-gen-cppstream",
+    ],
+
+    cmd: "mkdir -p $(genDir) " +
+        "&& $(location aprotoc) " +
+        "  --plugin=$(location protoc-gen-cppstream) " +
+        "  --dependency_out=$(depfile) " +
+        "  --cppstream_out=$(genDir) " +
+        "  -Iexternal/protobuf/src " +
+        "  -I . " +
+        "  $(in)",
+
+    srcs: [
+        ":ipconnectivity-proto-src",
+        "core/proto/**/*.proto",
+        "libs/incident/**/*.proto",
+    ],
+
+    output_extension: "proto.h",
+}
+
 filegroup {
     name: "framework-annotations",
     srcs: [
@@ -945,7 +969,7 @@
     name: "incremental_manager_aidl",
     srcs: [
         "core/java/android/os/incremental/IIncrementalManager.aidl",
-        "core/java/android/os/incremental/IIncrementalManagerNative.aidl",
+        "core/java/android/os/incremental/IIncrementalService.aidl",
         "core/java/android/os/incremental/IncrementalNewFileParams.aidl",
         "core/java/android/os/incremental/IncrementalSignature.aidl",
     ],
@@ -1013,43 +1037,6 @@
     },
 }
 
-gensrcs {
-    name: "gen-platform-proto-constants",
-    depfile: true,
-
-    tools: [
-        "aprotoc",
-        "protoc-gen-cppstream",
-    ],
-
-    srcs: [
-        "core/proto/android/os/backtrace.proto",
-        "core/proto/android/os/batterytype.proto",
-        "core/proto/android/os/cpufreq.proto",
-        "core/proto/android/os/cpuinfo.proto",
-        "core/proto/android/os/data.proto",
-        "core/proto/android/os/kernelwake.proto",
-        "core/proto/android/os/pagetypeinfo.proto",
-        "core/proto/android/os/procrank.proto",
-        "core/proto/android/os/ps.proto",
-        "core/proto/android/os/system_properties.proto",
-        "core/proto/android/util/event_log_tags.proto",
-        "core/proto/android/util/log.proto",
-    ],
-
-    // Append protoc-gen-cppstream tool's PATH otherwise aprotoc can't find the plugin tool
-    cmd: "mkdir -p $(genDir) " +
-        "&& $(location aprotoc) " +
-        "  --plugin=$(location protoc-gen-cppstream) " +
-        "  --dependency_out=$(depfile) " +
-        "  --cppstream_out=$(genDir) " +
-        "  -Iexternal/protobuf/src " +
-        "  -I . " +
-        "  $(in)",
-
-    output_extension: "proto.h",
-}
-
 
 subdirs = [
     "cmds/*",
diff --git a/apct-tests/perftests/blobstore/Android.bp b/apct-tests/perftests/blobstore/Android.bp
new file mode 100644
index 0000000..be5072c
--- /dev/null
+++ b/apct-tests/perftests/blobstore/Android.bp
@@ -0,0 +1,28 @@
+// 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.
+
+android_test {
+  name: "BlobStorePerfTests",
+  srcs: ["src/**/*.java"],
+  static_libs: [
+    "BlobStoreTestUtils",
+    "androidx.test.rules",
+    "androidx.annotation_annotation",
+    "apct-perftests-utils",
+    "ub-uiautomator",
+  ],
+  platform_apis: true,
+  test_suites: ["device-tests"],
+  certificate: "platform",
+}
\ No newline at end of file
diff --git a/apct-tests/perftests/blobstore/AndroidManifest.xml b/apct-tests/perftests/blobstore/AndroidManifest.xml
new file mode 100644
index 0000000..21d0726
--- /dev/null
+++ b/apct-tests/perftests/blobstore/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.perftests.blob">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.perftests.blob"/>
+
+</manifest>
\ No newline at end of file
diff --git a/apct-tests/perftests/blobstore/AndroidTest.xml b/apct-tests/perftests/blobstore/AndroidTest.xml
new file mode 100644
index 0000000..19456c6
--- /dev/null
+++ b/apct-tests/perftests/blobstore/AndroidTest.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+<configuration description="Runs BlobStorePerfTests metric instrumentation.">
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="apct-metric-instrumentation" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="BlobStorePerfTests.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.perftests.blob" />
+        <option name="hidden-api-checks" value="false"/>
+    </test>
+</configuration>
\ No newline at end of file
diff --git a/apct-tests/perftests/blobstore/src/com/android/perftests/blob/AtraceUtils.java b/apct-tests/perftests/blobstore/src/com/android/perftests/blob/AtraceUtils.java
new file mode 100644
index 0000000..0208dab
--- /dev/null
+++ b/apct-tests/perftests/blobstore/src/com/android/perftests/blob/AtraceUtils.java
@@ -0,0 +1,120 @@
+/*
+ * 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.perftests.blob;
+
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.os.ParcelFileDescriptor;
+import android.perftests.utils.TraceMarkParser;
+import android.perftests.utils.TraceMarkParser.TraceMarkSlice;
+import android.support.test.uiautomator.UiDevice;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.List;
+import java.util.function.BiConsumer;
+
+// Copy of com.android.frameworks.perftests.am.util.AtraceUtils. TODO: avoid this duplication.
+public class AtraceUtils {
+    private static final String TAG = "AtraceUtils";
+    private static final boolean VERBOSE = true;
+
+    private static final String ATRACE_START = "atrace --async_start -b %d -c %s";
+    private static final String ATRACE_DUMP = "atrace --async_dump";
+    private static final String ATRACE_STOP = "atrace --async_stop";
+    private static final int DEFAULT_ATRACE_BUF_SIZE = 1024;
+
+    private UiAutomation mAutomation;
+    private static AtraceUtils sUtils = null;
+    private boolean mStarted = false;
+
+    private AtraceUtils(Instrumentation instrumentation) {
+        mAutomation = instrumentation.getUiAutomation();
+    }
+
+    public static AtraceUtils getInstance(Instrumentation instrumentation) {
+        if (sUtils == null) {
+            sUtils = new AtraceUtils(instrumentation);
+        }
+        return sUtils;
+    }
+
+    /**
+     * @param categories The list of the categories to trace, separated with space.
+     */
+    public void startTrace(String categories) {
+        synchronized (this) {
+            if (mStarted) {
+                throw new IllegalStateException("atrace already started");
+            }
+            runShellCommand(String.format(
+                    ATRACE_START, DEFAULT_ATRACE_BUF_SIZE, categories));
+            mStarted = true;
+        }
+    }
+
+    public void stopTrace() {
+        synchronized (this) {
+            mStarted = false;
+            runShellCommand(ATRACE_STOP);
+        }
+    }
+
+    private String runShellCommand(String cmd) {
+        try {
+            return UiDevice.getInstance(
+                    InstrumentationRegistry.getInstrumentation()).executeShellCommand(cmd);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * @param parser The function that can accept the buffer of atrace dump and parse it.
+     * @param handler The parse result handler
+     */
+    public void performDump(TraceMarkParser parser,
+            BiConsumer<String, List<TraceMarkSlice>> handler) {
+        parser.reset();
+        try {
+            if (VERBOSE) {
+                Log.i(TAG, "Collecting atrace dump...");
+            }
+            writeDataToBuf(mAutomation.executeShellCommand(ATRACE_DUMP), parser);
+        } catch (IOException e) {
+            Log.e(TAG, "Error in reading dump", e);
+        }
+        parser.forAllSlices(handler);
+    }
+
+    // The given file descriptor here will be closed by this function
+    private void writeDataToBuf(ParcelFileDescriptor pfDescriptor,
+            TraceMarkParser parser) throws IOException {
+        InputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(pfDescriptor);
+        try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
+            String line;
+            while ((line = reader.readLine()) != null) {
+                parser.visit(line);
+            }
+        }
+    }
+}
diff --git a/apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java b/apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java
new file mode 100644
index 0000000..8e0ea98
--- /dev/null
+++ b/apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java
@@ -0,0 +1,146 @@
+/*
+ * 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.perftests.blob;
+
+import android.app.blob.BlobStoreManager;
+import android.content.Context;
+import android.perftests.utils.ManualBenchmarkState;
+import android.perftests.utils.PerfManualStatusReporter;
+import android.perftests.utils.TraceMarkParser;
+import android.perftests.utils.TraceMarkParser.TraceMarkSlice;
+import android.support.test.uiautomator.UiDevice;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.utils.blob.DummyBlobData;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+@LargeTest
+@RunWith(Parameterized.class)
+public class BlobStorePerfTests {
+    // From frameworks/native/cmds/atrace/atrace.cpp
+    private static final String ATRACE_CATEGORY_SYSTEM_SERVER = "ss";
+    // From f/b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java
+    private static final String ATRACE_COMPUTE_DIGEST_PREFIX = "computeBlobDigest-";
+
+    private Context mContext;
+    private BlobStoreManager mBlobStoreManager;
+    private AtraceUtils mAtraceUtils;
+    private ManualBenchmarkState mState;
+
+    @Rule
+    public PerfManualStatusReporter mPerfManualStatusReporter = new PerfManualStatusReporter();
+
+    @Parameterized.Parameter(0)
+    public int fileSizeInMb;
+
+    @Parameterized.Parameters(name = "{0}MB")
+    public static Collection<Object[]> getParameters() {
+        return Arrays.asList(new Object[][] {
+                { 25 },
+                { 50 },
+                { 100 },
+                { 200 },
+        });
+    }
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        mBlobStoreManager = (BlobStoreManager) mContext.getSystemService(
+                Context.BLOB_STORE_SERVICE);
+        mAtraceUtils = AtraceUtils.getInstance(InstrumentationRegistry.getInstrumentation());
+        mState = mPerfManualStatusReporter.getBenchmarkState();
+    }
+
+    @After
+    public void tearDown() {
+        // TODO: Add a blob_store shell command to trigger idle maintenance to avoid hardcoding
+        // job id like this.
+        // From BlobStoreConfig.IDLE_JOB_ID = 191934935.
+        runShellCommand("cmd jobscheduler run -f android 191934935");
+    }
+
+    @Test
+    public void testComputeDigest() throws Exception {
+        mAtraceUtils.startTrace(ATRACE_CATEGORY_SYSTEM_SERVER);
+        try {
+            final List<Long> durations = new ArrayList<>();
+            final DummyBlobData blobData = prepareDataBlob(fileSizeInMb);
+            final TraceMarkParser parser = new TraceMarkParser(
+                    line -> line.name.startsWith(ATRACE_COMPUTE_DIGEST_PREFIX));
+            while (mState.keepRunning(durations)) {
+                commitBlob(blobData);
+
+                durations.clear();
+                collectDigestDurationsFromTrace(parser, durations);
+                // TODO: get and delete blobId before next iteration.
+            }
+        } finally {
+            mAtraceUtils.stopTrace();
+        }
+    }
+
+    private void collectDigestDurationsFromTrace(TraceMarkParser parser, List<Long> durations) {
+        mAtraceUtils.performDump(parser, (key, slices) -> {
+            for (TraceMarkSlice slice : slices) {
+                durations.add(TimeUnit.MICROSECONDS.toNanos(slice.getDurationInMicroseconds()));
+            }
+        });
+    }
+
+    private DummyBlobData prepareDataBlob(int fileSizeInMb) throws Exception {
+        final DummyBlobData blobData = new DummyBlobData(mContext,
+                fileSizeInMb * 1024 * 1024 /* bytes */);
+        blobData.prepare();
+        return blobData;
+    }
+
+    private void commitBlob(DummyBlobData blobData) throws Exception {
+        final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
+        try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) {
+            blobData.writeToSession(session);
+            final CompletableFuture<Integer> callback = new CompletableFuture<>();
+            session.commit(mContext.getMainExecutor(), callback::complete);
+            // Ignore commit callback result.
+            callback.get();
+        }
+    }
+
+    private String runShellCommand(String cmd) {
+        try {
+            return UiDevice.getInstance(
+                    InstrumentationRegistry.getInstrumentation()).executeShellCommand(cmd);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/apex/blobstore/TEST_MAPPING b/apex/blobstore/TEST_MAPPING
index cfe19a5..25a1537 100644
--- a/apex/blobstore/TEST_MAPPING
+++ b/apex/blobstore/TEST_MAPPING
@@ -4,7 +4,7 @@
       "name": "CtsBlobStoreTestCases"
     },
     {
-      "name": "FrameworksServicesTests",
+      "name": "FrameworksMockingServicesTests",
       "options": [
         {
           "include-filter": "com.android.server.blob"
diff --git a/apex/blobstore/framework/java/android/app/blob/BlobHandle.java b/apex/blobstore/framework/java/android/app/blob/BlobHandle.java
index f110b36..d339afa 100644
--- a/apex/blobstore/framework/java/android/app/blob/BlobHandle.java
+++ b/apex/blobstore/framework/java/android/app/blob/BlobHandle.java
@@ -257,6 +257,11 @@
         return Base64.encodeToString(digest, Base64.NO_WRAP);
     }
 
+    /** @hide */
+    public boolean isExpired() {
+        return expiryTimeMillis != 0 && expiryTimeMillis < System.currentTimeMillis();
+    }
+
     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 aba3e8c..c12e0ec 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
@@ -64,9 +64,9 @@
 
     private final Context mContext;
 
-    public final long blobId;
-    public final BlobHandle blobHandle;
-    public final int userId;
+    private final long mBlobId;
+    private final BlobHandle mBlobHandle;
+    private final int mUserId;
 
     @GuardedBy("mMetadataLock")
     private final ArraySet<Committer> mCommitters = new ArraySet<>();
@@ -90,9 +90,21 @@
 
     BlobMetadata(Context context, long blobId, BlobHandle blobHandle, int userId) {
         mContext = context;
-        this.blobId = blobId;
-        this.blobHandle = blobHandle;
-        this.userId = userId;
+        this.mBlobId = blobId;
+        this.mBlobHandle = blobHandle;
+        this.mUserId = userId;
+    }
+
+    long getBlobId() {
+        return mBlobId;
+    }
+
+    BlobHandle getBlobHandle() {
+        return mBlobHandle;
+    }
+
+    int getUserId() {
+        return mUserId;
     }
 
     void addCommitter(@NonNull Committer committer) {
@@ -159,7 +171,7 @@
 
     boolean hasLeases() {
         synchronized (mMetadataLock) {
-            return mLeasees.isEmpty();
+            return !mLeasees.isEmpty();
         }
     }
 
@@ -196,7 +208,7 @@
 
     File getBlobFile() {
         if (mBlobFile == null) {
-            mBlobFile = BlobStoreConfig.getBlobFile(blobId);
+            mBlobFile = BlobStoreConfig.getBlobFile(mBlobId);
         }
         return mBlobFile;
     }
@@ -244,7 +256,7 @@
     void dump(IndentingPrintWriter fout, DumpArgs dumpArgs) {
         fout.println("blobHandle:");
         fout.increaseIndent();
-        blobHandle.dump(fout, dumpArgs.shouldDumpFull());
+        mBlobHandle.dump(fout, dumpArgs.shouldDumpFull());
         fout.decreaseIndent();
 
         fout.println("Committers:");
@@ -274,11 +286,11 @@
 
     void writeToXml(XmlSerializer out) throws IOException {
         synchronized (mMetadataLock) {
-            XmlUtils.writeLongAttribute(out, ATTR_ID, blobId);
-            XmlUtils.writeIntAttribute(out, ATTR_USER_ID, userId);
+            XmlUtils.writeLongAttribute(out, ATTR_ID, mBlobId);
+            XmlUtils.writeIntAttribute(out, ATTR_USER_ID, mUserId);
 
             out.startTag(null, TAG_BLOB_HANDLE);
-            blobHandle.writeToXml(out);
+            mBlobHandle.writeToXml(out);
             out.endTag(null, TAG_BLOB_HANDLE);
 
             for (int i = 0, count = mCommitters.size(); i < count; ++i) {
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java
index eb414b0..ba2e559 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java
@@ -18,12 +18,15 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Environment;
+import android.util.Log;
 import android.util.Slog;
 
 import java.io.File;
+import java.util.concurrent.TimeUnit;
 
 class BlobStoreConfig {
     public static final String TAG = "BlobStore";
+    public static final boolean LOGV = Log.isLoggable(TAG, Log.VERBOSE);
 
     public static final int CURRENT_XML_VERSION = 1;
 
@@ -32,6 +35,20 @@
     private static final String SESSIONS_INDEX_FILE_NAME = "sessions_index.xml";
     private static final String BLOBS_INDEX_FILE_NAME = "blobs_index.xml";
 
+    /**
+     * Job Id for idle maintenance job ({@link BlobStoreIdleJobService}).
+     */
+    public static final int IDLE_JOB_ID = 0xB70B1D7; // 191934935L
+    /**
+     * Max time period (in millis) between each idle maintenance job run.
+     */
+    public static final long IDLE_JOB_PERIOD_MILLIS = TimeUnit.DAYS.toMillis(1);
+
+    /**
+     * Timeout in millis after which sessions with no updates will be deleted.
+     */
+    public static final long SESSION_EXPIRY_TIMEOUT_MILLIS = TimeUnit.DAYS.toMillis(7);
+
     @Nullable
     public static File prepareBlobFile(long sessionId) {
         final File blobsDir = prepareBlobsDir();
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreIdleJobService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreIdleJobService.java
new file mode 100644
index 0000000..460e776
--- /dev/null
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreIdleJobService.java
@@ -0,0 +1,70 @@
+/*
+ * 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 static com.android.server.blob.BlobStoreConfig.IDLE_JOB_ID;
+import static com.android.server.blob.BlobStoreConfig.IDLE_JOB_PERIOD_MILLIS;
+import static com.android.server.blob.BlobStoreConfig.LOGV;
+import static com.android.server.blob.BlobStoreConfig.TAG;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.AsyncTask;
+import android.util.Slog;
+
+import com.android.server.LocalServices;
+
+/**
+ * Maintenance job to clean up stale sessions and blobs.
+ */
+public class BlobStoreIdleJobService extends JobService {
+    @Override
+    public boolean onStartJob(final JobParameters params) {
+        AsyncTask.execute(() -> {
+            final BlobStoreManagerInternal blobStoreManagerInternal = LocalServices.getService(
+                    BlobStoreManagerInternal.class);
+            blobStoreManagerInternal.onIdleMaintenance();
+            jobFinished(params, false);
+        });
+        return false;
+    }
+
+    @Override
+    public boolean onStopJob(final JobParameters params) {
+        Slog.d(TAG, "Idle maintenance job is stopped; id=" + params.getJobId()
+                + ", reason=" + JobParameters.getReasonCodeDescription(params.getStopReason()));
+        return false;
+    }
+
+    static void schedule(Context context) {
+        final JobScheduler jobScheduler = (JobScheduler) context.getSystemService(
+                Context.JOB_SCHEDULER_SERVICE);
+        final JobInfo job = new JobInfo.Builder(IDLE_JOB_ID,
+                new ComponentName(context, BlobStoreIdleJobService.class))
+                        .setRequiresDeviceIdle(true)
+                        .setRequiresCharging(true)
+                        .setPeriodic(IDLE_JOB_PERIOD_MILLIS)
+                        .build();
+        jobScheduler.schedule(job);
+        if (LOGV) {
+            Slog.v(TAG, "Scheduling the idle maintenance job");
+        }
+    }
+}
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerInternal.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerInternal.java
new file mode 100644
index 0000000..5358245
--- /dev/null
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerInternal.java
@@ -0,0 +1,28 @@
+/*
+ * 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;
+
+/**
+ * BlobStoreManager local system service interface.
+ *
+ * Only for use within the system server.
+ */
+public abstract class BlobStoreManagerInternal {
+    /**
+     * Triggered from idle maintenance job to cleanup stale blobs and sessions.
+     */
+    public abstract void onIdleMaintenance();
+}
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 13f095e..0ba34ca 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
@@ -28,6 +28,8 @@
 import static android.os.UserHandle.USER_NULL;
 
 import static com.android.server.blob.BlobStoreConfig.CURRENT_XML_VERSION;
+import static com.android.server.blob.BlobStoreConfig.LOGV;
+import static com.android.server.blob.BlobStoreConfig.SESSION_EXPIRY_TIMEOUT_MILLIS;
 import static com.android.server.blob.BlobStoreConfig.TAG;
 import static com.android.server.blob.BlobStoreSession.STATE_ABANDONED;
 import static com.android.server.blob.BlobStoreSession.STATE_COMMITTED;
@@ -61,6 +63,7 @@
 import android.os.UserHandle;
 import android.os.UserManagerInternal;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.ExceptionUtils;
 import android.util.LongSparseArray;
@@ -94,8 +97,10 @@
 import java.io.PrintWriter;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 
 /**
  * Service responsible for maintaining and facilitating access to data blobs published by apps.
@@ -115,6 +120,10 @@
     @GuardedBy("mBlobsLock")
     private final SparseArray<ArrayMap<BlobHandle, BlobMetadata>> mBlobsMap = new SparseArray<>();
 
+    // Contains all ids that are currently in use.
+    @GuardedBy("mBlobsLock")
+    private final ArraySet<Long> mKnownBlobIds = new ArraySet<>();
+
     private final Context mContext;
     private final Handler mHandler;
     private final Injector mInjector;
@@ -151,6 +160,7 @@
     @Override
     public void onStart() {
         publishBinderService(Context.BLOB_STORE_SERVICE, new Stub());
+        LocalServices.addService(BlobStoreManagerInternal.class, new LocalService());
 
         mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
         registerReceivers();
@@ -164,6 +174,8 @@
                 readBlobSessionsLocked(allPackages);
                 readBlobsInfoLocked(allPackages);
             }
+        } else if (phase == PHASE_BOOT_COMPLETED) {
+            BlobStoreIdleJobService.schedule(mContext);
         }
     }
 
@@ -215,6 +227,40 @@
         }
     }
 
+    @VisibleForTesting
+    void addKnownIdsForTest(long... knownIds) {
+        synchronized (mBlobsLock) {
+            for (long id : knownIds) {
+                mKnownBlobIds.add(id);
+            }
+        }
+    }
+
+    @VisibleForTesting
+    Set<Long> getKnownIdsForTest() {
+        synchronized (mBlobsLock) {
+            return mKnownBlobIds;
+        }
+    }
+
+    @GuardedBy("mBlobsLock")
+    private void addSessionForUserLocked(BlobStoreSession session, int userId) {
+        getUserSessionsLocked(userId).put(session.getSessionId(), session);
+        mKnownBlobIds.add(session.getSessionId());
+    }
+
+    @GuardedBy("mBlobsLock")
+    private void addBlobForUserLocked(BlobMetadata blobMetadata, int userId) {
+        addBlobForUserLocked(blobMetadata, getUserBlobsLocked(userId));
+    }
+
+    @GuardedBy("mBlobsLock")
+    private void addBlobForUserLocked(BlobMetadata blobMetadata,
+            ArrayMap<BlobHandle, BlobMetadata> userBlobs) {
+        userBlobs.put(blobMetadata.getBlobHandle(), blobMetadata);
+        mKnownBlobIds.add(blobMetadata.getBlobId());
+    }
+
     private long createSessionInternal(BlobHandle blobHandle,
             int callingUid, String callingPackage) {
         synchronized (mBlobsLock) {
@@ -223,7 +269,11 @@
             final BlobStoreSession session = new BlobStoreSession(mContext,
                     sessionId, blobHandle, callingUid, callingPackage,
                     mSessionStateChangeListener);
-            getUserSessionsLocked(UserHandle.getUserId(callingUid)).put(sessionId, session);
+            addSessionForUserLocked(session, UserHandle.getUserId(callingUid));
+            if (LOGV) {
+                Slog.v(TAG, "Created session for " + blobHandle
+                        + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage);
+            }
             writeBlobSessionsAsync();
             return sessionId;
         }
@@ -251,7 +301,10 @@
                     callingUid, callingPackage);
             session.open();
             session.abandon();
-
+            if (LOGV) {
+                Slog.v(TAG, "Deleted session with id " + sessionId
+                        + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage);
+            }
             writeBlobSessionsAsync();
         }
     }
@@ -286,6 +339,10 @@
             }
             blobMetadata.addLeasee(callingPackage, callingUid,
                     descriptionResId, leaseExpiryTimeMillis);
+            if (LOGV) {
+                Slog.v(TAG, "Acquired lease on " + blobHandle
+                        + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage);
+            }
             writeBlobsInfoAsync();
         }
     }
@@ -301,6 +358,10 @@
                         + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage);
             }
             blobMetadata.removeLeasee(callingPackage, callingUid);
+            if (LOGV) {
+                Slog.v(TAG, "Released lease on " + blobHandle
+                        + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage);
+            }
             writeBlobsInfoAsync();
         }
     }
@@ -329,6 +390,10 @@
                     session.getSessionFile().delete();
                     getUserSessionsLocked(UserHandle.getUserId(session.getOwnerUid()))
                             .remove(session.getSessionId());
+                    mKnownBlobIds.remove(session.getSessionId());
+                    if (LOGV) {
+                        Slog.v(TAG, "Session is invalid; deleted " + session);
+                    }
                     break;
                 case STATE_COMMITTED:
                     session.verifyBlobData();
@@ -340,7 +405,7 @@
                     if (blob == null) {
                         blob = new BlobMetadata(mContext,
                                 session.getSessionId(), session.getBlobHandle(), userId);
-                        userBlobs.put(session.getBlobHandle(), blob);
+                        addBlobForUserLocked(blob, userBlobs);
                     }
                     final Committer newCommitter = new Committer(session.getOwnerPackageName(),
                             session.getOwnerUid(), session.getBlobAccessMode());
@@ -355,6 +420,9 @@
                     }
                     getUserSessionsLocked(UserHandle.getUserId(session.getOwnerUid()))
                             .remove(session.getSessionId());
+                    if (LOGV) {
+                        Slog.v(TAG, "Successfully committed session " + session);
+                    }
                     break;
                 default:
                     Slog.wtf(TAG, "Invalid session state: "
@@ -397,6 +465,9 @@
             out.endTag(null, TAG_SESSIONS);
             out.endDocument();
             sessionsIndexFile.finishWrite(fos);
+            if (LOGV) {
+                Slog.v(TAG, "Finished persisting sessions data");
+            }
         } catch (Exception e) {
             sessionsIndexFile.failWrite(fos);
             Slog.wtf(TAG, "Error writing sessions data", e);
@@ -437,8 +508,8 @@
                     if (userPackages != null
                             && session.getOwnerPackageName().equals(
                                     userPackages.get(session.getOwnerUid()))) {
-                        getUserSessionsLocked(UserHandle.getUserId(session.getOwnerUid())).put(
-                                session.getSessionId(), session);
+                        addSessionForUserLocked(session,
+                                UserHandle.getUserId(session.getOwnerUid()));
                     } else {
                         // Unknown package or the session data does not belong to this package.
                         session.getSessionFile().delete();
@@ -446,6 +517,9 @@
                     mCurrentMaxSessionId = Math.max(mCurrentMaxSessionId, session.getSessionId());
                 }
             }
+            if (LOGV) {
+                Slog.v(TAG, "Finished reading sessions data");
+            }
         } catch (Exception e) {
             Slog.wtf(TAG, "Error reading sessions data", e);
         }
@@ -479,6 +553,9 @@
             out.endTag(null, TAG_BLOBS);
             out.endDocument();
             blobsIndexFile.finishWrite(fos);
+            if (LOGV) {
+                Slog.v(TAG, "Finished persisting blobs data");
+            }
         } catch (Exception e) {
             blobsIndexFile.failWrite(fos);
             Slog.wtf(TAG, "Error writing blobs data", e);
@@ -510,18 +587,21 @@
 
                 if (TAG_BLOB.equals(in.getName())) {
                     final BlobMetadata blobMetadata = BlobMetadata.createFromXml(mContext, in);
-                    final SparseArray<String> userPackages = allPackages.get(blobMetadata.userId);
+                    final SparseArray<String> userPackages = allPackages.get(
+                            blobMetadata.getUserId());
                     if (userPackages == null) {
                         blobMetadata.getBlobFile().delete();
                     } else {
-                        getUserBlobsLocked(blobMetadata.userId).put(
-                                blobMetadata.blobHandle, blobMetadata);
+                        addBlobForUserLocked(blobMetadata, blobMetadata.getUserId());
                         blobMetadata.removeInvalidCommitters(userPackages);
                         blobMetadata.removeInvalidLeasees(userPackages);
                     }
-                    mCurrentMaxSessionId = Math.max(mCurrentMaxSessionId, blobMetadata.blobId);
+                    mCurrentMaxSessionId = Math.max(mCurrentMaxSessionId, blobMetadata.getBlobId());
                 }
             }
+            if (LOGV) {
+                Slog.v(TAG, "Finished reading blobs data");
+            }
         } catch (Exception e) {
             Slog.wtf(TAG, "Error reading blobs data", e);
         }
@@ -614,6 +694,7 @@
                 if (session.getOwnerUid() == uid
                         && session.getOwnerPackageName().equals(packageName)) {
                     session.getSessionFile().delete();
+                    mKnownBlobIds.remove(session.getSessionId());
                     indicesToRemove.add(i);
                 }
             }
@@ -633,6 +714,7 @@
                 // Delete the blob if it doesn't have any active leases.
                 if (!blobMetadata.hasLeases()) {
                     blobMetadata.getBlobFile().delete();
+                    mKnownBlobIds.remove(blobMetadata.getBlobId());
                     indicesToRemove.add(i);
                 }
             }
@@ -640,6 +722,10 @@
                 userBlobs.removeAt(indicesToRemove.get(i));
             }
             writeBlobsInfoAsync();
+            if (LOGV) {
+                Slog.v(TAG, "Removed blobs data associated with pkg="
+                        + packageName + ", uid=" + uid);
+            }
         }
     }
 
@@ -651,6 +737,7 @@
                 for (int i = 0, count = userSessions.size(); i < count; ++i) {
                     final BlobStoreSession session = userSessions.valueAt(i);
                     session.getSessionFile().delete();
+                    mKnownBlobIds.remove(session.getSessionId());
                 }
             }
 
@@ -660,11 +747,107 @@
                 for (int i = 0, count = userBlobs.size(); i < count; ++i) {
                     final BlobMetadata blobMetadata = userBlobs.valueAt(i);
                     blobMetadata.getBlobFile().delete();
+                    mKnownBlobIds.remove(blobMetadata.getBlobId());
                 }
             }
+            if (LOGV) {
+                Slog.v(TAG, "Removed blobs data in user " + userId);
+            }
         }
     }
 
+    @GuardedBy("mBlobsLock")
+    @VisibleForTesting
+    void handleIdleMaintenanceLocked() {
+        // Cleanup any left over data on disk that is not part of index.
+        final ArrayList<Long> deletedBlobIds = new ArrayList<>();
+        final ArrayList<File> filesToDelete = new ArrayList<>();
+        final File blobsDir = BlobStoreConfig.getBlobsDir();
+        if (blobsDir.exists()) {
+            for (File file : blobsDir.listFiles()) {
+                try {
+                    final long id = Long.parseLong(file.getName());
+                    if (mKnownBlobIds.indexOf(id) < 0) {
+                        filesToDelete.add(file);
+                        deletedBlobIds.add(id);
+                    }
+                } catch (NumberFormatException e) {
+                    Slog.wtf(TAG, "Error parsing the file name: " + file, e);
+                    filesToDelete.add(file);
+                }
+            }
+            for (int i = 0, count = filesToDelete.size(); i < count; ++i) {
+                filesToDelete.get(i).delete();
+            }
+        }
+
+        // Cleanup any stale blobs.
+        for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) {
+            final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i);
+            userBlobs.entrySet().removeIf(entry -> {
+                final BlobHandle blobHandle = entry.getKey();
+                final BlobMetadata blobMetadata = entry.getValue();
+                boolean shouldRemove = false;
+
+                // Cleanup expired data blobs.
+                if (blobHandle.isExpired()) {
+                    shouldRemove = true;
+                }
+
+                // Cleanup blobs with no active leases.
+                // TODO: Exclude blobs which were just committed.
+                if (!blobMetadata.hasLeases()) {
+                    shouldRemove = true;
+                }
+
+                if (shouldRemove) {
+                    blobMetadata.getBlobFile().delete();
+                    mKnownBlobIds.remove(blobMetadata.getBlobId());
+                    deletedBlobIds.add(blobMetadata.getBlobId());
+                }
+                return shouldRemove;
+            });
+        }
+        writeBlobsInfoAsync();
+
+        // Cleanup any stale sessions.
+        final ArrayList<Integer> indicesToRemove = new ArrayList<>();
+        for (int i = 0, userCount = mSessions.size(); i < userCount; ++i) {
+            final LongSparseArray<BlobStoreSession> userSessions = mSessions.valueAt(i);
+            indicesToRemove.clear();
+            for (int j = 0, sessionsCount = userSessions.size(); j < sessionsCount; ++j) {
+                final BlobStoreSession blobStoreSession = userSessions.valueAt(j);
+                boolean shouldRemove = false;
+
+                // Cleanup sessions which haven't been modified in a while.
+                if (blobStoreSession.getSessionFile().lastModified()
+                        < System.currentTimeMillis() - SESSION_EXPIRY_TIMEOUT_MILLIS) {
+                    shouldRemove = true;
+                }
+
+                // Cleanup sessions with already expired data.
+                if (blobStoreSession.getBlobHandle().isExpired()) {
+                    shouldRemove = true;
+                }
+
+                if (shouldRemove) {
+                    blobStoreSession.getSessionFile().delete();
+                    mKnownBlobIds.remove(blobStoreSession.getSessionId());
+                    indicesToRemove.add(j);
+                    deletedBlobIds.add(blobStoreSession.getSessionId());
+                }
+            }
+            for (int j = 0; j < indicesToRemove.size(); ++j) {
+                userSessions.removeAt(indicesToRemove.get(j));
+            }
+        }
+        if (LOGV) {
+            Slog.v(TAG, "Completed idle maintenance; deleted "
+                    + Arrays.toString(deletedBlobIds.toArray()));
+        }
+        writeBlobSessionsAsync();
+    }
+
     void runClearAllSessions(@UserIdInt int userId) {
         synchronized (mBlobsLock) {
             if (userId == UserHandle.USER_ALL) {
@@ -727,10 +910,10 @@
             fout.increaseIndent();
             for (int j = 0, blobsCount = userBlobs.size(); j < blobsCount; ++j) {
                 final BlobMetadata blobMetadata = userBlobs.valueAt(j);
-                if (!dumpArgs.shouldDumpBlob(blobMetadata.blobId)) {
+                if (!dumpArgs.shouldDumpBlob(blobMetadata.getBlobId())) {
                     continue;
                 }
-                fout.println("Blob #" + blobMetadata.blobId);
+                fout.println("Blob #" + blobMetadata.getBlobId());
                 fout.increaseIndent();
                 blobMetadata.dump(fout, dumpArgs);
                 fout.decreaseIndent();
@@ -742,6 +925,9 @@
     private class PackageChangedReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
+            if (LOGV) {
+                Slog.v(TAG, "Received " + intent);
+            }
             switch (intent.getAction()) {
                 case Intent.ACTION_PACKAGE_FULLY_REMOVED:
                 case Intent.ACTION_PACKAGE_DATA_CLEARED:
@@ -893,6 +1079,14 @@
             final DumpArgs dumpArgs = DumpArgs.parse(args);
 
             final IndentingPrintWriter fout = new IndentingPrintWriter(writer, "    ");
+            if (dumpArgs.shouldDumpHelp()) {
+                writer.println("dumpsys blob_store [options]:");
+                fout.increaseIndent();
+                dumpArgs.dumpArgsUsage(fout);
+                fout.decreaseIndent();
+                return;
+            }
+
             synchronized (mBlobsLock) {
                 fout.println("mCurrentMaxSessionId: " + mCurrentMaxSessionId);
                 fout.println();
@@ -926,6 +1120,7 @@
         private boolean mDumpOnlySelectedSections;
         private boolean mDumpSessions;
         private boolean mDumpBlobs;
+        private boolean mDumpHelp;
 
         public boolean shouldDumpSession(String packageName, int uid, long blobId) {
             if (!CollectionUtils.isEmpty(mDumpPackages)
@@ -971,6 +1166,10 @@
                     || mDumpUserIds.indexOf(userId) >= 0;
         }
 
+        public boolean shouldDumpHelp() {
+            return mDumpHelp;
+        }
+
         private DumpArgs() {}
 
         public static DumpArgs parse(String[] args) {
@@ -1000,6 +1199,8 @@
                     dumpArgs.mDumpUserIds.add(getIntArgRequired(args, ++i, "userId"));
                 } else if ("--blob".equals(opt) || "-b".equals(opt)) {
                     dumpArgs.mDumpBlobIds.add(getLongArgRequired(args, ++i, "blobId"));
+                } else if ("--help".equals(opt) || "-h".equals(opt)) {
+                    dumpArgs.mDumpHelp = true;
                 } else {
                     // Everything else is assumed to be blob ids.
                     dumpArgs.mDumpBlobIds.add(getLongArgRequired(args, i, "blobId"));
@@ -1040,6 +1241,40 @@
             }
             return value;
         }
+
+        private void dumpArgsUsage(IndentingPrintWriter pw) {
+            pw.println("--help | -h");
+            printWithIndent(pw, "Dump this help text");
+            pw.println("--sessions");
+            printWithIndent(pw, "Dump only the sessions info");
+            pw.println("--blobs");
+            printWithIndent(pw, "Dump only the committed blobs info");
+            pw.println("--package | -p [package-name]");
+            printWithIndent(pw, "Dump blobs info associated with the given package");
+            pw.println("--uid | -u [uid]");
+            printWithIndent(pw, "Dump blobs info associated with the given uid");
+            pw.println("--user [user-id]");
+            printWithIndent(pw, "Dump blobs info in the given user");
+            pw.println("--blob | -b [session-id | blob-id]");
+            printWithIndent(pw, "Dump blob info corresponding to the given ID");
+            pw.println("--full | -f");
+            printWithIndent(pw, "Dump full unredacted blobs data");
+        }
+
+        private void printWithIndent(IndentingPrintWriter pw, String str) {
+            pw.increaseIndent();
+            pw.println(str);
+            pw.decreaseIndent();
+        }
+    }
+
+    private class LocalService extends BlobStoreManagerInternal {
+        @Override
+        public void onIdleMaintenance() {
+            synchronized (mBlobsLock) {
+                handleIdleMaintenanceLocked();
+            }
+        }
     }
 
     @VisibleForTesting
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 54a2997..bd35b86 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java
@@ -21,11 +21,13 @@
 import static android.app.blob.XmlTags.ATTR_UID;
 import static android.app.blob.XmlTags.TAG_ACCESS_MODE;
 import static android.app.blob.XmlTags.TAG_BLOB_HANDLE;
+import static android.os.Trace.TRACE_TAG_SYSTEM_SERVER;
 import static android.system.OsConstants.O_CREAT;
 import static android.system.OsConstants.O_RDONLY;
 import static android.system.OsConstants.O_RDWR;
 import static android.system.OsConstants.SEEK_SET;
 
+import static com.android.server.blob.BlobStoreConfig.LOGV;
 import static com.android.server.blob.BlobStoreConfig.TAG;
 
 import android.annotation.BytesLong;
@@ -40,6 +42,7 @@
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.os.RevocableFileDescriptor;
+import android.os.Trace;
 import android.os.storage.StorageManager;
 import android.system.ErrnoException;
 import android.system.Os;
@@ -381,15 +384,22 @@
     void verifyBlobData() {
         byte[] actualDigest = null;
         try {
+            Trace.traceBegin(TRACE_TAG_SYSTEM_SERVER,
+                    "computeBlobDigest-i" + mSessionId + "-l" + getSessionFile().length());
             actualDigest = FileUtils.digest(getSessionFile(), mBlobHandle.algorithm);
         } catch (IOException | NoSuchAlgorithmException e) {
             Slog.e(TAG, "Error computing the digest", e);
+        } finally {
+            Trace.traceEnd(TRACE_TAG_SYSTEM_SERVER);
         }
         synchronized (mSessionLock) {
             if (actualDigest != null && Arrays.equals(actualDigest, mBlobHandle.digest)) {
                 mState = STATE_VERIFIED_VALID;
                 // Commit callback will be sent once the data is persisted.
             } else {
+                if (LOGV) {
+                    Slog.v(TAG, "Digest of the data didn't match the given BlobHandle.digest");
+                }
                 mState = STATE_VERIFIED_INVALID;
                 sendCommitCallbackResult(COMMIT_RESULT_ERROR);
             }
@@ -447,6 +457,16 @@
         }
     }
 
+    @Override
+    public String toString() {
+        return "BlobStoreSession {"
+                + "id:" + mSessionId
+                + ",handle:" + mBlobHandle
+                + ",uid:" + mOwnerUid
+                + ",pkg:" + mOwnerPackageName
+                + "}";
+    }
+
     private void assertCallerIsOwner() {
         final int callingUid = Binder.getCallingUid();
         if (callingUid != mOwnerUid) {
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index 0bb07ca..088cadb 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -1287,7 +1287,7 @@
          * {@link #setPeriodic(long)} or {@link #setPersisted(boolean)}.  To continually monitor
          * for content changes, you need to schedule a new JobInfo observing the same URIs
          * before you finish execution of the JobService handling the most recent changes.
-         * Following this pattern will ensure you do not lost any content changes: while your
+         * Following this pattern will ensure you do not lose any content changes: while your
          * job is running, the system will continue monitoring for content changes, and propagate
          * any it sees over to the next job you schedule.</p>
          *
diff --git a/apex/media/framework/Android.bp b/apex/media/framework/Android.bp
index 91df098..23ae8af 100644
--- a/apex/media/framework/Android.bp
+++ b/apex/media/framework/Android.bp
@@ -38,6 +38,12 @@
         "android.media",
     ],
 
+    optimize: {
+        enabled: true,
+        shrink: true,
+        proguard_flags_files: ["updatable-media-proguard.flags"],
+    },
+
     installable: true,
 
     // TODO: build against stable API surface. Use core_platform for now to avoid
diff --git a/apex/media/framework/java/android/media/MediaParser.java b/apex/media/framework/java/android/media/MediaParser.java
index d59270c..96110e1 100644
--- a/apex/media/framework/java/android/media/MediaParser.java
+++ b/apex/media/framework/java/android/media/MediaParser.java
@@ -15,6 +15,7 @@
  */
 package android.media;
 
+import android.annotation.CheckResult;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.net.Uri;
@@ -32,6 +33,7 @@
 import com.google.android.exoplayer2.extractor.SeekMap.SeekPoints;
 import com.google.android.exoplayer2.extractor.TrackOutput;
 import com.google.android.exoplayer2.extractor.amr.AmrExtractor;
+import com.google.android.exoplayer2.extractor.flac.FlacExtractor;
 import com.google.android.exoplayer2.extractor.flv.FlvExtractor;
 import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor;
 import com.google.android.exoplayer2.extractor.mp3.Mp3Extractor;
@@ -382,6 +384,7 @@
          * parse the input.
          */
         @NonNull
+        @CheckResult
         private static UnrecognizedInputFormatException createForExtractors(
                 @NonNull String... extractorNames) {
             StringBuilder builder = new StringBuilder();
@@ -536,7 +539,7 @@
                 }
             }
             if (mExtractor == null) {
-                UnrecognizedInputFormatException.createForExtractors(mExtractorNamesPool);
+                throw UnrecognizedInputFormatException.createForExtractors(mExtractorNamesPool);
             }
             return true;
         }
@@ -912,6 +915,7 @@
         extractorFactoriesByName.put("exo.Ac4Extractor", Ac4Extractor::new);
         extractorFactoriesByName.put("exo.AdtsExtractor", AdtsExtractor::new);
         extractorFactoriesByName.put("exo.AmrExtractor", AmrExtractor::new);
+        extractorFactoriesByName.put("exo.FlacExtractor", FlacExtractor::new);
         extractorFactoriesByName.put("exo.FlvExtractor", FlvExtractor::new);
         extractorFactoriesByName.put("exo.FragmentedMp4Extractor", FragmentedMp4Extractor::new);
         extractorFactoriesByName.put("exo.MatroskaExtractor", MatroskaExtractor::new);
diff --git a/apex/media/framework/updatable-media-proguard.flags b/apex/media/framework/updatable-media-proguard.flags
new file mode 100644
index 0000000..4e7d842
--- /dev/null
+++ b/apex/media/framework/updatable-media-proguard.flags
@@ -0,0 +1,2 @@
+# Keep all symbols in android.media.
+-keep class android.media.* {*;}
diff --git a/apex/sdkextensions/testing/Android.bp b/apex/sdkextensions/testing/Android.bp
index e6451cc..f2f5b32 100644
--- a/apex/sdkextensions/testing/Android.bp
+++ b/apex/sdkextensions/testing/Android.bp
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-apex {
+apex_test {
     name: "test_com.android.sdkext",
     visibility: [ "//system/apex/tests" ],
     defaults: ["com.android.sdkext-defaults"],
diff --git a/apex/statsd/aidl/android/os/IStatsd.aidl b/apex/statsd/aidl/android/os/IStatsd.aidl
index a2564212..253b2c1 100644
--- a/apex/statsd/aidl/android/os/IStatsd.aidl
+++ b/apex/statsd/aidl/android/os/IStatsd.aidl
@@ -222,12 +222,6 @@
     const int FLAG_REQUIRE_LOW_LATENCY_MONITOR = 0x04;
 
     /**
-     * Logs an event for binary push for module updates.
-     */
-     oneway void sendBinaryPushStateChangedAtom(in String trainName, in long trainVersionCode,
-         in int options, in int state, in long[] experimentId);
-
-    /**
      * Logs an event for watchdog rollbacks.
      */
      oneway void sendWatchdogRollbackOccurredAtom(in int rollbackType, in String packageName,
diff --git a/apex/statsd/framework/java/android/app/StatsManager.java b/apex/statsd/framework/java/android/app/StatsManager.java
index a1de330..411482b 100644
--- a/apex/statsd/framework/java/android/app/StatsManager.java
+++ b/apex/statsd/framework/java/android/app/StatsManager.java
@@ -476,7 +476,7 @@
     /**
      * Registers a callback for an atom when that atom is to be pulled. The stats service will
      * invoke pullData in the callback when the stats service determines that this atom needs to be
-     * pulled.
+     * pulled. This method should not be called by third-party apps.
      *
      * @param atomTag           The tag of the atom for this puller callback.
      * @param metadata          Optional metadata specifying the timeout, cool down time, and
@@ -485,6 +485,7 @@
      * @param executor          The executor in which to run the callback.
      *
      */
+    @RequiresPermission(android.Manifest.permission.REGISTER_STATS_PULL_ATOM)
     public void registerPullAtomCallback(int atomTag, @Nullable PullAtomMetadata metadata,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull StatsPullAtomCallback callback) {
@@ -510,11 +511,12 @@
 
     /**
      * Unregisters a callback for an atom when that atom is to be pulled. Note that any ongoing
-     * pulls will still occur.
+     * pulls will still occur. This method should not be called by third-party apps.
      *
      * @param atomTag           The tag of the atom of which to unregister
      *
      */
+    @RequiresPermission(android.Manifest.permission.REGISTER_STATS_PULL_ATOM)
     public void unregisterPullAtomCallback(int atomTag) {
         synchronized (sLock) {
             try {
diff --git a/core/java/android/util/StatsLog.java b/apex/statsd/framework/java/android/util/StatsLog.java
similarity index 91%
rename from core/java/android/util/StatsLog.java
rename to apex/statsd/framework/java/android/util/StatsLog.java
index feeff6c..7910737 100644
--- a/core/java/android/util/StatsLog.java
+++ b/apex/statsd/framework/java/android/util/StatsLog.java
@@ -27,6 +27,7 @@
 import android.os.IStatsd;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.util.FrameworkStatsLog;
 
@@ -37,6 +38,7 @@
 public final class StatsLog {
     private static final String TAG = "StatsLog";
     private static final boolean DEBUG = false;
+    private static final int EXPERIMENT_IDS_FIELD_ID = 1;
 
     private static IStatsd sService;
 
@@ -152,28 +154,25 @@
     public static boolean logBinaryPushStateChanged(@NonNull String trainName,
             long trainVersionCode, int options, int state,
             @NonNull long[] experimentIds) {
-        synchronized (sLogLock) {
-            try {
-                IStatsd service = getIStatsdLocked();
-                if (service == null) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "Failed to find statsd when logging event");
-                    }
-                    return false;
-                }
-                service.sendBinaryPushStateChangedAtom(
-                        trainName, trainVersionCode, options, state, experimentIds);
-                return true;
-            } catch (RemoteException e) {
-                sService = null;
-                if (DEBUG) {
-                    Slog.d(TAG,
-                            "Failed to connect to StatsCompanionService when logging "
-                                    + "BinaryPushStateChanged");
-                }
-                return false;
-            }
+        ProtoOutputStream proto = new ProtoOutputStream();
+        for (long id : experimentIds) {
+            proto.write(
+                    ProtoOutputStream.FIELD_TYPE_INT64
+                    | ProtoOutputStream.FIELD_COUNT_REPEATED
+                    | EXPERIMENT_IDS_FIELD_ID,
+                    id);
         }
+        FrameworkStatsLog.write(FrameworkStatsLog.BINARY_PUSH_STATE_CHANGED,
+                trainName,
+                trainVersionCode,
+                (options & IStatsd.FLAG_REQUIRE_STAGING) > 0,
+                (options & IStatsd.FLAG_ROLLBACK_ENABLED) > 0,
+                (options & IStatsd.FLAG_REQUIRE_LOW_LATENCY_MONITOR) > 0,
+                state,
+                proto.getBytes(),
+                0,
+                0);
+        return true;
     }
 
     /**
diff --git a/apex/statsd/service/java/com/android/server/stats/StatsCompanion.java b/apex/statsd/service/java/com/android/server/stats/StatsCompanion.java
index 4383b50..4495dc9 100644
--- a/apex/statsd/service/java/com/android/server/stats/StatsCompanion.java
+++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanion.java
@@ -38,11 +38,15 @@
     private static final String TAG = "StatsCompanion";
     private static final boolean DEBUG = false;
 
-    static void enforceStatsCompanionPermission(Context context) {
+    private static final int AID_STATSD = 1066;
+
+    static void enforceStatsdCallingUid() {
         if (Binder.getCallingPid() == Process.myPid()) {
             return;
         }
-        context.enforceCallingPermission(android.Manifest.permission.STATSCOMPANION, null);
+        if (Binder.getCallingUid() != AID_STATSD) {
+            throw new SecurityException("Not allowed to access StatsCompanion");
+        }
     }
 
     /**
@@ -114,7 +118,7 @@
 
         @Override
         public void sendDataBroadcast(long lastReportTimeNs) {
-            enforceStatsCompanionPermission(mContext);
+            enforceStatsdCallingUid();
             Intent intent = new Intent();
             intent.putExtra(EXTRA_LAST_REPORT_TIME, lastReportTimeNs);
             try {
@@ -126,7 +130,7 @@
 
         @Override
         public void sendActiveConfigsChangedBroadcast(long[] configIds) {
-            enforceStatsCompanionPermission(mContext);
+            enforceStatsdCallingUid();
             Intent intent = new Intent();
             intent.putExtra(StatsManager.EXTRA_STATS_ACTIVE_CONFIG_KEYS, configIds);
             try {
@@ -142,7 +146,7 @@
         @Override
         public void sendSubscriberBroadcast(long configUid, long configId, long subscriptionId,
                 long subscriptionRuleId, String[] cookies, StatsDimensionsValue dimensionsValue) {
-            enforceStatsCompanionPermission(mContext);
+            enforceStatsdCallingUid();
             Intent intent =
                     new Intent()
                             .putExtra(StatsManager.EXTRA_STATS_CONFIG_UID, configUid)
diff --git a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
index 3e9a488..a735cb8 100644
--- a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
+++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
@@ -398,7 +398,7 @@
 
     @Override // Binder call
     public void setAnomalyAlarm(long timestampMs) {
-        StatsCompanion.enforceStatsCompanionPermission(mContext);
+        StatsCompanion.enforceStatsdCallingUid();
         if (DEBUG) Slog.d(TAG, "Setting anomaly alarm for " + timestampMs);
         final long callingToken = Binder.clearCallingIdentity();
         try {
@@ -414,7 +414,7 @@
 
     @Override // Binder call
     public void cancelAnomalyAlarm() {
-        StatsCompanion.enforceStatsCompanionPermission(mContext);
+        StatsCompanion.enforceStatsdCallingUid();
         if (DEBUG) Slog.d(TAG, "Cancelling anomaly alarm");
         final long callingToken = Binder.clearCallingIdentity();
         try {
@@ -426,7 +426,7 @@
 
     @Override // Binder call
     public void setAlarmForSubscriberTriggering(long timestampMs) {
-        StatsCompanion.enforceStatsCompanionPermission(mContext);
+        StatsCompanion.enforceStatsdCallingUid();
         if (DEBUG) {
             Slog.d(TAG,
                     "Setting periodic alarm in about " + (timestampMs
@@ -445,7 +445,7 @@
 
     @Override // Binder call
     public void cancelAlarmForSubscriberTriggering() {
-        StatsCompanion.enforceStatsCompanionPermission(mContext);
+        StatsCompanion.enforceStatsdCallingUid();
         if (DEBUG) {
             Slog.d(TAG, "Cancelling periodic alarm");
         }
@@ -459,7 +459,7 @@
 
     @Override // Binder call
     public void setPullingAlarm(long nextPullTimeMs) {
-        StatsCompanion.enforceStatsCompanionPermission(mContext);
+        StatsCompanion.enforceStatsdCallingUid();
         if (DEBUG) {
             Slog.d(TAG, "Setting pulling alarm in about "
                     + (nextPullTimeMs - SystemClock.elapsedRealtime()));
@@ -477,7 +477,7 @@
 
     @Override // Binder call
     public void cancelPullingAlarm() {
-        StatsCompanion.enforceStatsCompanionPermission(mContext);
+        StatsCompanion.enforceStatsdCallingUid();
         if (DEBUG) {
             Slog.d(TAG, "Cancelling pulling alarm");
         }
@@ -491,7 +491,7 @@
 
     @Override // Binder call
     public void statsdReady() {
-        StatsCompanion.enforceStatsCompanionPermission(mContext);
+        StatsCompanion.enforceStatsdCallingUid();
         if (DEBUG) {
             Slog.d(TAG, "learned that statsdReady");
         }
@@ -503,7 +503,7 @@
 
     @Override
     public void triggerUidSnapshot() {
-        StatsCompanion.enforceStatsCompanionPermission(mContext);
+        StatsCompanion.enforceStatsdCallingUid();
         synchronized (sStatsdLock) {
             final long token = Binder.clearCallingIdentity();
             try {
@@ -518,7 +518,7 @@
 
     @Override // Binder call
     public boolean checkPermission(String permission, int pid, int uid) {
-        StatsCompanion.enforceStatsCompanionPermission(mContext);
+        StatsCompanion.enforceStatsdCallingUid();
         return mContext.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_GRANTED;
     }
 
diff --git a/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java b/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java
index 04d8b00..c1dc584 100644
--- a/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java
+++ b/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java
@@ -171,8 +171,8 @@
     @Override
     public void registerPullAtomCallback(int atomTag, long coolDownNs, long timeoutNs,
             int[] additiveFields, IPullAtomCallback pullerCallback) {
+        enforceRegisterStatsPullAtomPermission();
         int callingUid = Binder.getCallingUid();
-        final long token = Binder.clearCallingIdentity();
         PullerKey key = new PullerKey(callingUid, atomTag);
         PullerValue val = new PullerValue(coolDownNs, timeoutNs, additiveFields, pullerCallback);
 
@@ -187,6 +187,7 @@
             return;
         }
 
+        final long token = Binder.clearCallingIdentity();
         try {
             statsd.registerPullAtomCallback(
                     callingUid, atomTag, coolDownNs, timeoutNs, additiveFields, pullerCallback);
@@ -199,8 +200,8 @@
 
     @Override
     public void unregisterPullAtomCallback(int atomTag) {
+        enforceRegisterStatsPullAtomPermission();
         int callingUid = Binder.getCallingUid();
-        final long token = Binder.clearCallingIdentity();
         PullerKey key = new PullerKey(callingUid, atomTag);
 
         // Always remove the puller from StatsManagerService even if statsd is down. When statsd
@@ -214,6 +215,7 @@
             return;
         }
 
+        final long token = Binder.clearCallingIdentity();
         try {
             statsd.unregisterPullAtomCallback(callingUid, atomTag);
         } catch (RemoteException e) {
@@ -502,6 +504,13 @@
         }
     }
 
+    private void enforceRegisterStatsPullAtomPermission() {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.REGISTER_STATS_PULL_ATOM,
+                "Need REGISTER_STATS_PULL_ATOM permission.");
+    }
+
+
     /**
      * Clients should call this if blocking until statsd to be ready is desired
      *
diff --git a/apex/statsd/tests/libstatspull/jni/stats_pull_helper.cpp b/apex/statsd/tests/libstatspull/jni/stats_pull_helper.cpp
index e4ab823..22daa8e 100644
--- a/apex/statsd/tests/libstatspull/jni/stats_pull_helper.cpp
+++ b/apex/statsd/tests/libstatspull/jni/stats_pull_helper.cpp
@@ -46,15 +46,15 @@
     }
 }
 
-static status_pull_atom_return_t pullAtomCallback(int32_t atomTag, pulled_stats_event_list* data,
-                                                  void* /*cookie*/) {
+static AStatsManager_PullAtomCallbackReturn pullAtomCallback(int32_t atomTag, AStatsEventList* data,
+                                                             void* /*cookie*/) {
     sNumPulls++;
     sleep_for(std::chrono::milliseconds(sLatencyMillis));
     for (int i = 0; i < sAtomsPerPull; i++) {
-        stats_event* event = add_stats_event_to_pull_data(data);
-        stats_event_set_atom_id(event, atomTag);
-        stats_event_write_int64(event, (int64_t) sNumPulls);
-        stats_event_build(event);
+        AStatsEvent* event = AStatsEventList_addStatsEvent(data);
+        AStatsEvent_setAtomId(event, atomTag);
+        AStatsEvent_writeInt64(event, (int64_t) sNumPulls);
+        AStatsEvent_build(event);
     }
     return sPullReturnVal;
 }
@@ -71,11 +71,12 @@
     sLatencyMillis = latencyMillis;
     sAtomsPerPull = atomsPerPull;
     sNumPulls = 0;
-    pull_atom_metadata metadata = {.cool_down_ns = coolDownNs,
-                                   .timeout_ns = timeoutNs,
-                                   .additive_fields = nullptr,
-                                   .additive_fields_size = 0};
-    register_stats_pull_atom_callback(sAtomTag, &pullAtomCallback, &metadata, nullptr);
+    AStatsManager_PullAtomMetadata* metadata = AStatsManager_PullAtomMetadata_obtain();
+    AStatsManager_PullAtomMetadata_setCoolDownNs(metadata, coolDownNs);
+    AStatsManager_PullAtomMetadata_setTimeoutNs(metadata, timeoutNs);
+
+    AStatsManager_registerPullAtomCallback(sAtomTag, &pullAtomCallback, metadata, nullptr);
+    AStatsManager_PullAtomMetadata_release(metadata);
 }
 
 extern "C"
@@ -83,6 +84,6 @@
 Java_com_android_internal_os_statsd_libstats_LibStatsPullTests_unregisterStatsPuller(
         JNIEnv* /*env*/, jobject /* this */, jint /*atomTag*/)
 {
-    unregister_stats_pull_atom_callback(sAtomTag);
+    AStatsManager_unregisterPullAtomCallback(sAtomTag);
 }
-} // namespace
\ No newline at end of file
+} // namespace
diff --git a/apex/statsd/tests/libstatspull/src/com/android/internal/os/statsd/libstats/LibStatsPullTests.java b/apex/statsd/tests/libstatspull/src/com/android/internal/os/statsd/libstats/LibStatsPullTests.java
index dbd636d..e119b4c 100644
--- a/apex/statsd/tests/libstatspull/src/com/android/internal/os/statsd/libstats/LibStatsPullTests.java
+++ b/apex/statsd/tests/libstatspull/src/com/android/internal/os/statsd/libstats/LibStatsPullTests.java
@@ -71,7 +71,6 @@
      */
     @Before
     public void setup() {
-//        Debug.waitForDebugger();
         mContext = InstrumentationRegistry.getTargetContext();
         assertThat(InstrumentationRegistry.getInstrumentation()).isNotNull();
         sPullReturnValue = StatsManager.PULL_SUCCESS;
diff --git a/api/current.txt b/api/current.txt
index 9b2e6d4..a5a51d6 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -1876,6 +1876,7 @@
     ctor public R.id();
     field public static final int accessibilityActionContextClick = 16908348; // 0x102003c
     field public static final int accessibilityActionHideTooltip = 16908357; // 0x1020045
+    field public static final int accessibilityActionImeEnter = 16908372; // 0x1020054
     field public static final int accessibilityActionMoveWindow = 16908354; // 0x1020042
     field public static final int accessibilityActionPageDown = 16908359; // 0x1020047
     field public static final int accessibilityActionPageLeft = 16908360; // 0x1020048
@@ -2872,7 +2873,7 @@
     method public void onSystemActionsChanged();
     method public final boolean performGlobalAction(int);
     method public final void setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo);
-    method public boolean takeScreenshot(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.graphics.Bitmap>);
+    method public boolean takeScreenshot(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.accessibilityservice.AccessibilityService.ScreenshotResult>);
     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
@@ -2887,6 +2888,13 @@
     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_4_FINGER_DOUBLE_TAP = 38; // 0x26
+    field public static final int GESTURE_4_FINGER_SINGLE_TAP = 37; // 0x25
+    field public static final int GESTURE_4_FINGER_SWIPE_DOWN = 34; // 0x22
+    field public static final int GESTURE_4_FINGER_SWIPE_LEFT = 35; // 0x23
+    field public static final int GESTURE_4_FINGER_SWIPE_RIGHT = 36; // 0x24
+    field public static final int GESTURE_4_FINGER_SWIPE_UP = 33; // 0x21
+    field public static final int GESTURE_4_FINGER_TRIPLE_TAP = 39; // 0x27
     field public static final int GESTURE_DOUBLE_TAP = 17; // 0x11
     field public static final int GESTURE_DOUBLE_TAP_AND_HOLD = 18; // 0x12
     field public static final int GESTURE_SWIPE_DOWN = 2; // 0x2
@@ -2944,6 +2952,12 @@
     method public void onMagnificationChanged(@NonNull android.accessibilityservice.AccessibilityService.MagnificationController, @NonNull android.graphics.Region, float, float, float);
   }
 
+  public static final class AccessibilityService.ScreenshotResult {
+    method @Nullable public android.graphics.ColorSpace getColorSpace();
+    method @NonNull public android.hardware.HardwareBuffer getHardwareBuffer();
+    method public long getTimestamp();
+  }
+
   public static final class AccessibilityService.SoftKeyboardController {
     method public void addOnShowModeChangedListener(@NonNull android.accessibilityservice.AccessibilityService.SoftKeyboardController.OnShowModeChangedListener);
     method public void addOnShowModeChangedListener(@NonNull android.accessibilityservice.AccessibilityService.SoftKeyboardController.OnShowModeChangedListener, @Nullable android.os.Handler);
@@ -3849,7 +3863,7 @@
     method public void onPerformDirectAction(@NonNull String, @NonNull android.os.Bundle, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.os.Bundle>);
     method public void onPictureInPictureModeChanged(boolean, android.content.res.Configuration);
     method @Deprecated public void onPictureInPictureModeChanged(boolean);
-    method public void onPictureInPictureRequested();
+    method public boolean onPictureInPictureRequested();
     method @CallSuper protected void onPostCreate(@Nullable android.os.Bundle);
     method public void onPostCreate(@Nullable android.os.Bundle, @Nullable android.os.PersistableBundle);
     method @CallSuper protected void onPostResume();
@@ -5918,6 +5932,7 @@
     method public long[] getVibrationPattern();
     method public boolean hasUserSetImportance();
     method public boolean hasUserSetSound();
+    method public boolean isImportantConversation();
     method public void setAllowBubbles(boolean);
     method public void setBypassDnd(boolean);
     method public void setConversationId(@Nullable String, @Nullable String);
@@ -6030,14 +6045,19 @@
   public static class NotificationManager.Policy implements android.os.Parcelable {
     ctor public NotificationManager.Policy(int, int, int);
     ctor public NotificationManager.Policy(int, int, int, int);
+    ctor public NotificationManager.Policy(int, int, int, int, int);
     method public int describeContents();
     method public static String priorityCategoriesToString(int);
     method public static String prioritySendersToString(int);
     method public static String suppressedEffectsToString(int);
     method public void writeToParcel(android.os.Parcel, int);
+    field public static final int CONVERSATION_SENDERS_ANYONE = 1; // 0x1
+    field public static final int CONVERSATION_SENDERS_IMPORTANT = 2; // 0x2
+    field public static final int CONVERSATION_SENDERS_NONE = 3; // 0x3
     field @NonNull public static final android.os.Parcelable.Creator<android.app.NotificationManager.Policy> CREATOR;
     field public static final int PRIORITY_CATEGORY_ALARMS = 32; // 0x20
     field public static final int PRIORITY_CATEGORY_CALLS = 8; // 0x8
+    field public static final int PRIORITY_CATEGORY_CONVERSATIONS = 256; // 0x100
     field public static final int PRIORITY_CATEGORY_EVENTS = 2; // 0x2
     field public static final int PRIORITY_CATEGORY_MEDIA = 64; // 0x40
     field public static final int PRIORITY_CATEGORY_MESSAGES = 4; // 0x4
@@ -6058,6 +6078,7 @@
     field public static final int SUPPRESSED_EFFECT_STATUS_BAR = 32; // 0x20
     field public final int priorityCallSenders;
     field public final int priorityCategories;
+    field public final int priorityConversationSenders;
     field public final int priorityMessageSenders;
     field public final int suppressedVisualEffects;
   }
@@ -6795,7 +6816,7 @@
     ctor public DevicePolicyKeyguardService();
     method @Nullable public void dismiss();
     method @Nullable public final android.os.IBinder onBind(@Nullable android.content.Intent);
-    method @Nullable public android.view.SurfaceControl onSurfaceReady(@Nullable android.os.IBinder);
+    method @Nullable public android.view.SurfaceControlViewHost.SurfacePackage onSurfaceReady(@Nullable android.os.IBinder);
   }
 
   public class DevicePolicyManager {
@@ -7139,6 +7160,7 @@
     field public static final int KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS = 8; // 0x8
     field public static final int KEYGUARD_DISABLE_WIDGETS_ALL = 1; // 0x1
     field public static final int LEAVE_ALL_SYSTEM_APPS_ENABLED = 16; // 0x10
+    field public static final int LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK = 64; // 0x40
     field public static final int LOCK_TASK_FEATURE_GLOBAL_ACTIONS = 16; // 0x10
     field public static final int LOCK_TASK_FEATURE_HOME = 4; // 0x4
     field public static final int LOCK_TASK_FEATURE_KEYGUARD = 32; // 0x20
@@ -26790,7 +26812,6 @@
     method public abstract void onSelectRoute(@NonNull String, @NonNull String);
     method public abstract void onSetVolume(@NonNull String, int);
     method public abstract void onTransferToRoute(@NonNull String, @NonNull String);
-    method public abstract void onUpdateVolume(@NonNull String, int);
     field public static final long REQUEST_ID_UNKNOWN = 0L; // 0x0L
     field public static final String SERVICE_INTERFACE = "android.media.MediaRoute2ProviderService";
   }
@@ -28763,6 +28784,7 @@
     field public static final String COLUMN_DESCRIPTION = "description";
     field public static final String COLUMN_DISPLAY_NAME = "display_name";
     field public static final String COLUMN_DISPLAY_NUMBER = "display_number";
+    field public static final String COLUMN_GLOBAL_CONTENT_ID = "global_content_id";
     field public static final String COLUMN_INPUT_ID = "input_id";
     field public static final String COLUMN_INTERNAL_PROVIDER_DATA = "internal_provider_data";
     field public static final String COLUMN_INTERNAL_PROVIDER_FLAG1 = "internal_provider_flag1";
@@ -28788,6 +28810,7 @@
     field public static final String SERVICE_TYPE_AUDIO_VIDEO = "SERVICE_TYPE_AUDIO_VIDEO";
     field public static final String SERVICE_TYPE_OTHER = "SERVICE_TYPE_OTHER";
     field public static final String TYPE_1SEG = "TYPE_1SEG";
+    field public static final String TYPE_ATSC3_T = "TYPE_ATSC3_T";
     field public static final String TYPE_ATSC_C = "TYPE_ATSC_C";
     field public static final String TYPE_ATSC_M_H = "TYPE_ATSC_M_H";
     field public static final String TYPE_ATSC_T = "TYPE_ATSC_T";
@@ -28881,6 +28904,7 @@
     field public static final String COLUMN_SEASON_TITLE = "season_title";
     field public static final String COLUMN_SERIES_ID = "series_id";
     field public static final String COLUMN_SHORT_DESCRIPTION = "short_description";
+    field public static final String COLUMN_SPLIT_ID = "split_id";
     field public static final String COLUMN_STARTING_PRICE = "starting_price";
     field public static final String COLUMN_THUMBNAIL_ASPECT_RATIO = "poster_thumbnail_aspect_ratio";
     field public static final String COLUMN_THUMBNAIL_URI = "thumbnail_uri";
@@ -28928,6 +28952,8 @@
     field public static final String COLUMN_EPISODE_DISPLAY_NUMBER = "episode_display_number";
     field @Deprecated public static final String COLUMN_EPISODE_NUMBER = "episode_number";
     field public static final String COLUMN_EPISODE_TITLE = "episode_title";
+    field public static final String COLUMN_EVENT_ID = "event_id";
+    field public static final String COLUMN_GLOBAL_CONTENT_ID = "global_content_id";
     field public static final String COLUMN_INTERNAL_PROVIDER_DATA = "internal_provider_data";
     field public static final String COLUMN_INTERNAL_PROVIDER_FLAG1 = "internal_provider_flag1";
     field public static final String COLUMN_INTERNAL_PROVIDER_FLAG2 = "internal_provider_flag2";
@@ -28944,6 +28970,7 @@
     field public static final String COLUMN_SEASON_TITLE = "season_title";
     field public static final String COLUMN_SERIES_ID = "series_id";
     field public static final String COLUMN_SHORT_DESCRIPTION = "short_description";
+    field public static final String COLUMN_SPLIT_ID = "split_id";
     field public static final String COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis";
     field public static final String COLUMN_THUMBNAIL_URI = "thumbnail_uri";
     field public static final String COLUMN_TITLE = "title";
@@ -29009,6 +29036,7 @@
     field public static final String COLUMN_SEASON_TITLE = "season_title";
     field public static final String COLUMN_SERIES_ID = "series_id";
     field public static final String COLUMN_SHORT_DESCRIPTION = "short_description";
+    field public static final String COLUMN_SPLIT_ID = "split_id";
     field public static final String COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis";
     field public static final String COLUMN_THUMBNAIL_URI = "thumbnail_uri";
     field public static final String COLUMN_TITLE = "title";
@@ -29069,6 +29097,7 @@
     field public static final String COLUMN_SEASON_TITLE = "season_title";
     field public static final String COLUMN_SERIES_ID = "series_id";
     field public static final String COLUMN_SHORT_DESCRIPTION = "short_description";
+    field public static final String COLUMN_SPLIT_ID = "split_id";
     field public static final String COLUMN_STARTING_PRICE = "starting_price";
     field public static final String COLUMN_THUMBNAIL_ASPECT_RATIO = "poster_thumbnail_aspect_ratio";
     field public static final String COLUMN_THUMBNAIL_URI = "thumbnail_uri";
@@ -30110,7 +30139,7 @@
     method @NonNull public android.net.NetworkCapabilities setLinkDownstreamBandwidthKbps(int);
     method @NonNull public android.net.NetworkCapabilities setLinkUpstreamBandwidthKbps(int);
     method @NonNull public android.net.NetworkCapabilities setNetworkSpecifier(@NonNull android.net.NetworkSpecifier);
-    method public void setOwnerUid(int);
+    method @NonNull public android.net.NetworkCapabilities setOwnerUid(int);
     method @NonNull public android.net.NetworkCapabilities setSignalStrength(int);
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkCapabilities> CREATOR;
@@ -35671,6 +35700,7 @@
     field public static final String INCREMENTAL;
     field public static final int PREVIEW_SDK_INT;
     field public static final String RELEASE;
+    field @NonNull public static final String RELEASE_OR_CODENAME;
     field @Deprecated public static final String SDK;
     field public static final int SDK_INT;
     field public static final String SECURITY_PATCH;
@@ -43775,12 +43805,14 @@
     method public int getPriorityCallSenders();
     method public int getPriorityCategoryAlarms();
     method public int getPriorityCategoryCalls();
+    method public int getPriorityCategoryConversations();
     method public int getPriorityCategoryEvents();
     method public int getPriorityCategoryMedia();
     method public int getPriorityCategoryMessages();
     method public int getPriorityCategoryReminders();
     method public int getPriorityCategoryRepeatCallers();
     method public int getPriorityCategorySystem();
+    method public int getPriorityConversationSenders();
     method public int getPriorityMessageSenders();
     method public int getVisualEffectAmbient();
     method public int getVisualEffectBadge();
@@ -43790,6 +43822,10 @@
     method public int getVisualEffectPeek();
     method public int getVisualEffectStatusBar();
     method public void writeToParcel(android.os.Parcel, int);
+    field public static final int CONVERSATION_SENDERS_ANYONE = 1; // 0x1
+    field public static final int CONVERSATION_SENDERS_IMPORTANT = 2; // 0x2
+    field public static final int CONVERSATION_SENDERS_NONE = 3; // 0x3
+    field public static final int CONVERSATION_SENDERS_UNSET = 0; // 0x0
     field @NonNull public static final android.os.Parcelable.Creator<android.service.notification.ZenPolicy> CREATOR;
     field public static final int PEOPLE_TYPE_ANYONE = 1; // 0x1
     field public static final int PEOPLE_TYPE_CONTACTS = 2; // 0x2
@@ -43806,6 +43842,7 @@
     method @NonNull public android.service.notification.ZenPolicy.Builder allowAlarms(boolean);
     method @NonNull public android.service.notification.ZenPolicy.Builder allowAllSounds();
     method @NonNull public android.service.notification.ZenPolicy.Builder allowCalls(int);
+    method @NonNull public android.service.notification.ZenPolicy.Builder allowConversations(int);
     method @NonNull public android.service.notification.ZenPolicy.Builder allowEvents(boolean);
     method @NonNull public android.service.notification.ZenPolicy.Builder allowMedia(boolean);
     method @NonNull public android.service.notification.ZenPolicy.Builder allowMessages(int);
@@ -44038,8 +44075,8 @@
   }
 
   public static final class AlwaysOnHotwordDetector.ModelParamRange {
-    method public int end();
-    method public int start();
+    method public int getEnd();
+    method public int getStart();
   }
 
   public class VoiceInteractionService extends android.app.Service {
@@ -55620,6 +55657,7 @@
     field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_EXPAND;
     field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_FOCUS;
     field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_HIDE_TOOLTIP;
+    field @NonNull public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_IME_ENTER;
     field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_LONG_CLICK;
     field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_MOVE_WINDOW;
     field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_NEXT_AT_MOVEMENT_GRANULARITY;
@@ -60796,6 +60834,7 @@
     method public void setTypeface(@Nullable android.graphics.Typeface, int);
     method public void setTypeface(@Nullable android.graphics.Typeface);
     method public void setWidth(int);
+    field public static final int ACCESSIBILITY_ACTION_IME_ENTER = 16908372; // 0x1020054
     field public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0; // 0x0
     field public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = 1; // 0x1
   }
diff --git a/api/module-lib-current.txt b/api/module-lib-current.txt
index b4259474..90531b1 100644
--- a/api/module-lib-current.txt
+++ b/api/module-lib-current.txt
@@ -1,151 +1,4 @@
 // Signature format: 2.0
-package android.app.timedetector {
-
-  public final class PhoneTimeSuggestion implements android.os.Parcelable {
-    method public void addDebugInfo(@NonNull String);
-    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 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;
-  }
-
-  public static final class PhoneTimeSuggestion.Builder {
-    ctor public PhoneTimeSuggestion.Builder(int);
-    method @NonNull public android.app.timedetector.PhoneTimeSuggestion.Builder addDebugInfo(@NonNull String);
-    method @NonNull public android.app.timedetector.PhoneTimeSuggestion build();
-    method @NonNull public android.app.timedetector.PhoneTimeSuggestion.Builder setUtcTime(@Nullable android.os.TimestampedValue<java.lang.Long>);
-  }
-
-  public interface TimeDetector {
-    method @RequiresPermission("android.permission.SUGGEST_PHONE_TIME_AND_ZONE") public void suggestPhoneTime(@NonNull android.app.timedetector.PhoneTimeSuggestion);
-  }
-
-}
-
-package android.app.timezonedetector {
-
-  public final class PhoneTimeZoneSuggestion implements android.os.Parcelable {
-    method public void addDebugInfo(@NonNull String);
-    method public void addDebugInfo(@NonNull java.util.List<java.lang.String>);
-    method @NonNull public static android.app.timezonedetector.PhoneTimeZoneSuggestion createEmptySuggestion(int, @NonNull String);
-    method public int describeContents();
-    method @NonNull public java.util.List<java.lang.String> getDebugInfo();
-    method public int getMatchType();
-    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;
-    field public static final int MATCH_TYPE_EMULATOR_ZONE_ID = 4; // 0x4
-    field public static final int MATCH_TYPE_NA = 0; // 0x0
-    field public static final int MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET = 3; // 0x3
-    field public static final int MATCH_TYPE_NETWORK_COUNTRY_ONLY = 2; // 0x2
-    field public static final int MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY = 5; // 0x5
-    field public static final int QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS = 3; // 0x3
-    field public static final int QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET = 2; // 0x2
-    field public static final int QUALITY_NA = 0; // 0x0
-    field public static final int QUALITY_SINGLE_ZONE = 1; // 0x1
-  }
-
-  public static final class PhoneTimeZoneSuggestion.Builder {
-    ctor public PhoneTimeZoneSuggestion.Builder(int);
-    method @NonNull public android.app.timezonedetector.PhoneTimeZoneSuggestion.Builder addDebugInfo(@NonNull String);
-    method @NonNull public android.app.timezonedetector.PhoneTimeZoneSuggestion build();
-    method @NonNull public android.app.timezonedetector.PhoneTimeZoneSuggestion.Builder setMatchType(int);
-    method @NonNull public android.app.timezonedetector.PhoneTimeZoneSuggestion.Builder setQuality(int);
-    method @NonNull public android.app.timezonedetector.PhoneTimeZoneSuggestion.Builder setZoneId(@Nullable String);
-  }
-
-  public interface TimeZoneDetector {
-    method @RequiresPermission("android.permission.SUGGEST_PHONE_TIME_AND_ZONE") public void suggestPhoneTimeZone(@NonNull android.app.timezonedetector.PhoneTimeZoneSuggestion);
-  }
-
-}
-
-package android.os {
-
-  public final class TimestampedValue<T> implements android.os.Parcelable {
-    ctor public TimestampedValue(long, @Nullable T);
-    method public int describeContents();
-    method public long getReferenceTimeMillis();
-    method @Nullable public T getValue();
-    method public static long referenceTimeDifference(@NonNull android.os.TimestampedValue<?>, @NonNull android.os.TimestampedValue<?>);
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.os.TimestampedValue<?>> CREATOR;
-  }
-
-}
-
-package android.timezone {
-
-  public final class CountryTimeZones {
-    method @Nullable public android.icu.util.TimeZone getDefaultTimeZone();
-    method @Nullable public String getDefaultTimeZoneId();
-    method @NonNull public java.util.List<android.timezone.CountryTimeZones.TimeZoneMapping> getEffectiveTimeZoneMappingsAt(long);
-    method public boolean hasUtcZone(long);
-    method public boolean isDefaultTimeZoneBoosted();
-    method @Nullable public android.timezone.CountryTimeZones.OffsetResult lookupByOffsetWithBias(int, @Nullable Boolean, @Nullable Integer, long, @Nullable android.icu.util.TimeZone);
-    method public boolean matchesCountryCode(@NonNull String);
-  }
-
-  public static final class CountryTimeZones.OffsetResult {
-    ctor public CountryTimeZones.OffsetResult(@NonNull android.icu.util.TimeZone, boolean);
-    method @NonNull public android.icu.util.TimeZone getTimeZone();
-    method public boolean isOnlyMatch();
-  }
-
-  public static final class CountryTimeZones.TimeZoneMapping {
-    method @NonNull public android.icu.util.TimeZone getTimeZone();
-    method @NonNull public String getTimeZoneId();
-  }
-
-  public final class TelephonyLookup {
-    method @NonNull public static android.timezone.TelephonyLookup getInstance();
-    method @Nullable public android.timezone.TelephonyNetworkFinder getTelephonyNetworkFinder();
-  }
-
-  public final class TelephonyNetwork {
-    method @NonNull public String getCountryIsoCode();
-    method @NonNull public String getMcc();
-    method @NonNull public String getMnc();
-  }
-
-  public final class TelephonyNetworkFinder {
-    method @Nullable public android.timezone.TelephonyNetwork findNetworkByMccMnc(@NonNull String, @NonNull String);
-  }
-
-  public final class TimeZoneFinder {
-    method @Nullable public String getIanaVersion();
-    method @NonNull public static android.timezone.TimeZoneFinder getInstance();
-    method @Nullable public android.timezone.CountryTimeZones lookupCountryTimeZones(@NonNull String);
-  }
-
-  public final class TzDataSetVersion {
-    method public static int currentFormatMajorVersion();
-    method public static int currentFormatMinorVersion();
-    method public int getFormatMajorVersion();
-    method public int getFormatMinorVersion();
-    method public int getRevision();
-    method @NonNull public String getRulesVersion();
-    method public static boolean isCompatibleWithThisDevice(android.timezone.TzDataSetVersion);
-    method @NonNull public static android.timezone.TzDataSetVersion read() throws java.io.IOException, android.timezone.TzDataSetVersion.TzDataSetException;
-  }
-
-  public static final class TzDataSetVersion.TzDataSetException extends java.lang.Exception {
-    ctor public TzDataSetVersion.TzDataSetException(String);
-    ctor public TzDataSetVersion.TzDataSetException(String, Throwable);
-  }
-
-  public final class ZoneInfoDb {
-    method @NonNull public static android.timezone.ZoneInfoDb getInstance();
-    method @NonNull public String getVersion();
-  }
-
-}
-
 package android.util {
 
   public final class Log {
diff --git a/api/system-current.txt b/api/system-current.txt
index dc126d8..a2de8fd 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -188,6 +188,7 @@
     field public static final String REGISTER_CALL_PROVIDER = "android.permission.REGISTER_CALL_PROVIDER";
     field public static final String REGISTER_CONNECTION_MANAGER = "android.permission.REGISTER_CONNECTION_MANAGER";
     field public static final String REGISTER_SIM_SUBSCRIPTION = "android.permission.REGISTER_SIM_SUBSCRIPTION";
+    field public static final String REGISTER_STATS_PULL_ATOM = "android.permission.REGISTER_STATS_PULL_ATOM";
     field public static final String REMOTE_DISPLAY_PROVIDER = "android.permission.REMOTE_DISPLAY_PROVIDER";
     field public static final String REMOVE_DRM_CERTIFICATES = "android.permission.REMOVE_DRM_CERTIFICATES";
     field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
@@ -218,7 +219,6 @@
     field public static final String STOP_APP_SWITCHES = "android.permission.STOP_APP_SWITCHES";
     field public static final String SUBSTITUTE_NOTIFICATION_APP_NAME = "android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME";
     field public static final String SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON = "android.permission.SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON";
-    field public static final String SUGGEST_PHONE_TIME_AND_ZONE = "android.permission.SUGGEST_PHONE_TIME_AND_ZONE";
     field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS";
     field public static final String SYSTEM_CAMERA = "android.permission.SYSTEM_CAMERA";
     field public static final String TETHER_PRIVILEGED = "android.permission.TETHER_PRIVILEGED";
@@ -693,7 +693,7 @@
     method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public long[] getRegisteredExperimentIds() throws android.app.StatsManager.StatsUnavailableException;
     method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public byte[] getReports(long) throws android.app.StatsManager.StatsUnavailableException;
     method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public byte[] getStatsMetadata() throws android.app.StatsManager.StatsUnavailableException;
-    method public void registerPullAtomCallback(int, @Nullable android.app.StatsManager.PullAtomMetadata, @NonNull java.util.concurrent.Executor, @NonNull android.app.StatsManager.StatsPullAtomCallback);
+    method @RequiresPermission(android.Manifest.permission.REGISTER_STATS_PULL_ATOM) public void registerPullAtomCallback(int, @Nullable android.app.StatsManager.PullAtomMetadata, @NonNull java.util.concurrent.Executor, @NonNull android.app.StatsManager.StatsPullAtomCallback);
     method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void removeConfig(long) throws android.app.StatsManager.StatsUnavailableException;
     method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public boolean removeConfiguration(long);
     method @NonNull @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public long[] setActiveConfigsChangedOperation(@Nullable android.app.PendingIntent) throws android.app.StatsManager.StatsUnavailableException;
@@ -701,7 +701,7 @@
     method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public boolean setBroadcastSubscriber(long, long, android.app.PendingIntent);
     method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public boolean setDataFetchOperation(long, android.app.PendingIntent);
     method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void setFetchReportsOperation(android.app.PendingIntent, long) throws android.app.StatsManager.StatsUnavailableException;
-    method public void unregisterPullAtomCallback(int);
+    method @RequiresPermission(android.Manifest.permission.REGISTER_STATS_PULL_ATOM) public void unregisterPullAtomCallback(int);
     field public static final String ACTION_STATSD_STARTED = "android.app.action.STATSD_STARTED";
     field public static final String EXTRA_STATS_ACTIVE_CONFIG_KEYS = "android.app.extra.STATS_ACTIVE_CONFIG_KEYS";
     field public static final String EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES = "android.app.extra.STATS_BROADCAST_SUBSCRIBER_COOKIES";
@@ -1152,6 +1152,16 @@
 
 }
 
+package android.app.compat {
+
+  public final class CompatChanges {
+    method public static boolean isChangeEnabled(long);
+    method public static boolean isChangeEnabled(long, @NonNull String, @NonNull android.os.UserHandle);
+    method public static boolean isChangeEnabled(long, int);
+  }
+
+}
+
 package android.app.contentsuggestions {
 
   public final class ClassificationsRequest implements android.os.Parcelable {
@@ -1473,9 +1483,9 @@
 
   public final class BluetoothAdapter {
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean addOnMetadataChangedListener(@NonNull android.bluetooth.BluetoothDevice, @NonNull java.util.concurrent.Executor, @NonNull android.bluetooth.BluetoothAdapter.OnMetadataChangedListener);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean connectAllEnabledProfiles(@NonNull android.bluetooth.BluetoothDevice);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean connectAllEnabledProfiles(@NonNull android.bluetooth.BluetoothDevice);
     method public boolean disableBLE();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean disconnectAllEnabledProfiles(@NonNull android.bluetooth.BluetoothDevice);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean disconnectAllEnabledProfiles(@NonNull android.bluetooth.BluetoothDevice);
     method public boolean enableBLE();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean enableNoAutoConnect();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean factoryReset();
@@ -2099,6 +2109,10 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.LauncherApps.AppUsageLimit> CREATOR;
   }
 
+  public static class LauncherApps.ShortcutQuery {
+    method @NonNull public android.content.pm.LauncherApps.ShortcutQuery setLocusIds(@Nullable java.util.List<android.content.LocusId>);
+  }
+
   public class PackageInstaller {
     method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setPermissionsResult(int, boolean);
     field public static final int DATA_LOADER_TYPE_INCREMENTAL = 2; // 0x2
@@ -2202,6 +2216,8 @@
     field public static final String FEATURE_TELEPHONY_CARRIERLOCK = "android.hardware.telephony.carrierlock";
     field public static final int FLAGS_PERMISSION_RESERVED_PERMISSIONCONTROLLER = -268435456; // 0xf0000000
     field public static final int FLAG_PERMISSION_APPLY_RESTRICTION = 16384; // 0x4000
+    field public static final int FLAG_PERMISSION_DONT_AUTO_REVOKE = 131072; // 0x20000
+    field public static final int FLAG_PERMISSION_DONT_AUTO_REVOKE_USER_SET = 262144; // 0x40000
     field public static final int FLAG_PERMISSION_GRANTED_BY_DEFAULT = 32; // 0x20
     field public static final int FLAG_PERMISSION_GRANTED_BY_ROLE = 32768; // 0x8000
     field public static final int FLAG_PERMISSION_ONE_TIME = 65536; // 0x10000
@@ -2285,7 +2301,7 @@
     method public void onPermissionsChanged(int);
   }
 
-  @IntDef(prefix={"FLAG_PERMISSION_"}, value={android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET, android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE, android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PackageManager.PermissionFlags {
+  @IntDef(prefix={"FLAG_PERMISSION_"}, value={android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET, android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE, android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME, android.content.pm.PackageManager.FLAG_PERMISSION_DONT_AUTO_REVOKE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PackageManager.PermissionFlags {
   }
 
   public class PermissionGroupInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
@@ -3679,17 +3695,17 @@
   }
 
   public static final class SoundTrigger.ModelParamRange implements android.os.Parcelable {
+    method public int getEnd();
+    method public int getStart();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.ModelParamRange> CREATOR;
-    field public final int end;
-    field public final int start;
   }
 
   public static final class SoundTrigger.ModuleProperties implements android.os.Parcelable {
     method public int describeContents();
     method public void writeToParcel(android.os.Parcel, int);
-    field public static final int CAPABILITY_ECHO_CANCELLATION = 1; // 0x1
-    field public static final int CAPABILITY_NOISE_SUPPRESSION = 2; // 0x2
+    field public static final int AUDIO_CAPABILITY_ECHO_CANCELLATION = 1; // 0x1
+    field public static final int AUDIO_CAPABILITY_NOISE_SUPPRESSION = 2; // 0x2
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.ModuleProperties> CREATOR;
     field public final int audioCapabilities;
     field @NonNull public final String description;
@@ -4318,7 +4334,7 @@
   }
 
   public final class AudioRecordingConfiguration implements android.os.Parcelable {
-    method public int getClientUid();
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getClientUid();
   }
 
   public class HwAudioSource {
@@ -6291,7 +6307,9 @@
     method @Nullable public String getSSID();
     method @NonNull public int[] getTransportTypes();
     method public boolean satisfiedByNetworkCapabilities(@Nullable android.net.NetworkCapabilities);
-    method public void setAdministratorUids(@NonNull java.util.List<java.lang.Integer>);
+    method @NonNull public android.net.NetworkCapabilities setAdministratorUids(@NonNull java.util.List<java.lang.Integer>);
+    method @NonNull public android.net.NetworkCapabilities setRequestorPackageName(@NonNull String);
+    method @NonNull public android.net.NetworkCapabilities setRequestorUid(int);
     method @NonNull public android.net.NetworkCapabilities setSSID(@Nullable String);
     method @NonNull public android.net.NetworkCapabilities setTransportInfo(@NonNull android.net.TransportInfo);
     field public static final int NET_CAPABILITY_OEM_PAID = 22; // 0x16
@@ -6343,6 +6361,8 @@
   }
 
   public class NetworkRequest implements android.os.Parcelable {
+    method @Nullable public String getRequestorPackageName();
+    method public int getRequestorUid();
     method public boolean satisfiedBy(@Nullable android.net.NetworkCapabilities);
   }
 
@@ -6427,7 +6447,6 @@
   }
 
   public abstract class NetworkSpecifier {
-    method public void assertValidFromUid(int);
     method @Nullable public android.net.NetworkSpecifier redact();
     method public abstract boolean satisfiedBy(@Nullable android.net.NetworkSpecifier);
   }
@@ -7518,9 +7537,6 @@
     method @Deprecated public boolean isNoInternetAccessExpected();
     method @Deprecated public void setIpConfiguration(@Nullable android.net.IpConfiguration);
     method @Deprecated public void setNetworkSelectionStatus(@NonNull android.net.wifi.WifiConfiguration.NetworkSelectionStatus);
-    field @Deprecated public static final int AP_BAND_2GHZ = 0; // 0x0
-    field @Deprecated public static final int AP_BAND_5GHZ = 1; // 0x1
-    field @Deprecated public static final int AP_BAND_ANY = -1; // 0xffffffff
     field @Deprecated public static final int INVALID_NETWORK_ID = -1; // 0xffffffff
     field @Deprecated public static final int METERED_OVERRIDE_METERED = 1; // 0x1
     field @Deprecated public static final int METERED_OVERRIDE_NONE = 0; // 0x0
@@ -7530,7 +7546,6 @@
     field @Deprecated public static final int RECENT_FAILURE_AP_UNABLE_TO_HANDLE_NEW_STA = 17; // 0x11
     field @Deprecated public static final int RECENT_FAILURE_NONE = 0; // 0x0
     field @Deprecated public boolean allowAutojoin;
-    field @Deprecated public int apBand;
     field @Deprecated public int carrierId;
     field @Deprecated public String creatorName;
     field @Deprecated public int creatorUid;
@@ -7559,13 +7574,12 @@
   @Deprecated public static class WifiConfiguration.NetworkSelectionStatus {
     method @Deprecated public int getDisableReasonCounter(int);
     method @Deprecated public long getDisableTime();
+    method @Deprecated public static int getMaxNetworkSelectionDisableReason();
     method @Deprecated @Nullable public static String getNetworkDisableReasonString(int);
     method @Deprecated public int getNetworkSelectionDisableReason();
     method @Deprecated public int getNetworkSelectionStatus();
     method @Deprecated @NonNull public String getNetworkStatusString();
     method @Deprecated public boolean hasEverConnected();
-    method @Deprecated public boolean isNetworkEnabled();
-    method @Deprecated public boolean isNetworkPermanentlyDisabled();
     field @Deprecated public static final int DISABLED_ASSOCIATION_REJECTION = 1; // 0x1
     field @Deprecated public static final int DISABLED_AUTHENTICATION_FAILURE = 2; // 0x2
     field @Deprecated public static final int DISABLED_AUTHENTICATION_NO_CREDENTIALS = 5; // 0x5
@@ -7576,7 +7590,6 @@
     field @Deprecated public static final int DISABLED_NONE = 0; // 0x0
     field @Deprecated public static final int DISABLED_NO_INTERNET_PERMANENT = 6; // 0x6
     field @Deprecated public static final int DISABLED_NO_INTERNET_TEMPORARY = 4; // 0x4
-    field @Deprecated public static final int NETWORK_SELECTION_DISABLED_MAX = 10; // 0xa
     field @Deprecated public static final int NETWORK_SELECTION_ENABLED = 0; // 0x0
     field @Deprecated public static final int NETWORK_SELECTION_PERMANENTLY_DISABLED = 2; // 0x2
     field @Deprecated public static final int NETWORK_SELECTION_TEMPORARY_DISABLED = 1; // 0x1
@@ -8200,7 +8213,7 @@
     ctor public NativeScanResult();
     method public int describeContents();
     method @NonNull public byte[] getBssid();
-    method @NonNull public java.util.BitSet getCapabilities();
+    method @NonNull public int getCapabilities();
     method public int getFrequencyMhz();
     method @NonNull public byte[] getInformationElements();
     method @NonNull public java.util.List<android.net.wifi.wificond.RadioChainInfo> getRadioChainInfos();
@@ -8236,12 +8249,12 @@
   public final class PnoSettings implements android.os.Parcelable {
     ctor public PnoSettings();
     method public int describeContents();
-    method public int getIntervalMillis();
+    method public long getIntervalMillis();
     method public int getMin2gRssiDbm();
     method public int getMin5gRssiDbm();
     method public int getMin6gRssiDbm();
     method @NonNull public java.util.List<android.net.wifi.wificond.PnoNetwork> getPnoNetworks();
-    method public void setIntervalMillis(int);
+    method public void setIntervalMillis(long);
     method public void setMin2gRssiDbm(int);
     method public void setMin5gRssiDbm(int);
     method public void setMin6gRssiDbm(int);
@@ -8266,10 +8279,10 @@
     method @Nullable public android.net.wifi.wificond.DeviceWiphyCapabilities getDeviceWiphyCapabilities(@NonNull String);
     method @NonNull public java.util.List<android.net.wifi.wificond.NativeScanResult> getScanResults(@NonNull String, int);
     method @Nullable public android.net.wifi.wificond.WifiCondManager.TxPacketCounters getTxPacketCounters(@NonNull String);
-    method public boolean initialize(@NonNull Runnable);
     method @Nullable public static android.net.wifi.wificond.WifiCondManager.OemSecurityType parseOemSecurityTypeElement(int, int, @NonNull byte[]);
     method public boolean registerApCallback(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.wificond.WifiCondManager.SoftApCallback);
     method public void sendMgmtFrame(@NonNull String, @NonNull byte[], int, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.wificond.WifiCondManager.SendMgmtFrameCallback);
+    method public void setOnServiceDeadCallback(@NonNull Runnable);
     method public boolean setupInterfaceForClientMode(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.wificond.WifiCondManager.ScanEventCallback, @NonNull android.net.wifi.wificond.WifiCondManager.ScanEventCallback);
     method public boolean setupInterfaceForSoftApMode(@NonNull String);
     method @Nullable public android.net.wifi.wificond.WifiCondManager.SignalPollResult signalPoll(@NonNull String);
@@ -12410,7 +12423,6 @@
     field public static final int CDMA_SUBSCRIPTION_RUIM_SIM = 0; // 0x0
     field public static final int CDMA_SUBSCRIPTION_UNKNOWN = -1; // 0xffffffff
     field public static final int CHANGE_ICC_LOCK_SUCCESS = 2147483647; // 0x7fffffff
-    field public static final int DEFAULT_PREFERRED_NETWORK_MODE = 0; // 0x0
     field public static final String EXTRA_ANOMALY_DESCRIPTION = "android.telephony.extra.ANOMALY_DESCRIPTION";
     field public static final String EXTRA_ANOMALY_ID = "android.telephony.extra.ANOMALY_ID";
     field @Deprecated public static final String EXTRA_APN_PROTOCOL = "apnProto";
diff --git a/api/system-lint-baseline.txt b/api/system-lint-baseline.txt
index 2f1889c..0caee6b 100644
--- a/api/system-lint-baseline.txt
+++ b/api/system-lint-baseline.txt
@@ -64,10 +64,6 @@
     
 
 
-HeavyBitSet: android.net.wifi.wificond.NativeScanResult#getCapabilities():
-    
-
-
 IntentBuilderName: android.content.Context#registerReceiverForAllUsers(android.content.BroadcastReceiver, android.content.IntentFilter, String, android.os.Handler):
     Methods creating an Intent should be named `create<Foo>Intent()`, was `registerReceiverForAllUsers`
 
@@ -200,8 +196,6 @@
     
 MutableBareField: android.net.wifi.WifiConfiguration#allowAutojoin:
     
-MutableBareField: android.net.wifi.WifiConfiguration#apBand:
-    
 MutableBareField: android.net.wifi.WifiConfiguration#carrierId:
     
 MutableBareField: android.net.wifi.WifiConfiguration#fromWifiNetworkSpecifier:
diff --git a/api/test-current.txt b/api/test-current.txt
index 9472ce2..e1f8382 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -183,6 +183,9 @@
     field public static final int HISTORICAL_MODE_DISABLED = 0; // 0x0
     field public static final int HISTORICAL_MODE_ENABLED_ACTIVE = 1; // 0x1
     field public static final int HISTORICAL_MODE_ENABLED_PASSIVE = 2; // 0x2
+    field public static final String KEY_BG_STATE_SETTLE_TIME = "bg_state_settle_time";
+    field public static final String KEY_FG_SERVICE_STATE_SETTLE_TIME = "fg_service_state_settle_time";
+    field public static final String KEY_TOP_STATE_SETTLE_TIME = "top_state_settle_time";
     field public static final String OPSTR_ACCEPT_HANDOVER = "android:accept_handover";
     field public static final String OPSTR_ACCESS_NOTIFICATIONS = "android:access_notifications";
     field public static final String OPSTR_ACTIVATE_VPN = "android:activate_vpn";
@@ -416,6 +419,7 @@
     method public void setFgServiceShown(boolean);
     method public void setImportanceLockedByCriticalDeviceFunction(boolean);
     method public void setImportanceLockedByOEM(boolean);
+    method public void setImportantConversation(boolean);
     method public void setOriginalImportance(int);
   }
 
@@ -885,6 +889,7 @@
     method public abstract boolean arePermissionsIndividuallyControlled();
     method @Nullable public String getContentCaptureServicePackageName();
     method @Nullable @RequiresPermission("android.permission.INTERACT_ACROSS_USERS_FULL") public abstract String getDefaultBrowserPackageNameAsUser(int);
+    method @Nullable public String getDefaultTextClassifierPackageName();
     method @Nullable public String getIncidentReportApproverPackageName();
     method public abstract int getInstallReason(@NonNull String, @NonNull android.os.UserHandle);
     method @NonNull public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplicationsAsUser(int, int);
@@ -894,6 +899,7 @@
     method @RequiresPermission(anyOf={"android.permission.GRANT_RUNTIME_PERMISSIONS", "android.permission.REVOKE_RUNTIME_PERMISSIONS", "android.permission.GET_RUNTIME_PERMISSIONS"}) public abstract int getPermissionFlags(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
     method @NonNull public abstract String getServicesSystemSharedLibraryPackageName();
     method @NonNull public abstract String getSharedSystemSharedLibraryPackageName();
+    method @Nullable public String getSystemTextClassifierPackageName();
     method @Nullable public String[] getTelephonyPackageNames();
     method @Nullable public String getWellbeingPackageName();
     method @RequiresPermission("android.permission.GRANT_RUNTIME_PERMISSIONS") public abstract void grantRuntimePermission(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
diff --git a/cmds/idmap2/include/idmap2/ResourceUtils.h b/cmds/idmap2/include/idmap2/ResourceUtils.h
index de1dbc9..c643b0e 100644
--- a/cmds/idmap2/include/idmap2/ResourceUtils.h
+++ b/cmds/idmap2/include/idmap2/ResourceUtils.h
@@ -37,6 +37,10 @@
 
 namespace utils {
 
+// Returns whether the Res_value::data_type represents a dynamic or regular resource reference.
+bool IsReference(uint8_t data_type);
+
+// Converts the Res_value::data_type to a human-readable string representation.
 StringPiece DataTypeToString(uint8_t data_type);
 
 struct OverlayManifestInfo {
diff --git a/cmds/idmap2/libidmap2/ResourceMapping.cpp b/cmds/idmap2/libidmap2/ResourceMapping.cpp
index 4074789..43cfec3 100644
--- a/cmds/idmap2/libidmap2/ResourceMapping.cpp
+++ b/cmds/idmap2/libidmap2/ResourceMapping.cpp
@@ -27,6 +27,7 @@
 #include "idmap2/ResourceUtils.h"
 
 using android::base::StringPrintf;
+using android::idmap2::utils::IsReference;
 using android::idmap2::utils::ResToTypeEntryName;
 
 namespace android::idmap2 {
@@ -200,8 +201,7 @@
     // Only rewrite resources defined within the overlay package to their corresponding target
     // resource ids at runtime.
     bool rewrite_overlay_reference =
-        (overlay_resource->dataType == Res_value::TYPE_REFERENCE ||
-         overlay_resource->dataType == Res_value::TYPE_DYNAMIC_REFERENCE)
+        IsReference(overlay_resource->dataType)
             ? overlay_package_id == EXTRACT_PACKAGE(overlay_resource->data)
             : false;
 
@@ -331,8 +331,13 @@
   std::unique_ptr<uint8_t[]> string_pool_data;
   Result<ResourceMapping> resource_mapping = {{}};
   if (overlay_info.resource_mapping != 0U) {
+    // Use the dynamic reference table to find the assigned resource id of the map xml.
+    const auto& ref_table = overlay_asset_manager.GetDynamicRefTableForCookie(0);
+    uint32_t resource_mapping_id = overlay_info.resource_mapping;
+    ref_table->lookupResourceId(&resource_mapping_id);
+
     // Load the overlay resource mappings from the file specified using android:resourcesMap.
-    auto asset = OpenNonAssetFromResource(overlay_info.resource_mapping, overlay_asset_manager);
+    auto asset = OpenNonAssetFromResource(resource_mapping_id, overlay_asset_manager);
     if (!asset) {
       return Error("failed opening xml for android:resourcesMap: %s",
                    asset.GetErrorMessage().c_str());
@@ -404,8 +409,7 @@
 
   target_map_.insert(std::make_pair(target_resource, TargetValue{data_type, data_value}));
 
-  if (rewrite_overlay_reference &&
-      (data_type == Res_value::TYPE_REFERENCE || data_type == Res_value::TYPE_DYNAMIC_REFERENCE)) {
+  if (rewrite_overlay_reference && IsReference(data_type)) {
     overlay_map_.insert(std::make_pair(data_value, target_resource));
   }
 
@@ -421,8 +425,7 @@
   const TargetValue value = target_iter->second;
   target_map_.erase(target_iter);
 
-  if (value.data_type != Res_value::TYPE_REFERENCE &&
-      value.data_type != Res_value::TYPE_DYNAMIC_REFERENCE) {
+  if (!IsReference(value.data_type)) {
     return;
   }
 
diff --git a/cmds/idmap2/libidmap2/ResourceUtils.cpp b/cmds/idmap2/libidmap2/ResourceUtils.cpp
index a5df746..98d026b 100644
--- a/cmds/idmap2/libidmap2/ResourceUtils.cpp
+++ b/cmds/idmap2/libidmap2/ResourceUtils.cpp
@@ -33,6 +33,10 @@
 
 namespace android::idmap2::utils {
 
+bool IsReference(uint8_t data_type) {
+  return data_type == Res_value::TYPE_REFERENCE || data_type == Res_value::TYPE_DYNAMIC_REFERENCE;
+}
+
 StringPiece DataTypeToString(uint8_t data_type) {
   switch (data_type) {
     case Res_value::TYPE_NULL:
@@ -133,7 +137,7 @@
   }
 
   if (auto result_value = overlay_it->GetAttributeValue("resourcesMap")) {
-    if ((*result_value).dataType == Res_value::TYPE_REFERENCE) {
+    if (IsReference((*result_value).dataType)) {
       info.resource_mapping = (*result_value).data;
     } else {
       return Error("android:resourcesMap is not a reference in AndroidManifest.xml of %s",
diff --git a/cmds/idmap2/tests/FileUtilsTests.cpp b/cmds/idmap2/tests/FileUtilsTests.cpp
index f55acee..8af4037 100644
--- a/cmds/idmap2/tests/FileUtilsTests.cpp
+++ b/cmds/idmap2/tests/FileUtilsTests.cpp
@@ -56,12 +56,12 @@
     return type == DT_REG && path.size() > 4 && path.compare(path.size() - 4, 4, ".apk") == 0;
   });
   ASSERT_THAT(v, NotNull());
-  ASSERT_EQ(v->size(), 10U);
+  ASSERT_EQ(v->size(), 11U);
   ASSERT_EQ(std::set<std::string>(v->begin(), v->end()),
             std::set<std::string>(
                 {root + "/target/target.apk", root + "/target/target-no-overlayable.apk",
                  root + "/overlay/overlay.apk", root + "/overlay/overlay-no-name.apk",
-                 root + "/overlay/overlay-no-name-static.apk",
+                 root + "/overlay/overlay-no-name-static.apk", root + "/overlay/overlay-shared.apk",
                  root + "/overlay/overlay-static-1.apk", root + "/overlay/overlay-static-2.apk",
                  root + "/signature-overlay/signature-overlay.apk",
                  root + "/system-overlay/system-overlay.apk",
diff --git a/cmds/idmap2/tests/IdmapTests.cpp b/cmds/idmap2/tests/IdmapTests.cpp
index 4bc6255..a2c1560 100644
--- a/cmds/idmap2/tests/IdmapTests.cpp
+++ b/cmds/idmap2/tests/IdmapTests.cpp
@@ -247,6 +247,43 @@
   ASSERT_OVERLAY_ENTRY(overlay_entries[3], 0x7f020002, 0x7f02000f);
 }
 
+TEST(IdmapTests, CreateIdmapDataFromApkAssetsSharedLibOverlay) {
+  std::string target_apk_path = GetTestDataPath() + "/target/target.apk";
+  std::string overlay_apk_path = GetTestDataPath() + "/overlay/overlay-shared.apk";
+
+  std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
+  ASSERT_THAT(target_apk, NotNull());
+
+  std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
+  ASSERT_THAT(overlay_apk, NotNull());
+
+  auto idmap_result = Idmap::FromApkAssets(*target_apk, *overlay_apk, PolicyFlags::POLICY_PUBLIC,
+                                           /* enforce_overlayable */ true);
+  ASSERT_TRUE(idmap_result) << idmap_result.GetErrorMessage();
+  auto& idmap = *idmap_result;
+  ASSERT_THAT(idmap, NotNull());
+
+  const std::vector<std::unique_ptr<const IdmapData>>& dataBlocks = idmap->GetData();
+  ASSERT_EQ(dataBlocks.size(), 1U);
+
+  const std::unique_ptr<const IdmapData>& data = dataBlocks[0];
+  ASSERT_THAT(data, NotNull());
+
+  const auto& target_entries = data->GetTargetEntries();
+  ASSERT_EQ(target_entries.size(), 4U);
+  ASSERT_TARGET_ENTRY(target_entries[0], 0x7f010000, Res_value::TYPE_DYNAMIC_REFERENCE, 0x00010000);
+  ASSERT_TARGET_ENTRY(target_entries[1], 0x7f02000c, Res_value::TYPE_DYNAMIC_REFERENCE, 0x00020000);
+  ASSERT_TARGET_ENTRY(target_entries[2], 0x7f02000e, Res_value::TYPE_DYNAMIC_REFERENCE, 0x00020001);
+  ASSERT_TARGET_ENTRY(target_entries[3], 0x7f02000f, Res_value::TYPE_DYNAMIC_REFERENCE, 0x00020002);
+
+  const auto& overlay_entries = data->GetOverlayEntries();
+  ASSERT_EQ(target_entries.size(), 4U);
+  ASSERT_OVERLAY_ENTRY(overlay_entries[0], 0x00010000, 0x7f010000);
+  ASSERT_OVERLAY_ENTRY(overlay_entries[1], 0x00020000, 0x7f02000c);
+  ASSERT_OVERLAY_ENTRY(overlay_entries[2], 0x00020001, 0x7f02000e);
+  ASSERT_OVERLAY_ENTRY(overlay_entries[3], 0x00020002, 0x7f02000f);
+}
+
 TEST(IdmapTests, CreateIdmapDataDoNotRewriteNonOverlayResourceId) {
   OverlayManifestInfo info{};
   info.target_package = "test.target";
diff --git a/cmds/idmap2/tests/data/overlay/build b/cmds/idmap2/tests/data/overlay/build
index b921b0d..114b099 100755
--- a/cmds/idmap2/tests/data/overlay/build
+++ b/cmds/idmap2/tests/data/overlay/build
@@ -51,4 +51,12 @@
     -o overlay-static-2.apk \
     compiled.flata
 
+aapt2 link \
+    --no-resource-removal \
+    --shared-lib \
+    -I "$FRAMEWORK_RES_APK" \
+    --manifest AndroidManifest.xml \
+    -o overlay-shared.apk \
+    compiled.flata
+
 rm compiled.flata
diff --git a/cmds/idmap2/tests/data/overlay/overlay-shared.apk b/cmds/idmap2/tests/data/overlay/overlay-shared.apk
new file mode 100644
index 0000000..93dcc82
--- /dev/null
+++ b/cmds/idmap2/tests/data/overlay/overlay-shared.apk
Binary files differ
diff --git a/cmds/incident/Android.bp b/cmds/incident/Android.bp
index 9e9dac1..94855aa 100644
--- a/cmds/incident/Android.bp
+++ b/cmds/incident/Android.bp
@@ -26,7 +26,7 @@
         "libcutils",
         "liblog",
         "libutils",
-        "libincident",
+        "libincidentpriv",
     ],
 
     static_libs: [
diff --git a/cmds/incident_helper/Android.bp b/cmds/incident_helper/Android.bp
index 64f4c66..f07743e 100644
--- a/cmds/incident_helper/Android.bp
+++ b/cmds/incident_helper/Android.bp
@@ -44,7 +44,7 @@
         "src/ih_util.cpp",
     ],
 
-    generated_headers: ["gen-platform-proto-constants"],
+    generated_headers: ["framework-cppstream-protos"],
 
     shared_libs: [
         "libbase",
diff --git a/cmds/incidentd/Android.bp b/cmds/incidentd/Android.bp
index 25e0328..c47526a 100644
--- a/cmds/incidentd/Android.bp
+++ b/cmds/incidentd/Android.bp
@@ -43,7 +43,7 @@
     ],
 
     local_include_dirs: ["src"],
-    generated_headers: ["gen-platform-proto-constants"],
+    generated_headers: ["framework-cppstream-protos"],
 
     proto: {
         type: "lite",
@@ -54,7 +54,7 @@
         "libbinder",
         "libdebuggerd_client",
         "libdumputils",
-        "libincident",
+        "libincidentpriv",
         "liblog",
         "libprotoutil",
         "libservices",
@@ -98,7 +98,7 @@
     ],
 
     local_include_dirs: ["src"],
-    generated_headers: ["gen-platform-proto-constants"],
+    generated_headers: ["framework-cppstream-protos"],
 
     srcs: [
         "tests/**/*.cpp",
@@ -128,7 +128,7 @@
         "libbinder",
         "libdebuggerd_client",
         "libdumputils",
-        "libincident",
+        "libincidentpriv",
         "liblog",
         "libprotobuf-cpp-full",
         "libprotoutil",
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index ebed4ee..7e069a6 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -64,7 +64,7 @@
         "src/config/ConfigKey.cpp",
         "src/config/ConfigListener.cpp",
         "src/config/ConfigManager.cpp",
-        "src/external/GpuStatsPuller.cpp",
+        "src/experiment_ids.proto",
         "src/external/Perfetto.cpp",
         "src/external/PullResultReceiver.cpp",
         "src/external/puller_util.cpp",
@@ -118,22 +118,19 @@
     ],
 
     static_libs: [
-        "android.frameworks.stats@1.0",
         "libbase",
         "libcutils",
-        "liblog",
         "libprotoutil",
         "libstatslog",
-        "libstatssocket",
+        "libstatsmetadata",
         "libsysutils",
     ],
     shared_libs: [
         "libbinder",
-        "libgraphicsenv",
-        "libhidlbase",
         "libincident",
+        "liblog",
         "libservices",
-        "libstatsmetadata",
+        "libstatssocket",
         "libutils",
     ],
 }
@@ -160,7 +157,7 @@
     ],
 }
 
-cc_library_shared {
+cc_library_static {
     name: "libstatsmetadata",
     host_supported: true,
     generated_sources: [
@@ -223,8 +220,6 @@
 
     shared_libs: ["libgtest_prod"],
 
-    vintf_fragments: ["android.frameworks.stats@1.0-service.xml"],
-
     init_rc: ["statsd.rc"],
 }
 
@@ -277,8 +272,6 @@
         "tests/e2e/PartialBucket_e2e_test.cpp",
         "tests/e2e/ValueMetric_pull_e2e_test.cpp",
         "tests/e2e/WakelockDuration_e2e_test.cpp",
-        "tests/external/GpuStatsPuller_test.cpp",
-        "tests/external/IncidentReportArgs_test.cpp",
         "tests/external/puller_util_test.cpp",
         "tests/external/StatsCallbackPuller_test.cpp",
         "tests/external/StatsPuller_test.cpp",
diff --git a/cmds/statsd/android.frameworks.stats@1.0-service.xml b/cmds/statsd/android.frameworks.stats@1.0-service.xml
deleted file mode 100644
index bb02f66..0000000
--- a/cmds/statsd/android.frameworks.stats@1.0-service.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<manifest version="1.0" type="framework">
-    <hal>
-        <name>android.frameworks.stats</name>
-        <transport>hwbinder</transport>
-        <version>1.0</version>
-        <interface>
-            <name>IStats</name>
-            <instance>default</instance>
-        </interface>
-    </hal>
-</manifest>
diff --git a/cmds/statsd/benchmark/log_event_benchmark.cpp b/cmds/statsd/benchmark/log_event_benchmark.cpp
index 30dfe32..8b68743 100644
--- a/cmds/statsd/benchmark/log_event_benchmark.cpp
+++ b/cmds/statsd/benchmark/log_event_benchmark.cpp
@@ -23,14 +23,14 @@
 namespace statsd {
 
 static size_t createAndParseStatsEvent(uint8_t* msg) {
-    struct stats_event* event = stats_event_obtain();
-    stats_event_set_atom_id(event, 100);
-    stats_event_write_int32(event, 2);
-    stats_event_write_float(event, 2.0);
-    stats_event_build(event);
+    AStatsEvent* event = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(event, 100);
+    AStatsEvent_writeInt32(event, 2);
+    AStatsEvent_writeFloat(event, 2.0);
+    AStatsEvent_build(event);
 
     size_t size;
-    uint8_t* buf = stats_event_get_buffer(event, &size);
+    uint8_t* buf = AStatsEvent_getBuffer(event, &size);
     memcpy(msg, buf, size);
     return size;
 }
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index 879b3c3..bde15a5 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -20,13 +20,17 @@
 #include "StatsLogProcessor.h"
 
 #include <android-base/file.h>
+#include <cutils/multiuser.h>
 #include <frameworks/base/cmds/statsd/src/active_config_list.pb.h>
+#include <frameworks/base/cmds/statsd/src/experiment_ids.pb.h>
 
 #include "android-base/stringprintf.h"
 #include "atoms_info.h"
 #include "external/StatsPullerManager.h"
 #include "guardrail/StatsdStats.h"
+#include "logd/LogEvent.h"
 #include "metrics/CountMetricProducer.h"
+#include "StatsService.h"
 #include "state/StateManager.h"
 #include "stats_log_util.h"
 #include "stats_util.h"
@@ -68,6 +72,10 @@
 // for ActiveConfigList
 const int FIELD_ID_ACTIVE_CONFIG_LIST_CONFIG = 1;
 
+// for permissions checks
+constexpr const char* kPermissionDump = "android.permission.DUMP";
+constexpr const char* kPermissionUsage = "android.permission.PACKAGE_USAGE_STATS";
+
 #define NS_PER_HOUR 3600 * NS_PER_SEC
 
 #define STATS_ACTIVE_METRIC_DIR "/data/misc/stats-active-metric"
@@ -181,6 +189,115 @@
     }
 }
 
+void StatsLogProcessor::onBinaryPushStateChangedEventLocked(LogEvent* event) {
+    pid_t pid = event->GetPid();
+    uid_t uid = event->GetUid();
+    if (!checkPermissionForIds(kPermissionDump, pid, uid) ||
+        !checkPermissionForIds(kPermissionUsage, pid, uid)) {
+        return;
+    }
+    status_t err = NO_ERROR, err2 = NO_ERROR, err3 = NO_ERROR, err4 = NO_ERROR;
+    string trainName = string(event->GetString(1 /*train name field id*/, &err));
+    int64_t trainVersionCode = event->GetLong(2 /*train version field id*/, &err2);
+    int32_t state = int32_t(event->GetLong(6 /*state field id*/, &err3));
+#ifdef NEW_ENCODING_SCHEME
+    std::vector<uint8_t> trainExperimentIdBytes =
+        event->GetStorage(7 /*experiment ids field id*/, &err4);
+#else
+    string trainExperimentIdString = event->GetString(7 /*experiment ids field id*/, &err4);
+#endif
+    if (err != NO_ERROR || err2 != NO_ERROR || err3 != NO_ERROR || err4 != NO_ERROR) {
+        ALOGE("Failed to parse fields in binary push state changed log event");
+        return;
+    }
+    ExperimentIds trainExperimentIds;
+#ifdef NEW_ENCODING_SCHEME
+    if (!trainExperimentIds.ParseFromArray(trainExperimentIdBytes.data(),
+                                           trainExperimentIdBytes.size())) {
+#else
+    if (!trainExperimentIds.ParseFromString(trainExperimentIdString)) {
+#endif
+        ALOGE("Failed to parse experimentids in binary push state changed.");
+        return;
+    }
+    vector<int64_t> experimentIdVector = {trainExperimentIds.experiment_id().begin(),
+                                          trainExperimentIds.experiment_id().end()};
+    // Update the train info on disk and get any data the logevent is missing.
+    getAndUpdateTrainInfoOnDisk(
+        state, &trainVersionCode, &trainName, &experimentIdVector);
+
+    std::vector<uint8_t> trainExperimentIdProto;
+    writeExperimentIdsToProto(experimentIdVector, &trainExperimentIdProto);
+    int32_t userId = multiuser_get_user_id(uid);
+
+    event->updateValue(1 /*train name field id*/, trainName, STRING);
+    event->updateValue(2 /*train version field id*/, trainVersionCode, LONG);
+#ifdef NEW_ENCODING_SCHEME
+    event->updateValue(7 /*experiment ids field id*/, trainExperimentIdProto, STORAGE);
+#else
+    event->updateValue(7 /*experiment ids field id*/, trainExperimentIdProto, STRING);
+#endif
+    event->updateValue(8 /*user id field id*/, userId, INT);
+}
+
+void StatsLogProcessor::getAndUpdateTrainInfoOnDisk(int32_t state,
+                                         int64_t* trainVersionCode,
+                                         string* trainName,
+                                         std::vector<int64_t>* experimentIds) {
+    bool readTrainInfoSuccess = false;
+    InstallTrainInfo trainInfoOnDisk;
+    readTrainInfoSuccess = StorageManager::readTrainInfo(trainInfoOnDisk);
+
+    bool resetExperimentIds = false;
+    if (readTrainInfoSuccess) {
+        // Keep the old train version if we received an empty version.
+        if (*trainVersionCode == -1) {
+            *trainVersionCode = trainInfoOnDisk.trainVersionCode;
+        } else if (*trainVersionCode != trainInfoOnDisk.trainVersionCode) {
+        // Reset experiment ids if we receive a new non-empty train version.
+            resetExperimentIds = true;
+        }
+
+        // Keep the old train name if we received an empty train name.
+        if (trainName->size() == 0) {
+            *trainName = trainInfoOnDisk.trainName;
+        } else if (*trainName != trainInfoOnDisk.trainName) {
+            // Reset experiment ids if we received a new valid train name.
+            resetExperimentIds = true;
+        }
+
+        // Reset if we received a different experiment id.
+        if (!experimentIds->empty() &&
+                (trainInfoOnDisk.experimentIds.empty() ||
+                 experimentIds->at(0) != trainInfoOnDisk.experimentIds[0])) {
+            resetExperimentIds = true;
+        }
+    }
+
+    // Find the right experiment IDs
+    if (!resetExperimentIds && readTrainInfoSuccess) {
+        *experimentIds = trainInfoOnDisk.experimentIds;
+    }
+
+    if (!experimentIds->empty()) {
+        int64_t firstId = experimentIds->at(0);
+        switch (state) {
+            case android::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALL_SUCCESS:
+                experimentIds->push_back(firstId + 1);
+                break;
+            case android::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALLER_ROLLBACK_INITIATED:
+                experimentIds->push_back(firstId + 2);
+                break;
+            case android::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALLER_ROLLBACK_SUCCESS:
+                experimentIds->push_back(firstId + 3);
+                break;
+        }
+    }
+
+    StorageManager::writeTrainInfo(*trainVersionCode, *trainName, state, *experimentIds);
+}
+
+
 void StatsLogProcessor::resetConfigs() {
     std::lock_guard<std::mutex> lock(mMetricsMutex);
     resetConfigsLocked(getElapsedRealtimeNs());
@@ -201,6 +318,12 @@
 void StatsLogProcessor::OnLogEvent(LogEvent* event, int64_t elapsedRealtimeNs) {
     std::lock_guard<std::mutex> lock(mMetricsMutex);
 
+    // Hard-coded logic to update train info on disk and fill in any information
+    // this log event may be missing.
+    if (event->GetTagId() == android::util::BINARY_PUSH_STATE_CHANGED) {
+      onBinaryPushStateChangedEventLocked(event);
+    }
+
 #ifdef VERY_VERBOSE_PRINTING
     if (mPrintAllLogs) {
         ALOGI("%s", event->ToString().c_str());
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index c569bc1..c49f2e0 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -196,6 +196,14 @@
     // Handler over the isolated uid change event.
     void onIsolatedUidChangedEventLocked(const LogEvent& event);
 
+    // Handler over the binary push state changed event.
+    void onBinaryPushStateChangedEventLocked(LogEvent* event);
+
+    // Updates train info on disk based on binary push state changed info and
+    // write disk info into parameters.
+    void getAndUpdateTrainInfoOnDisk(int32_t state, int64_t* trainVersionCode,
+                                     string* trainName, std::vector<int64_t>* experimentIds);
+
     // Reset all configs.
     void resetConfigsLocked(const int64_t timestampNs);
     // Reset the specified configs.
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 8a8c1e6..a06e59c 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -70,25 +70,12 @@
     return binder::Status::fromExceptionCode(code, String8(msg.c_str()));
 }
 
-
 static bool checkPermission(const char* permission) {
-    sp<IStatsCompanionService> scs = getStatsCompanionService();
-    if (scs == nullptr) {
-        return false;
-    }
-
-    bool success;
     pid_t pid = IPCThreadState::self()->getCallingPid();
     uid_t uid = IPCThreadState::self()->getCallingUid();
-
-    binder::Status status = scs->checkPermission(String16(permission), pid, uid, &success);
-    if (!status.isOk()) {
-        return false;
-    }
-    return success;
+    return checkPermissionForIds(permission, pid, uid);
 }
 
-
 binder::Status checkUid(uid_t expectedUid) {
     uid_t uid = IPCThreadState::self()->getCallingUid();
     if (uid == expectedUid || uid == AID_ROOT) {
@@ -870,18 +857,8 @@
         dprintf(out, "Incorrect number of argument supplied\n");
         return UNKNOWN_ERROR;
     }
-    android::String16 trainName = android::String16(args[1].c_str());
+    string trainName = string(args[1].c_str());
     int64_t trainVersion = strtoll(args[2].c_str(), nullptr, 10);
-    int options = 0;
-    if (args[3] == "1") {
-        options = options | IStatsd::FLAG_REQUIRE_STAGING;
-    }
-    if (args[4] == "1") {
-        options = options | IStatsd::FLAG_ROLLBACK_ENABLED;
-    }
-    if (args[5] == "1") {
-        options = options | IStatsd::FLAG_REQUIRE_LOW_LATENCY_MONITOR;
-    }
     int32_t state = atoi(args[6].c_str());
     vector<int64_t> experimentIds;
     if (argCount == 8) {
@@ -892,7 +869,10 @@
         }
     }
     dprintf(out, "Logging BinaryPushStateChanged\n");
-    sendBinaryPushStateChangedAtom(trainName, trainVersion, options, state, experimentIds);
+    vector<uint8_t> experimentIdBytes;
+    writeExperimentIdsToProto(experimentIds, &experimentIdBytes);
+    LogEvent event(trainName, trainVersion, args[3], args[4], args[5], state, experimentIdBytes, 0);
+    mProcessor->OnLogEvent(&event);
     return NO_ERROR;
 }
 
@@ -1313,101 +1293,6 @@
     return Status::ok();
 }
 
-Status StatsService::sendBinaryPushStateChangedAtom(const android::String16& trainNameIn,
-                                                    const int64_t trainVersionCodeIn,
-                                                    const int options,
-                                                    const int32_t state,
-                                                    const std::vector<int64_t>& experimentIdsIn) {
-    // Note: We skip the usage stats op check here since we do not have a package name.
-    // This is ok since we are overloading the usage_stats permission.
-    // This method only sends data, it does not receive it.
-    pid_t pid = IPCThreadState::self()->getCallingPid();
-    uid_t uid = IPCThreadState::self()->getCallingUid();
-    // Root, system, and shell always have access
-    if (uid != AID_ROOT && uid != AID_SYSTEM && uid != AID_SHELL) {
-        // Caller must be granted these permissions
-        if (!checkPermission(kPermissionDump)) {
-            return exception(binder::Status::EX_SECURITY,
-                             StringPrintf("UID %d / PID %d lacks permission %s", uid, pid,
-                                          kPermissionDump));
-        }
-        if (!checkPermission(kPermissionUsage)) {
-            return exception(binder::Status::EX_SECURITY,
-                             StringPrintf("UID %d / PID %d lacks permission %s", uid, pid,
-                                          kPermissionUsage));
-        }
-    }
-
-    bool readTrainInfoSuccess = false;
-    InstallTrainInfo trainInfoOnDisk;
-    readTrainInfoSuccess = StorageManager::readTrainInfo(trainInfoOnDisk);
-
-    bool resetExperimentIds = false;
-    int64_t trainVersionCode = trainVersionCodeIn;
-    std::string trainNameUtf8 = std::string(String8(trainNameIn).string());
-    if (readTrainInfoSuccess) {
-        // Keep the old train version if we received an empty version.
-        if (trainVersionCodeIn == -1) {
-            trainVersionCode = trainInfoOnDisk.trainVersionCode;
-        } else if (trainVersionCodeIn != trainInfoOnDisk.trainVersionCode) {
-        // Reset experiment ids if we receive a new non-empty train version.
-            resetExperimentIds = true;
-        }
-
-        // Keep the old train name if we received an empty train name.
-        if (trainNameUtf8.size() == 0) {
-            trainNameUtf8 = trainInfoOnDisk.trainName;
-        } else if (trainNameUtf8 != trainInfoOnDisk.trainName) {
-            // Reset experiment ids if we received a new valid train name.
-            resetExperimentIds = true;
-        }
-
-        // Reset if we received a different experiment id.
-        if (!experimentIdsIn.empty() &&
-                (trainInfoOnDisk.experimentIds.empty() ||
-                 experimentIdsIn[0] != trainInfoOnDisk.experimentIds[0])) {
-            resetExperimentIds = true;
-        }
-    }
-
-    // Find the right experiment IDs
-    std::vector<int64_t> experimentIds;
-    if (resetExperimentIds || !readTrainInfoSuccess) {
-        experimentIds = experimentIdsIn;
-    } else {
-        experimentIds = trainInfoOnDisk.experimentIds;
-    }
-
-    if (!experimentIds.empty()) {
-        int64_t firstId = experimentIds[0];
-        switch (state) {
-            case android::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALL_SUCCESS:
-                experimentIds.push_back(firstId + 1);
-                break;
-            case android::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALLER_ROLLBACK_INITIATED:
-                experimentIds.push_back(firstId + 2);
-                break;
-            case android::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALLER_ROLLBACK_SUCCESS:
-                experimentIds.push_back(firstId + 3);
-                break;
-        }
-    }
-
-    // Flatten the experiment IDs to proto
-    vector<uint8_t> experimentIdsProtoBuffer;
-    writeExperimentIdsToProto(experimentIds, &experimentIdsProtoBuffer);
-    StorageManager::writeTrainInfo(trainVersionCode, trainNameUtf8, state, experimentIds);
-
-    userid_t userId = multiuser_get_user_id(uid);
-    bool requiresStaging = options & IStatsd::FLAG_REQUIRE_STAGING;
-    bool rollbackEnabled = options & IStatsd::FLAG_ROLLBACK_ENABLED;
-    bool requiresLowLatencyMonitor = options & IStatsd::FLAG_REQUIRE_LOW_LATENCY_MONITOR;
-    LogEvent event(trainNameUtf8, trainVersionCode, requiresStaging, rollbackEnabled,
-                   requiresLowLatencyMonitor, state, experimentIdsProtoBuffer, userId);
-    mProcessor->OnLogEvent(&event);
-    return Status::ok();
-}
-
 Status StatsService::sendWatchdogRollbackOccurredAtom(const int32_t rollbackTypeIn,
                                                       const android::String16& packageNameIn,
                                                       const int64_t packageVersionCodeIn,
@@ -1469,7 +1354,6 @@
     return Status::ok();
 }
 
-
 Status StatsService::getRegisteredExperimentIds(std::vector<int64_t>* experimentIdsOut) {
     ENFORCE_UID(AID_SYSTEM);
     // TODO: add verifier permission
@@ -1487,103 +1371,6 @@
     return Status::ok();
 }
 
-hardware::Return<void> StatsService::reportSpeakerImpedance(
-        const SpeakerImpedance& speakerImpedance) {
-    android::util::stats_write(android::util::SPEAKER_IMPEDANCE_REPORTED,
-            speakerImpedance.speakerLocation, speakerImpedance.milliOhms);
-
-    return hardware::Void();
-}
-
-hardware::Return<void> StatsService::reportHardwareFailed(const HardwareFailed& hardwareFailed) {
-    android::util::stats_write(android::util::HARDWARE_FAILED, int32_t(hardwareFailed.hardwareType),
-            hardwareFailed.hardwareLocation, int32_t(hardwareFailed.errorCode));
-
-    return hardware::Void();
-}
-
-hardware::Return<void> StatsService::reportPhysicalDropDetected(
-        const PhysicalDropDetected& physicalDropDetected) {
-    android::util::stats_write(android::util::PHYSICAL_DROP_DETECTED,
-            int32_t(physicalDropDetected.confidencePctg), physicalDropDetected.accelPeak,
-            physicalDropDetected.freefallDuration);
-
-    return hardware::Void();
-}
-
-hardware::Return<void> StatsService::reportChargeCycles(const ChargeCycles& chargeCycles) {
-    std::vector<int32_t> buckets = chargeCycles.cycleBucket;
-    int initialSize = buckets.size();
-    for (int i = 0; i < 10 - initialSize; i++) {
-        buckets.push_back(-1); // Push -1 for buckets that do not exist.
-    }
-    android::util::stats_write(android::util::CHARGE_CYCLES_REPORTED, buckets[0], buckets[1],
-            buckets[2], buckets[3], buckets[4], buckets[5], buckets[6], buckets[7], buckets[8],
-            buckets[9]);
-
-    return hardware::Void();
-}
-
-hardware::Return<void> StatsService::reportBatteryHealthSnapshot(
-        const BatteryHealthSnapshotArgs& batteryHealthSnapshotArgs) {
-    android::util::stats_write(android::util::BATTERY_HEALTH_SNAPSHOT,
-            int32_t(batteryHealthSnapshotArgs.type), batteryHealthSnapshotArgs.temperatureDeciC,
-            batteryHealthSnapshotArgs.voltageMicroV, batteryHealthSnapshotArgs.currentMicroA,
-            batteryHealthSnapshotArgs.openCircuitVoltageMicroV,
-            batteryHealthSnapshotArgs.resistanceMicroOhm, batteryHealthSnapshotArgs.levelPercent);
-
-    return hardware::Void();
-}
-
-hardware::Return<void> StatsService::reportSlowIo(const SlowIo& slowIo) {
-    android::util::stats_write(android::util::SLOW_IO, int32_t(slowIo.operation), slowIo.count);
-
-    return hardware::Void();
-}
-
-hardware::Return<void> StatsService::reportBatteryCausedShutdown(
-        const BatteryCausedShutdown& batteryCausedShutdown) {
-    android::util::stats_write(android::util::BATTERY_CAUSED_SHUTDOWN,
-            batteryCausedShutdown.voltageMicroV);
-
-    return hardware::Void();
-}
-
-hardware::Return<void> StatsService::reportUsbPortOverheatEvent(
-        const UsbPortOverheatEvent& usbPortOverheatEvent) {
-    android::util::stats_write(android::util::USB_PORT_OVERHEAT_EVENT_REPORTED,
-            usbPortOverheatEvent.plugTemperatureDeciC, usbPortOverheatEvent.maxTemperatureDeciC,
-            usbPortOverheatEvent.timeToOverheat, usbPortOverheatEvent.timeToHysteresis,
-            usbPortOverheatEvent.timeToInactive);
-
-    return hardware::Void();
-}
-
-hardware::Return<void> StatsService::reportSpeechDspStat(
-        const SpeechDspStat& speechDspStat) {
-    android::util::stats_write(android::util::SPEECH_DSP_STAT_REPORTED,
-            speechDspStat.totalUptimeMillis, speechDspStat.totalDowntimeMillis,
-            speechDspStat.totalCrashCount, speechDspStat.totalRecoverCount);
-
-    return hardware::Void();
-}
-
-hardware::Return<void> StatsService::reportVendorAtom(const VendorAtom& vendorAtom) {
-    std::string reverseDomainName = (std::string) vendorAtom.reverseDomainName;
-    if (vendorAtom.atomId < 100000 || vendorAtom.atomId >= 200000) {
-        ALOGE("Atom ID %ld is not a valid vendor atom ID", (long) vendorAtom.atomId);
-        return hardware::Void();
-    }
-    if (reverseDomainName.length() > 50) {
-        ALOGE("Vendor atom reverse domain name %s is too long.", reverseDomainName.c_str());
-        return hardware::Void();
-    }
-    LogEvent event(getWallClockSec() * NS_PER_SEC, getElapsedRealtimeNs(), vendorAtom);
-    mProcessor->OnLogEvent(&event);
-
-    return hardware::Void();
-}
-
 void StatsService::binderDied(const wp <IBinder>& who) {
     ALOGW("statscompanion service died");
     StatsdStats::getInstance().noteSystemServerRestart(getWallClockSec());
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index 3bfaa98..82a5a53 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -27,8 +27,6 @@
 #include "shell/ShellSubscriber.h"
 #include "statscompanion_util.h"
 
-#include <android/frameworks/stats/1.0/IStats.h>
-#include <android/frameworks/stats/1.0/types.h>
 #include <android/os/BnStatsd.h>
 #include <android/os/IPendingIntentRef.h>
 #include <android/os/IStatsCompanionService.h>
@@ -41,7 +39,6 @@
 
 using namespace android;
 using namespace android::binder;
-using namespace android::frameworks::stats::V1_0;
 using namespace android::os;
 using namespace std;
 
@@ -49,10 +46,7 @@
 namespace os {
 namespace statsd {
 
-using android::hardware::Return;
-
 class StatsService : public BnStatsd,
-                     public IStats,
                      public IBinder::DeathRecipient {
 public:
     StatsService(const sp<Looper>& handlerLooper, std::shared_ptr<LogEventQueue> queue);
@@ -193,16 +187,6 @@
     virtual Status unregisterNativePullAtomCallback(int32_t atomTag) override;
 
     /**
-     * Binder call to log BinaryPushStateChanged atom.
-     */
-    virtual Status sendBinaryPushStateChangedAtom(
-            const android::String16& trainNameIn,
-            const int64_t trainVersionCodeIn,
-            const int options,
-            const int32_t state,
-            const std::vector<int64_t>& experimentIdsIn) override;
-
-    /**
      * Binder call to log WatchdogRollbackOccurred atom.
      */
     virtual Status sendWatchdogRollbackOccurredAtom(
@@ -217,61 +201,6 @@
      */
     virtual Status getRegisteredExperimentIds(std::vector<int64_t>* expIdsOut);
 
-    /**
-     * Binder call to get SpeakerImpedance atom.
-     */
-    virtual Return<void> reportSpeakerImpedance(const SpeakerImpedance& speakerImpedance) override;
-
-    /**
-     * Binder call to get HardwareFailed atom.
-     */
-    virtual Return<void> reportHardwareFailed(const HardwareFailed& hardwareFailed) override;
-
-    /**
-     * Binder call to get PhysicalDropDetected atom.
-     */
-    virtual Return<void> reportPhysicalDropDetected(
-            const PhysicalDropDetected& physicalDropDetected) override;
-
-    /**
-     * Binder call to get ChargeCyclesReported atom.
-     */
-    virtual Return<void> reportChargeCycles(const ChargeCycles& chargeCycles) override;
-
-    /**
-     * Binder call to get BatteryHealthSnapshot atom.
-     */
-    virtual Return<void> reportBatteryHealthSnapshot(
-            const BatteryHealthSnapshotArgs& batteryHealthSnapshotArgs) override;
-
-    /**
-     * Binder call to get SlowIo atom.
-     */
-    virtual Return<void> reportSlowIo(const SlowIo& slowIo) override;
-
-    /**
-     * Binder call to get BatteryCausedShutdown atom.
-     */
-    virtual Return<void> reportBatteryCausedShutdown(
-            const BatteryCausedShutdown& batteryCausedShutdown) override;
-
-    /**
-     * Binder call to get UsbPortOverheatEvent atom.
-     */
-    virtual Return<void> reportUsbPortOverheatEvent(
-            const UsbPortOverheatEvent& usbPortOverheatEvent) override;
-
-    /**
-     * Binder call to get Speech DSP state atom.
-     */
-    virtual Return<void> reportSpeechDspStat(
-            const SpeechDspStat& speechDspStat) override;
-
-    /**
-     * Binder call to get vendor atom.
-     */
-    virtual Return<void> reportVendorAtom(const VendorAtom& vendorAtom) override;
-
     /** IBinder::DeathRecipient */
     virtual void binderDied(const wp<IBinder>& who) override;
 
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index fccefdc..71afc32 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -202,7 +202,8 @@
         DocsUIStartupMsReported docs_ui_startup_ms = 111 [(module) = "docsui"];
         DocsUIUserActionReported docs_ui_user_action_reported = 112 [(module) = "docsui"];
         WifiEnabledStateChanged wifi_enabled_state_changed = 113 [(module) = "framework"];
-        WifiRunningStateChanged wifi_running_state_changed = 114 [(module) = "framework"];
+        WifiRunningStateChanged wifi_running_state_changed = 114
+                [(module) = "framework", deprecated = true];
         AppCompacted app_compacted = 115 [(module) = "framework"];
         NetworkDnsEventReported network_dns_event_reported = 116 [(module) = "resolv"];
         DocsUIPickerLaunchedFromReported docs_ui_picker_launched_from_reported =
@@ -390,6 +391,7 @@
         WifiHealthStatReported wifi_health_stat_reported = 251 [(module) = "wifi"];
         WifiFailureStatReported wifi_failure_stat_reported = 252 [(module) = "wifi"];
         WifiConnectionResultReported wifi_connection_result_reported = 253 [(module) = "wifi"];
+        SdkExtensionStatus sdk_extension_status = 354;
     }
 
     // Pulled events will start at field 10000.
@@ -1258,6 +1260,8 @@
 }
 
 /**
+ * This atom is deprecated starting in R.
+ *
  * Logs when an app causes Wifi to run. In this context, 'to run' means to use Wifi Client Mode.
  * TODO: Include support for Hotspot, perhaps by using an extra field to denote 'mode'.
  * Note that Wifi Scanning is monitored separately in WifiScanStateChanged.
@@ -3706,6 +3710,7 @@
 
 /**
  * Potential experiment ids that goes with a train install.
+ * Should be kept in sync with experiment_ids.proto.
  */
 message TrainExperimentIds {
     repeated int64 experiment_id = 1;
@@ -8333,3 +8338,27 @@
     // Exception message (or log message) associated with the error (max 1000 chars)
     optional string exception_message = 2;
 }
+
+/**
+ * Logs when the SDK Extensions test app has polled the current version.
+ * This is atom ID 354.
+ *
+ * Logged from:
+ *   vendor/google_testing/integration/packages/apps/SdkExtensionsTestApp/
+ */
+message SdkExtensionStatus {
+    enum ApiCallStatus {
+        CALL_NOT_ATTEMPTED = 0;
+        CALL_SUCCESSFUL = 1;
+        CALL_FAILED = 2;
+    }
+
+    optional ApiCallStatus result = 1;
+
+    // The R extension version, i.e. android.os.ext.SdkExtension.getExtensionVersion(R).
+    optional int32 r_extension_version = 2;
+
+    // A number identifying which particular symbol's call failed, if any. 0 means no missing symbol.
+    // "Failed" here can mean a symbol that wasn't meant to be visible was, or the other way around.
+    optional int32 failed_call_symbol = 3;
+}
diff --git a/cmds/statsd/src/experiment_ids.proto b/cmds/statsd/src/experiment_ids.proto
new file mode 100644
index 0000000..c203631
--- /dev/null
+++ b/cmds/statsd/src/experiment_ids.proto
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+package android.os.statsd;
+
+option java_package = "com.android.internal.os";
+option java_outer_classname = "ExperimentIdsProto";
+
+// StatsLogProcessor uses the proto to parse experiment ids from
+// BinaryPushStateChanged atoms. This needs to be in sync with
+// TrainExperimentIds in atoms.proto.
+message ExperimentIds {
+    repeated int64 experiment_id = 1;
+}
diff --git a/cmds/statsd/src/external/GpuStatsPuller.cpp b/cmds/statsd/src/external/GpuStatsPuller.cpp
deleted file mode 100644
index 3229ba8..0000000
--- a/cmds/statsd/src/external/GpuStatsPuller.cpp
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * Copyright 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.
- */
-
-#include "GpuStatsPuller.h"
-
-#include <binder/IServiceManager.h>
-#include <graphicsenv/GpuStatsInfo.h>
-#include <graphicsenv/IGpuService.h>
-
-#include "logd/LogEvent.h"
-
-#include "stats_log_util.h"
-#include "statslog.h"
-
-namespace android {
-namespace os {
-namespace statsd {
-
-using android::util::ProtoReader;
-
-GpuStatsPuller::GpuStatsPuller(const int tagId) : StatsPuller(tagId) {
-}
-
-static sp<IGpuService> getGpuService() {
-    const sp<IBinder> binder = defaultServiceManager()->checkService(String16("gpu"));
-    if (!binder) {
-        ALOGE("Failed to get gpu service");
-        return nullptr;
-    }
-
-    return interface_cast<IGpuService>(binder);
-}
-
-static bool pullGpuStatsGlobalInfo(const sp<IGpuService>& gpuService,
-                                   std::vector<std::shared_ptr<LogEvent>>* data) {
-    std::vector<GpuStatsGlobalInfo> stats;
-    status_t status = gpuService->getGpuStatsGlobalInfo(&stats);
-    if (status != OK) {
-        return false;
-    }
-
-    data->clear();
-    data->reserve(stats.size());
-    for (const auto& info : stats) {
-        std::shared_ptr<LogEvent> event = make_shared<LogEvent>(
-                android::util::GPU_STATS_GLOBAL_INFO, getWallClockNs(), getElapsedRealtimeNs());
-        if (!event->write(info.driverPackageName)) return false;
-        if (!event->write(info.driverVersionName)) return false;
-        if (!event->write((int64_t)info.driverVersionCode)) return false;
-        if (!event->write(info.driverBuildTime)) return false;
-        if (!event->write((int64_t)info.glLoadingCount)) return false;
-        if (!event->write((int64_t)info.glLoadingFailureCount)) return false;
-        if (!event->write((int64_t)info.vkLoadingCount)) return false;
-        if (!event->write((int64_t)info.vkLoadingFailureCount)) return false;
-        if (!event->write(info.vulkanVersion)) return false;
-        if (!event->write(info.cpuVulkanVersion)) return false;
-        if (!event->write(info.glesVersion)) return false;
-        if (!event->write((int64_t)info.angleLoadingCount)) return false;
-        if (!event->write((int64_t)info.angleLoadingFailureCount)) return false;
-        event->init();
-        data->emplace_back(event);
-    }
-
-    return true;
-}
-
-static bool pullGpuStatsAppInfo(const sp<IGpuService>& gpuService,
-                                std::vector<std::shared_ptr<LogEvent>>* data) {
-    std::vector<GpuStatsAppInfo> stats;
-    status_t status = gpuService->getGpuStatsAppInfo(&stats);
-    if (status != OK) {
-        return false;
-    }
-
-    data->clear();
-    data->reserve(stats.size());
-    for (const auto& info : stats) {
-        std::shared_ptr<LogEvent> event = make_shared<LogEvent>(
-                android::util::GPU_STATS_APP_INFO, getWallClockNs(), getElapsedRealtimeNs());
-        if (!event->write(info.appPackageName)) return false;
-        if (!event->write((int64_t)info.driverVersionCode)) return false;
-        if (!event->writeBytes(int64VectorToProtoByteString(info.glDriverLoadingTime)))  {
-            return false;
-        }
-        if (!event->writeBytes(int64VectorToProtoByteString(info.vkDriverLoadingTime))) {
-            return false;
-        }
-        if (!event->writeBytes(int64VectorToProtoByteString(info.angleDriverLoadingTime))) {
-            return false;
-        }
-        if (!event->write(info.cpuVulkanInUse)) return false;
-        if (!event->write(info.falsePrerotation)) return false;
-        if (!event->write(info.gles1InUse)) return false;
-        event->init();
-        data->emplace_back(event);
-    }
-
-    return true;
-}
-
-bool GpuStatsPuller::PullInternal(std::vector<std::shared_ptr<LogEvent>>* data) {
-    const sp<IGpuService> gpuService = getGpuService();
-    if (!gpuService) {
-        return false;
-    }
-
-    switch (mTagId) {
-        case android::util::GPU_STATS_GLOBAL_INFO:
-            return pullGpuStatsGlobalInfo(gpuService, data);
-        case android::util::GPU_STATS_APP_INFO:
-            return pullGpuStatsAppInfo(gpuService, data);
-        default:
-            break;
-    }
-
-    return false;
-}
-
-static std::string protoOutputStreamToByteString(ProtoOutputStream& proto) {
-    if (!proto.size()) return "";
-
-    std::string byteString;
-    sp<ProtoReader> reader = proto.data();
-    while (reader->readBuffer() != nullptr) {
-        const size_t toRead = reader->currentToRead();
-        byteString.append((char*)reader->readBuffer(), toRead);
-        reader->move(toRead);
-    }
-
-    if (byteString.size() != proto.size()) return "";
-
-    return byteString;
-}
-
-std::string int64VectorToProtoByteString(const std::vector<int64_t>& value) {
-    if (value.empty()) return "";
-
-    ProtoOutputStream proto;
-    for (const auto& ele : value) {
-        proto.write(android::util::FIELD_TYPE_INT64 | android::util::FIELD_COUNT_REPEATED |
-                            1 /* field id */,
-                    (long long)ele);
-    }
-
-    return protoOutputStreamToByteString(proto);
-}
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
diff --git a/cmds/statsd/src/external/GpuStatsPuller.h b/cmds/statsd/src/external/GpuStatsPuller.h
deleted file mode 100644
index 2da199c..0000000
--- a/cmds/statsd/src/external/GpuStatsPuller.h
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 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.
- */
-
-#pragma once
-
-#include "StatsPuller.h"
-
-namespace android {
-namespace os {
-namespace statsd {
-
-/**
- * Pull GpuStats from GpuService.
- */
-class GpuStatsPuller : public StatsPuller {
-public:
-    explicit GpuStatsPuller(const int tagId);
-    bool PullInternal(std::vector<std::shared_ptr<LogEvent>>* data) override;
-};
-
-// convert a int64_t vector into a byte string for proto message like:
-// message RepeatedInt64Wrapper {
-//   repeated int64 value = 1;
-// }
-std::string int64VectorToProtoByteString(const std::vector<int64_t>& value);
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 15d7e33..3ceff75 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -32,7 +32,6 @@
 #include "../logd/LogEvent.h"
 #include "../stats_log_util.h"
 #include "../statscompanion_util.h"
-#include "GpuStatsPuller.h"
 #include "StatsCallbackPuller.h"
 #include "TrainInfoPuller.h"
 #include "statslog.h"
@@ -51,15 +50,6 @@
     : kAllPullAtomInfo({
               // TrainInfo.
               {{.atomTag = android::util::TRAIN_INFO}, new TrainInfoPuller()},
-
-              // GpuStatsGlobalInfo
-              {{.atomTag = android::util::GPU_STATS_GLOBAL_INFO},
-               new GpuStatsPuller(android::util::GPU_STATS_GLOBAL_INFO)},
-
-              // GpuStatsAppInfo
-              {{.atomTag = android::util::GPU_STATS_APP_INFO},
-               new GpuStatsPuller(android::util::GPU_STATS_APP_INFO)},
-
       }),
       mNextPullTimeNs(NO_ALARM_UPDATE) {
 }
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index 9a0693a..103eb0c 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -35,6 +35,34 @@
 using std::string;
 using std::vector;
 
+// stats_event.h socket types. Keep in sync.
+/* ERRORS */
+#define ERROR_NO_TIMESTAMP 0x1
+#define ERROR_NO_ATOM_ID 0x2
+#define ERROR_OVERFLOW 0x4
+#define ERROR_ATTRIBUTION_CHAIN_TOO_LONG 0x8
+#define ERROR_TOO_MANY_KEY_VALUE_PAIRS 0x10
+#define ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD 0x20
+#define ERROR_INVALID_ANNOTATION_ID 0x40
+#define ERROR_ANNOTATION_ID_TOO_LARGE 0x80
+#define ERROR_TOO_MANY_ANNOTATIONS 0x100
+#define ERROR_TOO_MANY_FIELDS 0x200
+#define ERROR_INVALID_VALUE_TYPE 0x400
+#define ERROR_STRING_NOT_NULL_TERMINATED 0x800
+
+/* TYPE IDS */
+#define INT32_TYPE 0x00
+#define INT64_TYPE 0x01
+#define STRING_TYPE 0x02
+#define LIST_TYPE 0x03
+#define FLOAT_TYPE 0x04
+#define BOOL_TYPE 0x05
+#define BYTE_ARRAY_TYPE 0x06
+#define OBJECT_TYPE 0x07
+#define KEY_VALUE_PAIRS_TYPE 0x08
+#define ATTRIBUTION_CHAIN_TYPE 0x09
+#define ERROR_TYPE 0x0F
+
 // Msg is expected to begin at the start of the serialized atom -- it should not
 // include the android_log_header_t or the StatsEventTag.
 LogEvent::LogEvent(uint8_t* msg, uint32_t len, int32_t uid, int32_t pid)
@@ -167,37 +195,6 @@
 }
 
 LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
-                   const VendorAtom& vendorAtom) {
-    mLogdTimestampNs = wallClockTimestampNs;
-    mElapsedTimestampNs = elapsedTimestampNs;
-    mTagId = vendorAtom.atomId;
-    mLogUid = AID_STATSD;
-
-    mValues.push_back(
-            FieldValue(Field(mTagId, getSimpleField(1)), Value(vendorAtom.reverseDomainName)));
-    for (int i = 0; i < (int)vendorAtom.values.size(); i++) {
-        switch (vendorAtom.values[i].getDiscriminator()) {
-            case VendorAtom::Value::hidl_discriminator::intValue:
-                mValues.push_back(FieldValue(Field(mTagId, getSimpleField(i + 2)),
-                                             Value(vendorAtom.values[i].intValue())));
-                break;
-            case VendorAtom::Value::hidl_discriminator::longValue:
-                mValues.push_back(FieldValue(Field(mTagId, getSimpleField(i + 2)),
-                                             Value(vendorAtom.values[i].longValue())));
-                break;
-            case VendorAtom::Value::hidl_discriminator::floatValue:
-                mValues.push_back(FieldValue(Field(mTagId, getSimpleField(i + 2)),
-                                             Value(vendorAtom.values[i].floatValue())));
-                break;
-            case VendorAtom::Value::hidl_discriminator::stringValue:
-                mValues.push_back(FieldValue(Field(mTagId, getSimpleField(i + 2)),
-                                             Value(vendorAtom.values[i].stringValue())));
-                break;
-        }
-    }
-}
-
-LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
                    const InstallTrainInfo& trainInfo) {
     mLogdTimestampNs = wallClockTimestampNs;
     mElapsedTimestampNs = elapsedTimestampNs;
@@ -809,6 +806,26 @@
     return 0.0;
 }
 
+std::vector<uint8_t> LogEvent::GetStorage(size_t key, status_t* err) const {
+    int field = getSimpleField(key);
+    for (const auto& value : mValues) {
+      if (value.mField.getField() == field) {
+        if (value.mValue.getType() == STORAGE) {
+          return value.mValue.storage_value;
+        } else {
+          *err = BAD_TYPE;
+          return vector<uint8_t>();
+        }
+      }
+      if ((size_t)value.mField.getPosAtDepth(0) > key) {
+        break;
+      }
+    }
+
+    *err = BAD_INDEX;
+    return vector<uint8_t>();
+}
+
 string LogEvent::ToString() const {
     string result;
     result += StringPrintf("{ uid(%d) %lld %lld (%d)", mLogUid, (long long)mLogdTimestampNs,
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
index 3db2676..5509c09 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -18,7 +18,6 @@
 
 #include "FieldValue.h"
 
-#include <android/frameworks/stats/1.0/types.h>
 #include <android/util/ProtoOutputStream.h>
 #include <private/android_logger.h>
 #include <stats_event_list.h>
@@ -27,8 +26,6 @@
 #include <string>
 #include <vector>
 
-using namespace android::frameworks::stats::V1_0;
-
 namespace android {
 namespace os {
 namespace statsd {
@@ -103,9 +100,6 @@
                       const std::vector<uint8_t>& experimentIds, int32_t userId);
 
     explicit LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
-                      const VendorAtom& vendorAtom);
-
-    explicit LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
                       const InstallTrainInfo& installTrainInfo);
 
     ~LogEvent();
@@ -144,6 +138,7 @@
     const char* GetString(size_t key, status_t* err) const;
     bool GetBool(size_t key, status_t* err) const;
     float GetFloat(size_t key, status_t* err) const;
+    std::vector<uint8_t> GetStorage(size_t key, status_t* err) const;
 
     /**
      * Write test data to the LogEvent. This can only be used when the LogEvent is constructed
@@ -214,6 +209,22 @@
         return LogEvent(*this);
     }
 
+    template <class T>
+    status_t updateValue(size_t key, T& value, Type type) {
+        int field = getSimpleField(key);
+        for (auto& fieldValue : mValues) {
+            if (fieldValue.mField.getField() == field) {
+                if (fieldValue.mValue.getType() == type) {
+                    fieldValue.mValue = Value(value);
+                   return OK;
+               } else {
+                   return BAD_TYPE;
+                }
+            }
+        }
+        return BAD_INDEX;
+    }
+
 private:
     /**
      * Only use this if copy is absolutely needed.
diff --git a/cmds/statsd/src/main.cpp b/cmds/statsd/src/main.cpp
index 58bfeb3..140ef4e 100644
--- a/cmds/statsd/src/main.cpp
+++ b/cmds/statsd/src/main.cpp
@@ -23,7 +23,6 @@
 #include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
 #include <binder/ProcessState.h>
-#include <hidl/HidlTransportSupport.h>
 #include <utils/Looper.h>
 
 #include <stdio.h>
@@ -75,8 +74,6 @@
     ps->giveThreadPoolName();
     IPCThreadState::self()->disableBackgroundScheduling(true);
 
-    ::android::hardware::configureRpcThreadpool(4 /*threads*/, false /*willJoin*/);
-
     std::shared_ptr<LogEventQueue> eventQueue =
             std::make_shared<LogEventQueue>(2000 /*buffer limit. Buffer is NOT pre-allocated*/);
 
@@ -89,12 +86,6 @@
         return -1;
     }
 
-    auto ret = gStatsService->registerAsService();
-    if (ret != ::android::OK) {
-        ALOGE("Failed to add service as HIDL service");
-        return 1; // or handle error
-    }
-
     registerSigHandler();
 
     gStatsService->sayHiToStatsCompanion();
diff --git a/cmds/statsd/src/stats_log_util.cpp b/cmds/statsd/src/stats_log_util.cpp
index 8e0c628..73f640e 100644
--- a/cmds/statsd/src/stats_log_util.cpp
+++ b/cmds/statsd/src/stats_log_util.cpp
@@ -21,6 +21,8 @@
 #include <set>
 #include <utils/SystemClock.h>
 
+#include "statscompanion_util.h"
+
 using android::util::AtomsInfo;
 using android::util::FIELD_COUNT_REPEATED;
 using android::util::FIELD_TYPE_BOOL;
@@ -584,6 +586,21 @@
     return millis * 1000000;
 }
 
+bool checkPermissionForIds(const char* permission, pid_t pid, uid_t uid) {
+    sp<IStatsCompanionService> scs = getStatsCompanionService();
+    if (scs == nullptr) {
+        return false;
+    }
+
+    bool success;
+    binder::Status status = scs->checkPermission(String16(permission), pid, uid, &success);
+    if (!status.isOk()) {
+        return false;
+    }
+
+    return success;
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/stats_log_util.h b/cmds/statsd/src/stats_log_util.h
index 5fdf6e2..aec0956 100644
--- a/cmds/statsd/src/stats_log_util.h
+++ b/cmds/statsd/src/stats_log_util.h
@@ -95,6 +95,9 @@
 // Returns the truncated timestamp to the nearest 5 minutes if needed.
 int64_t truncateTimestampIfNecessary(int atomId, int64_t timestampNs);
 
+// Checks permission for given pid and uid.
+bool checkPermissionForIds(const char* permission, pid_t pid, uid_t uid);
+
 inline bool isVendorPulledAtom(int atomId) {
     return atomId >= StatsdStats::kVendorPulledAtomStartTag && atomId < StatsdStats::kMaxAtomTag;
 }
diff --git a/cmds/statsd/src/subscriber/IncidentdReporter.cpp b/cmds/statsd/src/subscriber/IncidentdReporter.cpp
index d86e291..30c90b1 100644
--- a/cmds/statsd/src/subscriber/IncidentdReporter.cpp
+++ b/cmds/statsd/src/subscriber/IncidentdReporter.cpp
@@ -21,10 +21,8 @@
 #include "packages/UidMap.h"
 #include "stats_log_util.h"
 
-#include <android/os/IIncidentManager.h>
-#include <android/os/IncidentReportArgs.h>
 #include <android/util/ProtoOutputStream.h>
-#include <binder/IServiceManager.h>
+#include <incident/incident_report.h>
 
 #include <vector>
 
@@ -132,7 +130,7 @@
         return false;
     }
 
-    IncidentReportArgs incidentReport;
+    android::os::IncidentReportRequest incidentReport;
 
     vector<uint8_t> protoData;
     getProtoData(rule_id, metricId, dimensionKey, metricValue, configKey,
@@ -146,30 +144,21 @@
     uint8_t dest;
     switch (config.dest()) {
         case IncidentdDetails_Destination_AUTOMATIC:
-            dest = android::os::PRIVACY_POLICY_AUTOMATIC;
+            dest = INCIDENT_REPORT_PRIVACY_POLICY_AUTOMATIC;
             break;
         case IncidentdDetails_Destination_EXPLICIT:
-            dest = android::os::PRIVACY_POLICY_EXPLICIT;
+            dest = INCIDENT_REPORT_PRIVACY_POLICY_EXPLICIT;
             break;
         default:
-            dest = android::os::PRIVACY_POLICY_AUTOMATIC;
+            dest = INCIDENT_REPORT_PRIVACY_POLICY_AUTOMATIC;
     }
     incidentReport.setPrivacyPolicy(dest);
 
-    incidentReport.setReceiverPkg(config.receiver_pkg());
+    incidentReport.setReceiverPackage(config.receiver_pkg());
 
-    incidentReport.setReceiverCls(config.receiver_cls());
+    incidentReport.setReceiverClass(config.receiver_cls());
 
-    sp<IIncidentManager> service = interface_cast<IIncidentManager>(
-            defaultServiceManager()->getService(android::String16("incident")));
-    if (service == nullptr) {
-        ALOGW("Failed to fetch incident service.");
-        return false;
-    }
-    VLOG("Calling incidentd %p", service.get());
-    binder::Status s = service->reportIncident(incidentReport);
-    VLOG("Report incident status: %s", s.toString8().string());
-    return s.isOk();
+    return incidentReport.takeReport() == NO_ERROR;
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/tests/LogEvent_test.cpp b/cmds/statsd/tests/LogEvent_test.cpp
index 35b0396..f624e12 100644
--- a/cmds/statsd/tests/LogEvent_test.cpp
+++ b/cmds/statsd/tests/LogEvent_test.cpp
@@ -46,16 +46,16 @@
 }
 
 TEST(LogEventTest, TestPrimitiveParsing) {
-    struct stats_event* event = stats_event_obtain();
-    stats_event_set_atom_id(event, 100);
-    stats_event_write_int32(event, 10);
-    stats_event_write_int64(event, 0x123456789);
-    stats_event_write_float(event, 2.0);
-    stats_event_write_bool(event, true);
-    stats_event_build(event);
+    AStatsEvent* event = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(event, 100);
+    AStatsEvent_writeInt32(event, 10);
+    AStatsEvent_writeInt64(event, 0x123456789);
+    AStatsEvent_writeFloat(event, 2.0);
+    AStatsEvent_writeBool(event, true);
+    AStatsEvent_build(event);
 
     size_t size;
-    uint8_t* buf = stats_event_get_buffer(event, &size);
+    uint8_t* buf = AStatsEvent_getBuffer(event, &size);
 
     LogEvent logEvent(buf, size, /*uid=*/ 1000, /*pid=*/ 1001);
     EXPECT_TRUE(logEvent.isValid());
@@ -90,20 +90,20 @@
     EXPECT_EQ(Type::INT, boolItem.mValue.getType()); // FieldValue does not support boolean type
     EXPECT_EQ(1, boolItem.mValue.int_value);
 
-    stats_event_release(event);
+    AStatsEvent_release(event);
 }
 
 
 TEST(LogEventTest, TestStringAndByteArrayParsing) {
-    struct stats_event* event = stats_event_obtain();
-    stats_event_set_atom_id(event, 100);
+    AStatsEvent* event = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(event, 100);
     string str = "test";
-    stats_event_write_string8(event, str.c_str());
-    stats_event_write_byte_array(event, (uint8_t*)str.c_str(), str.length());
-    stats_event_build(event);
+    AStatsEvent_writeString(event, str.c_str());
+    AStatsEvent_writeByteArray(event, (uint8_t*)str.c_str(), str.length());
+    AStatsEvent_build(event);
 
     size_t size;
-    uint8_t* buf = stats_event_get_buffer(event, &size);
+    uint8_t* buf = AStatsEvent_getBuffer(event, &size);
 
     LogEvent logEvent(buf, size, /*uid=*/ 1000, /*pid=*/ 1001);
     EXPECT_TRUE(logEvent.isValid());
@@ -127,18 +127,18 @@
     vector<uint8_t> expectedValue = {'t', 'e', 's', 't'};
     EXPECT_EQ(expectedValue, storageItem.mValue.storage_value);
 
-    stats_event_release(event);
+    AStatsEvent_release(event);
 }
 
 TEST(LogEventTest, TestEmptyString) {
-    struct stats_event* event = stats_event_obtain();
-    stats_event_set_atom_id(event, 100);
+    AStatsEvent* event = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(event, 100);
     string empty = "";
-    stats_event_write_string8(event, empty.c_str());
-    stats_event_build(event);
+    AStatsEvent_writeString(event, empty.c_str());
+    AStatsEvent_build(event);
 
     size_t size;
-    uint8_t* buf = stats_event_get_buffer(event, &size);
+    uint8_t* buf = AStatsEvent_getBuffer(event, &size);
 
     LogEvent logEvent(buf, size, /*uid=*/ 1000, /*pid=*/ 1001);
     EXPECT_TRUE(logEvent.isValid());
@@ -155,18 +155,18 @@
     EXPECT_EQ(Type::STRING, item.mValue.getType());
     EXPECT_EQ(empty, item.mValue.str_value);
 
-    stats_event_release(event);
+    AStatsEvent_release(event);
 }
 
 TEST(LogEventTest, TestByteArrayWithNullCharacter) {
-    struct stats_event* event = stats_event_obtain();
-    stats_event_set_atom_id(event, 100);
+    AStatsEvent* event = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(event, 100);
     uint8_t message[] = {'\t', 'e', '\0', 's', 't'};
-    stats_event_write_byte_array(event, message, 5);
-    stats_event_build(event);
+    AStatsEvent_writeByteArray(event, message, 5);
+    AStatsEvent_build(event);
 
     size_t size;
-    uint8_t* buf = stats_event_get_buffer(event, &size);
+    uint8_t* buf = AStatsEvent_getBuffer(event, &size);
 
     LogEvent logEvent(buf, size, /*uid=*/ 1000, /*pid=*/ 1001);
     EXPECT_TRUE(logEvent.isValid());
@@ -184,79 +184,12 @@
     vector<uint8_t> expectedValue(message, message + 5);
     EXPECT_EQ(expectedValue, item.mValue.storage_value);
 
-    stats_event_release(event);
-}
-
-TEST(LogEventTest, TestKeyValuePairs) {
-    struct stats_event* event = stats_event_obtain();
-    stats_event_set_atom_id(event, 100);
-
-    struct key_value_pair pairs[4];
-    pairs[0] = {.key = 0, .valueType = INT32_TYPE, .int32Value = 1};
-    pairs[1] = {.key = 1, .valueType = INT64_TYPE, .int64Value = 0x123456789};
-    pairs[2] = {.key = 2, .valueType = FLOAT_TYPE, .floatValue = 2.0};
-    string str = "test";
-    pairs[3] = {.key = 3, .valueType = STRING_TYPE, .stringValue = str.c_str()};
-
-    stats_event_write_key_value_pairs(event, pairs, 4);
-    stats_event_build(event);
-
-    size_t size;
-    uint8_t* buf = stats_event_get_buffer(event, &size);
-
-    LogEvent logEvent(buf, size, /*uid=*/ 1000, /*pid=*/ 1001);
-    EXPECT_TRUE(logEvent.isValid());
-    EXPECT_EQ(100, logEvent.GetTagId());
-    EXPECT_EQ(1000, logEvent.GetUid());
-    EXPECT_EQ(1001, logEvent.GetPid());
-
-    const vector<FieldValue>& values = logEvent.getValues();
-    EXPECT_EQ(8, values.size()); // 2 FieldValues per key-value pair
-
-    // Check the keys first
-    for (int i = 0; i < values.size() / 2; i++) {
-        const FieldValue& item = values[2 * i];
-        int32_t depth1Pos = i + 1;
-        bool depth1Last = i == (values.size() / 2 - 1);
-        Field expectedField = getField(100, {1, depth1Pos, 1}, 2, {true, depth1Last, false});
-
-        EXPECT_EQ(expectedField, item.mField);
-        EXPECT_EQ(Type::INT, item.mValue.getType());
-        EXPECT_EQ(i, item.mValue.int_value);
-    }
-
-    // Check the values now
-    // Note: pos[2] = index of type in KeyValuePair in atoms.proto
-    const FieldValue& int32Item = values[1];
-    Field expectedField = getField(100, {1, 1, 2}, 2, {true, false, true});
-    EXPECT_EQ(expectedField, int32Item.mField);
-    EXPECT_EQ(Type::INT, int32Item.mValue.getType());
-    EXPECT_EQ(1, int32Item.mValue.int_value);
-
-    const FieldValue& int64Item = values[3];
-    expectedField = getField(100, {1, 2, 3}, 2, {true, false, true});
-    EXPECT_EQ(expectedField, int64Item.mField);
-    EXPECT_EQ(Type::LONG, int64Item.mValue.getType());
-    EXPECT_EQ(0x123456789, int64Item.mValue.long_value);
-
-    const FieldValue& floatItem = values[5];
-    expectedField = getField(100, {1, 3, 5}, 2, {true, false, true});
-    EXPECT_EQ(expectedField, floatItem.mField);
-    EXPECT_EQ(Type::FLOAT, floatItem.mValue.getType());
-    EXPECT_EQ(2.0, floatItem.mValue.float_value);
-
-    const FieldValue& stringItem = values[7];
-    expectedField = getField(100, {1, 4, 4}, 2, {true, true, true});
-    EXPECT_EQ(expectedField, stringItem.mField);
-    EXPECT_EQ(Type::STRING, stringItem.mValue.getType());
-    EXPECT_EQ(str, stringItem.mValue.str_value);
-
-    stats_event_release(event);
+    AStatsEvent_release(event);
 }
 
 TEST(LogEventTest, TestAttributionChain) {
-    struct stats_event* event = stats_event_obtain();
-    stats_event_set_atom_id(event, 100);
+    AStatsEvent* event = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(event, 100);
 
     string tag1 = "tag1";
     string tag2 = "tag2";
@@ -264,11 +197,11 @@
     uint32_t uids[] = {1001, 1002};
     const char* tags[] = {tag1.c_str(), tag2.c_str()};
 
-    stats_event_write_attribution_chain(event, uids, tags, 2);
-    stats_event_build(event);
+    AStatsEvent_writeAttributionChain(event, uids, tags, 2);
+    AStatsEvent_build(event);
 
     size_t size;
-    uint8_t* buf = stats_event_get_buffer(event, &size);
+    uint8_t* buf = AStatsEvent_getBuffer(event, &size);
 
     LogEvent logEvent(buf, size, /*uid=*/ 1000, /*pid=*/ 1001);
     EXPECT_TRUE(logEvent.isValid());
@@ -305,7 +238,7 @@
     EXPECT_EQ(Type::STRING, tag2Item.mValue.getType());
     EXPECT_EQ(tag2, tag2Item.mValue.str_value);
 
-    stats_event_release(event);
+    AStatsEvent_release(event);
 }
 
 #else // NEW_ENCODING_SCHEME
diff --git a/cmds/statsd/tests/external/GpuStatsPuller_test.cpp b/cmds/statsd/tests/external/GpuStatsPuller_test.cpp
deleted file mode 100644
index ae92705..0000000
--- a/cmds/statsd/tests/external/GpuStatsPuller_test.cpp
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * Copyright 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.
- */
-
-#undef LOG_TAG
-#define LOG_TAG "GpuStatsPuller_test"
-
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-
-#include <graphicsenv/GpuStatsInfo.h>
-#include <log/log.h>
-
-#include "src/external/GpuStatsPuller.h"
-#include "statslog.h"
-
-#ifdef __ANDROID__
-
-namespace android {
-namespace os {
-namespace statsd {
-
-// clang-format off
-static const std::string DRIVER_PACKAGE_NAME      = "TEST_DRIVER";
-static const std::string DRIVER_VERSION_NAME      = "TEST_DRIVER_VERSION";
-static const std::string APP_PACKAGE_NAME         = "TEST_APP";
-static const int64_t TIMESTAMP_WALLCLOCK          = 111;
-static const int64_t TIMESTAMP_ELAPSED            = 222;
-static const int64_t DRIVER_VERSION_CODE          = 333;
-static const int64_t DRIVER_BUILD_TIME            = 444;
-static const int64_t GL_LOADING_COUNT             = 3;
-static const int64_t GL_LOADING_FAILURE_COUNT     = 1;
-static const int64_t VK_LOADING_COUNT             = 4;
-static const int64_t VK_LOADING_FAILURE_COUNT     = 0;
-static const int64_t ANGLE_LOADING_COUNT          = 2;
-static const int64_t ANGLE_LOADING_FAILURE_COUNT  = 1;
-static const int64_t GL_DRIVER_LOADING_TIME_0     = 555;
-static const int64_t GL_DRIVER_LOADING_TIME_1     = 666;
-static const int64_t VK_DRIVER_LOADING_TIME_0     = 777;
-static const int64_t VK_DRIVER_LOADING_TIME_1     = 888;
-static const int64_t VK_DRIVER_LOADING_TIME_2     = 999;
-static const int64_t ANGLE_DRIVER_LOADING_TIME_0  = 1010;
-static const int64_t ANGLE_DRIVER_LOADING_TIME_1  = 1111;
-static const int32_t VULKAN_VERSION               = 1;
-static const int32_t CPU_VULKAN_VERSION           = 2;
-static const int32_t GLES_VERSION                 = 3;
-static const bool CPU_VULKAN_IN_USE               = true;
-static const bool FALSE_PREROTATION               = true;
-static const bool GLES_1_IN_USE                   = true;
-static const size_t NUMBER_OF_VALUES_GLOBAL       = 13;
-static const size_t NUMBER_OF_VALUES_APP          = 8;
-// clang-format on
-
-class MockGpuStatsPuller : public GpuStatsPuller {
-public:
-    MockGpuStatsPuller(const int tagId, vector<std::shared_ptr<LogEvent>>* data)
-        : GpuStatsPuller(tagId), mData(data){};
-
-private:
-    bool PullInternal(vector<std::shared_ptr<LogEvent>>* data) override {
-        *data = *mData;
-        return true;
-    }
-
-    vector<std::shared_ptr<LogEvent>>* mData;
-};
-
-class GpuStatsPuller_test : public ::testing::Test {
-public:
-    GpuStatsPuller_test() {
-        const ::testing::TestInfo* const test_info =
-                ::testing::UnitTest::GetInstance()->current_test_info();
-        ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
-    }
-
-    ~GpuStatsPuller_test() {
-        const ::testing::TestInfo* const test_info =
-                ::testing::UnitTest::GetInstance()->current_test_info();
-        ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
-    }
-};
-
-TEST_F(GpuStatsPuller_test, PullGpuStatsGlobalInfo) {
-    vector<std::shared_ptr<LogEvent>> inData, outData;
-    std::shared_ptr<LogEvent> event = make_shared<LogEvent>(android::util::GPU_STATS_GLOBAL_INFO,
-                                                            TIMESTAMP_WALLCLOCK, TIMESTAMP_ELAPSED);
-    EXPECT_TRUE(event->write(DRIVER_PACKAGE_NAME));
-    EXPECT_TRUE(event->write(DRIVER_VERSION_NAME));
-    EXPECT_TRUE(event->write(DRIVER_VERSION_CODE));
-    EXPECT_TRUE(event->write(DRIVER_BUILD_TIME));
-    EXPECT_TRUE(event->write(GL_LOADING_COUNT));
-    EXPECT_TRUE(event->write(GL_LOADING_FAILURE_COUNT));
-    EXPECT_TRUE(event->write(VK_LOADING_COUNT));
-    EXPECT_TRUE(event->write(VK_LOADING_FAILURE_COUNT));
-    EXPECT_TRUE(event->write(VULKAN_VERSION));
-    EXPECT_TRUE(event->write(CPU_VULKAN_VERSION));
-    EXPECT_TRUE(event->write(GLES_VERSION));
-    EXPECT_TRUE(event->write(ANGLE_LOADING_COUNT));
-    EXPECT_TRUE(event->write(ANGLE_LOADING_FAILURE_COUNT));
-    event->init();
-    inData.emplace_back(event);
-    MockGpuStatsPuller mockPuller(android::util::GPU_STATS_GLOBAL_INFO, &inData);
-    mockPuller.ForceClearCache();
-    mockPuller.Pull(&outData);
-
-    ASSERT_EQ(1, outData.size());
-    EXPECT_EQ(android::util::GPU_STATS_GLOBAL_INFO, outData[0]->GetTagId());
-    ASSERT_EQ(NUMBER_OF_VALUES_GLOBAL, outData[0]->size());
-    EXPECT_EQ(DRIVER_PACKAGE_NAME, outData[0]->getValues()[0].mValue.str_value);
-    EXPECT_EQ(DRIVER_VERSION_NAME, outData[0]->getValues()[1].mValue.str_value);
-    EXPECT_EQ(DRIVER_VERSION_CODE, outData[0]->getValues()[2].mValue.long_value);
-    EXPECT_EQ(DRIVER_BUILD_TIME, outData[0]->getValues()[3].mValue.long_value);
-    EXPECT_EQ(GL_LOADING_COUNT, outData[0]->getValues()[4].mValue.long_value);
-    EXPECT_EQ(GL_LOADING_FAILURE_COUNT, outData[0]->getValues()[5].mValue.long_value);
-    EXPECT_EQ(VK_LOADING_COUNT, outData[0]->getValues()[6].mValue.long_value);
-    EXPECT_EQ(VK_LOADING_FAILURE_COUNT, outData[0]->getValues()[7].mValue.long_value);
-    EXPECT_EQ(VULKAN_VERSION, outData[0]->getValues()[8].mValue.int_value);
-    EXPECT_EQ(CPU_VULKAN_VERSION, outData[0]->getValues()[9].mValue.int_value);
-    EXPECT_EQ(GLES_VERSION, outData[0]->getValues()[10].mValue.int_value);
-    EXPECT_EQ(ANGLE_LOADING_COUNT, outData[0]->getValues()[11].mValue.long_value);
-    EXPECT_EQ(ANGLE_LOADING_FAILURE_COUNT, outData[0]->getValues()[12].mValue.long_value);
-}
-
-TEST_F(GpuStatsPuller_test, PullGpuStatsAppInfo) {
-    vector<std::shared_ptr<LogEvent>> inData, outData;
-    std::shared_ptr<LogEvent> event = make_shared<LogEvent>(android::util::GPU_STATS_APP_INFO,
-                                                            TIMESTAMP_WALLCLOCK, TIMESTAMP_ELAPSED);
-    EXPECT_TRUE(event->write(APP_PACKAGE_NAME));
-    EXPECT_TRUE(event->write(DRIVER_VERSION_CODE));
-    std::vector<int64_t> glDriverLoadingTime;
-    glDriverLoadingTime.emplace_back(GL_DRIVER_LOADING_TIME_0);
-    glDriverLoadingTime.emplace_back(GL_DRIVER_LOADING_TIME_1);
-    std::vector<int64_t> vkDriverLoadingTime;
-    vkDriverLoadingTime.emplace_back(VK_DRIVER_LOADING_TIME_0);
-    vkDriverLoadingTime.emplace_back(VK_DRIVER_LOADING_TIME_1);
-    vkDriverLoadingTime.emplace_back(VK_DRIVER_LOADING_TIME_2);
-    std::vector<int64_t> angleDriverLoadingTime;
-    angleDriverLoadingTime.emplace_back(ANGLE_DRIVER_LOADING_TIME_0);
-    angleDriverLoadingTime.emplace_back(ANGLE_DRIVER_LOADING_TIME_1);
-    EXPECT_TRUE(event->write(int64VectorToProtoByteString(glDriverLoadingTime)));
-    EXPECT_TRUE(event->write(int64VectorToProtoByteString(vkDriverLoadingTime)));
-    EXPECT_TRUE(event->write(int64VectorToProtoByteString(angleDriverLoadingTime)));
-    EXPECT_TRUE(event->write(CPU_VULKAN_IN_USE));
-    EXPECT_TRUE(event->write(FALSE_PREROTATION));
-    EXPECT_TRUE(event->write(GLES_1_IN_USE));
-    event->init();
-    inData.emplace_back(event);
-    MockGpuStatsPuller mockPuller(android::util::GPU_STATS_APP_INFO, &inData);
-    mockPuller.ForceClearCache();
-    mockPuller.Pull(&outData);
-
-    ASSERT_EQ(1, outData.size());
-    EXPECT_EQ(android::util::GPU_STATS_APP_INFO, outData[0]->GetTagId());
-    ASSERT_EQ(NUMBER_OF_VALUES_APP, outData[0]->size());
-    EXPECT_EQ(APP_PACKAGE_NAME, outData[0]->getValues()[0].mValue.str_value);
-    EXPECT_EQ(DRIVER_VERSION_CODE, outData[0]->getValues()[1].mValue.long_value);
-    EXPECT_EQ(int64VectorToProtoByteString(glDriverLoadingTime),
-              outData[0]->getValues()[2].mValue.str_value);
-    EXPECT_EQ(int64VectorToProtoByteString(vkDriverLoadingTime),
-              outData[0]->getValues()[3].mValue.str_value);
-    EXPECT_EQ(int64VectorToProtoByteString(angleDriverLoadingTime),
-              outData[0]->getValues()[4].mValue.str_value);
-    EXPECT_EQ(CPU_VULKAN_IN_USE, outData[0]->getValues()[5].mValue.int_value);
-    EXPECT_EQ(FALSE_PREROTATION, outData[0]->getValues()[6].mValue.int_value);
-    EXPECT_EQ(GLES_1_IN_USE, outData[0]->getValues()[7].mValue.int_value);
-}
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
-#else
-GTEST_LOG_(INFO) << "This test does nothing.\n";
-#endif
diff --git a/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp b/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp
index 2576cf5..a011692e 100644
--- a/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp
+++ b/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp
@@ -50,11 +50,11 @@
 int64_t pullCoolDownNs;
 std::thread pullThread;
 
-stats_event* createSimpleEvent(int64_t value) {
-    stats_event* event = stats_event_obtain();
-    stats_event_set_atom_id(event, pullTagId);
-    stats_event_write_int64(event, value);
-    stats_event_build(event);
+AStatsEvent* createSimpleEvent(int64_t value) {
+    AStatsEvent* event = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(event, pullTagId);
+    AStatsEvent_writeInt64(event, value);
+    AStatsEvent_build(event);
     return event;
 }
 
@@ -62,16 +62,16 @@
     // Convert stats_events into StatsEventParcels.
     std::vector<android::util::StatsEventParcel> parcels;
     for (int i = 0; i < values.size(); i++) {
-        stats_event* event = createSimpleEvent(values[i]);
+        AStatsEvent* event = createSimpleEvent(values[i]);
         size_t size;
-        uint8_t* buffer = stats_event_get_buffer(event, &size);
+        uint8_t* buffer = AStatsEvent_getBuffer(event, &size);
 
         android::util::StatsEventParcel p;
         // vector.assign() creates a copy, but this is inevitable unless
         // stats_event.h/c uses a vector as opposed to a buffer.
         p.buffer.assign(buffer, buffer + size);
         parcels.push_back(std::move(p));
-        stats_event_release(event);
+        AStatsEvent_release(event);
     }
 
     sleep_for(std::chrono::nanoseconds(pullDelayNs));
diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp
index 6e1890a..db09ee9 100644
--- a/cmds/statsd/tests/statsd_test_util.cpp
+++ b/cmds/statsd/tests/statsd_test_util.cpp
@@ -953,24 +953,24 @@
     // Convert stats_events into StatsEventParcels.
     std::vector<android::util::StatsEventParcel> parcels;
     for (int i = 1; i < 3; i++) {
-        stats_event* event = stats_event_obtain();
-        stats_event_set_atom_id(event, atomTag);
+        AStatsEvent* event = AStatsEvent_obtain();
+        AStatsEvent_setAtomId(event, atomTag);
         std::string subsystemName = "subsystem_name_";
         subsystemName = subsystemName + std::to_string(i);
-        stats_event_write_string8(event, subsystemName.c_str());
-        stats_event_write_string8(event, "subsystem_subname foo");
-        stats_event_write_int64(event, /*count= */ i);
-        stats_event_write_int64(event, /*time_millis= */ i * 100);
-        stats_event_build(event);
+        AStatsEvent_writeString(event, subsystemName.c_str());
+        AStatsEvent_writeString(event, "subsystem_subname foo");
+        AStatsEvent_writeInt64(event, /*count= */ i);
+        AStatsEvent_writeInt64(event, /*time_millis= */ i * 100);
+        AStatsEvent_build(event);
         size_t size;
-        uint8_t* buffer = stats_event_get_buffer(event, &size);
+        uint8_t* buffer = AStatsEvent_getBuffer(event, &size);
 
         android::util::StatsEventParcel p;
         // vector.assign() creates a copy, but this is inevitable unless
         // stats_event.h/c uses a vector as opposed to a buffer.
         p.buffer.assign(buffer, buffer + size);
         parcels.push_back(std::move(p));
-        stats_event_release(event);
+        AStatsEvent_write(event);
     }
     resultReceiver->pullFinished(atomTag, /*success=*/true, parcels);
     return binder::Status::ok();
diff --git a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
index 9cf1de9..ace13513 100644
--- a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
+++ b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
@@ -31,6 +31,13 @@
 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_4_FINGER_DOUBLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SINGLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_RIGHT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_UP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_TRIPLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP_AND_HOLD;
 import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN;
@@ -105,7 +112,14 @@
             GESTURE_3_FINGER_SWIPE_DOWN,
             GESTURE_3_FINGER_SWIPE_LEFT,
             GESTURE_3_FINGER_SWIPE_RIGHT,
-            GESTURE_3_FINGER_SWIPE_UP
+            GESTURE_3_FINGER_SWIPE_UP,
+            GESTURE_4_FINGER_DOUBLE_TAP,
+            GESTURE_4_FINGER_SINGLE_TAP,
+            GESTURE_4_FINGER_SWIPE_DOWN,
+            GESTURE_4_FINGER_SWIPE_LEFT,
+            GESTURE_4_FINGER_SWIPE_RIGHT,
+            GESTURE_4_FINGER_SWIPE_UP,
+            GESTURE_4_FINGER_TRIPLE_TAP
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface GestureId {}
@@ -165,6 +179,9 @@
             case GESTURE_3_FINGER_SINGLE_TAP: return "GESTURE_3_FINGER_SINGLE_TAP";
             case GESTURE_3_FINGER_DOUBLE_TAP: return "GESTURE_3_FINGER_DOUBLE_TAP";
             case GESTURE_3_FINGER_TRIPLE_TAP: return "GESTURE_3_FINGER_TRIPLE_TAP";
+            case GESTURE_4_FINGER_SINGLE_TAP: return "GESTURE_4_FINGER_SINGLE_TAP";
+            case GESTURE_4_FINGER_DOUBLE_TAP: return "GESTURE_4_FINGER_DOUBLE_TAP";
+            case GESTURE_4_FINGER_TRIPLE_TAP: return "GESTURE_4_FINGER_TRIPLE_TAP";
             case GESTURE_DOUBLE_TAP: return "GESTURE_DOUBLE_TAP";
             case GESTURE_DOUBLE_TAP_AND_HOLD: return "GESTURE_DOUBLE_TAP_AND_HOLD";
             case GESTURE_SWIPE_DOWN: return "GESTURE_SWIPE_DOWN";
@@ -191,6 +208,10 @@
             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";
+            case GESTURE_4_FINGER_SWIPE_DOWN: return "GESTURE_4_FINGER_SWIPE_DOWN";
+            case GESTURE_4_FINGER_SWIPE_LEFT: return "GESTURE_4_FINGER_SWIPE_LEFT";
+            case GESTURE_4_FINGER_SWIPE_RIGHT: return "GESTURE_4_FINGER_SWIPE_RIGHT";
+            case GESTURE_4_FINGER_SWIPE_UP: return "GESTURE_4_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 2165fb3..b65f68e 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -28,7 +28,9 @@
 import android.content.Intent;
 import android.content.pm.ParceledListSlice;
 import android.graphics.Bitmap;
+import android.graphics.ColorSpace;
 import android.graphics.Region;
+import android.hardware.HardwareBuffer;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Handler;
@@ -388,6 +390,27 @@
      */
     public static final int GESTURE_3_FINGER_SWIPE_RIGHT = 32;
 
+    /** The user has performed a four-finger swipe up gesture on the touch screen. */
+    public static final int GESTURE_4_FINGER_SWIPE_UP = 33;
+
+    /** The user has performed a four-finger swipe down gesture on the touch screen. */
+    public static final int GESTURE_4_FINGER_SWIPE_DOWN = 34;
+
+    /** The user has performed a four-finger swipe left gesture on the touch screen. */
+    public static final int GESTURE_4_FINGER_SWIPE_LEFT = 35;
+
+    /** The user has performed a four-finger swipe right gesture on the touch screen. */
+    public static final int GESTURE_4_FINGER_SWIPE_RIGHT = 36;
+
+    /** The user has performed a four-finger single tap gesture on the touch screen. */
+    public static final int GESTURE_4_FINGER_SINGLE_TAP = 37;
+
+    /** The user has performed a four-finger double tap gesture on the touch screen. */
+    public static final int GESTURE_4_FINGER_DOUBLE_TAP = 38;
+
+    /** The user has performed a four-finger triple tap gesture on the touch screen. */
+    public static final int GESTURE_4_FINGER_TRIPLE_TAP = 39;
+
     /**
      * The {@link Intent} that must be declared as handled by the service.
      */
@@ -564,7 +587,12 @@
     private FingerprintGestureController mFingerprintGestureController;
 
     /** @hide */
-    public static final String KEY_ACCESSIBILITY_SCREENSHOT = "screenshot";
+    public static final String KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER =
+            "screenshot_hardwareBuffer";
+
+    /** @hide */
+    public static final String KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE_ID =
+            "screenshot_colorSpaceId";
 
     /**
      * Callback for {@link android.view.accessibility.AccessibilityEvent}s.
@@ -1867,8 +1895,9 @@
     }
 
     /**
-     * Takes a screenshot of the specified display and returns it by {@link Bitmap.Config#HARDWARE}
-     * format.
+     * Takes a screenshot of the specified display and returns it via an
+     * {@link AccessibilityService.ScreenshotResult}. You can use {@link Bitmap#wrapHardwareBuffer}
+     * to construct the bitmap from the ScreenshotResult's payload.
      * <p>
      * <strong>Note:</strong> In order to take screenshot your service has
      * to declare the capability to take screenshot by setting the
@@ -1886,7 +1915,7 @@
      * @return {@code true} if the taking screenshot accepted, {@code false} if not.
      */
     public boolean takeScreenshot(int displayId, @NonNull @CallbackExecutor Executor executor,
-            @NonNull Consumer<Bitmap> callback) {
+            @NonNull Consumer<ScreenshotResult> callback) {
         Preconditions.checkNotNull(executor, "executor cannot be null");
         Preconditions.checkNotNull(callback, "callback cannot be null");
         final IAccessibilityServiceConnection connection =
@@ -1896,14 +1925,22 @@
             return false;
         }
         try {
-            connection.takeScreenshotWithCallback(displayId, new RemoteCallback((result) -> {
-                final Bitmap screenshot = result.getParcelable(KEY_ACCESSIBILITY_SCREENSHOT);
-                final long identity = Binder.clearCallingIdentity();
-                try {
-                    executor.execute(() -> callback.accept(screenshot));
-                } finally {
-                    Binder.restoreCallingIdentity(identity);
+            connection.takeScreenshot(displayId, new RemoteCallback((result) -> {
+                if (result == null) {
+                    sendScreenshotResult(executor, callback, null);
+                    return;
                 }
+                final HardwareBuffer hardwareBuffer =
+                        result.getParcelable(KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER);
+                final int colorSpaceId =
+                        result.getInt(KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE_ID);
+                ColorSpace colorSpace = null;
+                if (colorSpaceId >= 0 && colorSpaceId < ColorSpace.Named.values().length) {
+                    colorSpace = ColorSpace.get(ColorSpace.Named.values()[colorSpaceId]);
+                }
+                ScreenshotResult screenshot = new ScreenshotResult(hardwareBuffer,
+                        colorSpace, System.currentTimeMillis());
+                sendScreenshotResult(executor, callback, screenshot);
             }));
         } catch (RemoteException re) {
             throw new RuntimeException(re);
@@ -2302,4 +2339,67 @@
             this.handler = handler;
         }
     }
+
+    private void sendScreenshotResult(Executor executor, Consumer<ScreenshotResult> callback,
+            ScreenshotResult screenshot) {
+        final ScreenshotResult result = screenshot;
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            executor.execute(() -> callback.accept(result));
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    /**
+     * Class including hardwareBuffer, colorSpace, and timestamp to be the result for
+     * {@link AccessibilityService#takeScreenshot} API.
+     * <p>
+     * <strong>Note:</strong> colorSpace would be null if the name of this colorSpace isn't at
+     * {@link ColorSpace.Named}.
+     * </p>
+     */
+    public static final class ScreenshotResult {
+        private final @NonNull HardwareBuffer mHardwareBuffer;
+        private final @Nullable ColorSpace mColorSpace;
+        private final long mTimestamp;
+
+        private ScreenshotResult(@NonNull HardwareBuffer hardwareBuffer,
+                @Nullable ColorSpace colorSpace, long timestamp) {
+            Preconditions.checkNotNull(hardwareBuffer, "hardwareBuffer cannot be null");
+            mHardwareBuffer = hardwareBuffer;
+            mColorSpace = colorSpace;
+            mTimestamp = timestamp;
+        }
+
+        /**
+         * Gets the colorSpace identifying a specific organization of colors of the screenshot.
+         *
+         * @return the colorSpace or {@code null} if the name of colorSpace isn't at
+         * {@link ColorSpace.Named}
+         */
+        @Nullable
+        public ColorSpace getColorSpace() {
+            return mColorSpace;
+        }
+
+        /**
+         * Gets the hardwareBuffer representing a memory buffer of the screenshot.
+         *
+         * @return the hardwareBuffer
+         */
+        @NonNull
+        public HardwareBuffer getHardwareBuffer() {
+            return mHardwareBuffer;
+        }
+
+        /**
+         * Gets the timestamp of taking the screenshot.
+         *
+         * @return the timestamp from {@link System#currentTimeMillis()}
+         */
+        public long getTimestamp() {
+            return mTimestamp;
+        };
+    }
 }
diff --git a/core/java/android/accessibilityservice/GestureDescription.java b/core/java/android/accessibilityservice/GestureDescription.java
index 3b79d21..a821dad 100644
--- a/core/java/android/accessibilityservice/GestureDescription.java
+++ b/core/java/android/accessibilityservice/GestureDescription.java
@@ -40,7 +40,7 @@
  */
 public final class GestureDescription {
     /** Gestures may contain no more than this many strokes */
-    private static final int MAX_STROKE_COUNT = 10;
+    private static final int MAX_STROKE_COUNT = 20;
 
     /**
      * Upper bound on total gesture duration. Nearly all gestures will be much shorter.
@@ -194,7 +194,10 @@
         public Builder addStroke(@NonNull StrokeDescription strokeDescription) {
             if (mStrokes.size() >= MAX_STROKE_COUNT) {
                 throw new IllegalStateException(
-                        "Attempting to add too many strokes to a gesture");
+                        "Attempting to add too many strokes to a gesture. Maximum is "
+                                + MAX_STROKE_COUNT
+                                + ", got "
+                                + mStrokes.size());
             }
 
             mStrokes.add(strokeDescription);
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index 5db4dd7..9177d4d 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -110,7 +110,5 @@
 
     int getWindowIdForLeashToken(IBinder token);
 
-    Bitmap takeScreenshot(int displayId);
-
-    void takeScreenshotWithCallback(int displayId, in RemoteCallback callback);
+    void takeScreenshot(int displayId, in RemoteCallback callback);
 }
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index f31c614..642f51b 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -2880,13 +2880,14 @@
      * {@link #enterPictureInPictureMode(PictureInPictureParams)} at this time. For example, the
      * system will call this method when the activity is being put into the background, so the app
      * developer might want to switch an activity into PIP mode instead.</p>
+     *
+     * @return {@code true} if the activity received this callback regardless of if it acts on it
+     * or not. If {@code false}, the framework will assume the app hasn't been updated to leverage
+     * this callback and will in turn send a legacy callback of {@link #onUserLeaveHint()} for the
+     * app to enter picture-in-picture mode.
      */
-    public void onPictureInPictureRequested() {
-        // Previous recommendation was for apps to enter picture-in-picture in onUserLeaveHint()
-        // which is sent after onPause(). This new method allows the system to request the app to
-        // go into picture-in-picture decoupling it from life cycle events. For backwards
-        // compatibility we schedule the life cycle events if the app didn't override this method.
-        mMainThread.schedulePauseAndReturnToCurrentState(mToken);
+    public boolean onPictureInPictureRequested() {
+        return false;
     }
 
     void dispatchMovedToDisplay(int displayId, Configuration config) {
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 206c771..db9aa18 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -1472,7 +1472,7 @@
                 dest.writeInt(1);
                 dest.writeString(mLabel);
             }
-            if (mIcon == null) {
+            if (mIcon == null || mIcon.isRecycled()) {
                 dest.writeInt(0);
             } else {
                 dest.writeInt(1);
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index c901d2a..1921567 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -3772,7 +3772,15 @@
             return;
         }
 
-        r.activity.onPictureInPictureRequested();
+        final boolean receivedByApp = r.activity.onPictureInPictureRequested();
+        if (!receivedByApp) {
+            // Previous recommendation was for apps to enter picture-in-picture in
+            // onUserLeavingHint() for cases such as the app being put into the background. For
+            // backwards compatibility with apps that are not using the newer
+            // onPictureInPictureRequested() callback, we schedule the life cycle events needed to
+            // trigger onUserLeavingHint(), then we return the activity to its previous state.
+            schedulePauseWithUserLeaveHintAndReturnToCurrentState(r);
+        }
     }
 
     /**
@@ -3780,18 +3788,7 @@
      * return to its previous state. This allows activities that rely on onUserLeaveHint instead of
      * onPictureInPictureRequested to enter picture-in-picture.
      */
-    public void schedulePauseAndReturnToCurrentState(IBinder token) {
-        final ActivityClientRecord r = mActivities.get(token);
-        if (r == null) {
-            Log.w(TAG, "Activity to request pause with user leaving hint to no longer exists");
-            return;
-        }
-
-        if (r.mIsUserLeaving) {
-            // The activity is about to perform user leaving, so there's no need to cycle ourselves.
-            return;
-        }
-
+    private void schedulePauseWithUserLeaveHintAndReturnToCurrentState(ActivityClientRecord r) {
         final int prevState = r.getLifecycleState();
         if (prevState != ON_RESUME && prevState != ON_PAUSE) {
             return;
@@ -4544,7 +4541,6 @@
         if (r != null) {
             if (userLeaving) {
                 performUserLeavingActivity(r);
-                r.mIsUserLeaving = false;
             }
 
             r.activity.mConfigChangeFlags |= configChanges;
@@ -4559,7 +4555,6 @@
     }
 
     final void performUserLeavingActivity(ActivityClientRecord r) {
-        r.mIsUserLeaving = true;
         mInstrumentation.callActivityOnPictureInPictureRequested(r.activity);
         mInstrumentation.callActivityOnUserLeaving(r.activity);
     }
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 46f8669..6ef99a3 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -27,6 +27,8 @@
 import android.annotation.SystemService;
 import android.annotation.TestApi;
 import android.app.usage.UsageStatsManager;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -88,14 +90,31 @@
 import java.util.function.Supplier;
 
 /**
- * API for interacting with "application operation" tracking.
+ * AppOps are mappings of [package/uid, op-name] -> [mode]. The list of existing appops is defined
+ * by the system and cannot be amended by apps. Only system apps can change appop-modes.
  *
- * <p>This API is not generally intended for third party application developers; most
- * features are only available to system applications.
+ * <p>Beside a mode the system tracks when an op was {@link #noteOp noted}. The tracked data can
+ * only be read by system components.
+ *
+ * <p>Installed apps can usually only listen to changes and events on their own ops. E.g.
+ * {@link AppOpsCollector} allows to get a callback each time an app called {@link #noteOp} or
+ * {@link #startOp} for an op belonging to the app.
  */
 @SystemService(Context.APP_OPS_SERVICE)
 public class AppOpsManager {
     /**
+     * This is a subtle behavior change to {@link #startWatchingMode}.
+     *
+     * Before this change the system called back for the switched op. After the change the system
+     * will call back for the actually requested op or all switched ops if no op is specified.
+     *
+     * @hide
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+    public static final long CALL_BACK_ON_CHANGED_LISTENER_WITH_SWITCHED_OP_CHANGE = 148180766L;
+
+    /**
      * <p>App ops allows callers to:</p>
      *
      * <ul>
@@ -142,6 +161,38 @@
 
     static IBinder sClientId;
 
+    /**
+     * How many seconds we want for a drop in uid state from top to settle before applying it.
+     *
+     * <>Set a parameter to {@link android.provider.Settings.Global#APP_OPS_CONSTANTS}
+     *
+     * @hide
+     */
+    @TestApi
+    public static final String KEY_TOP_STATE_SETTLE_TIME = "top_state_settle_time";
+
+    /**
+     * How many second we want for a drop in uid state from foreground to settle before applying it.
+     *
+     * <>Set a parameter to {@link android.provider.Settings.Global#APP_OPS_CONSTANTS}
+     *
+     * @hide
+     */
+    @TestApi
+    public static final String KEY_FG_SERVICE_STATE_SETTLE_TIME =
+            "fg_service_state_settle_time";
+
+    /**
+     * How many seconds we want for a drop in uid state from background to settle before applying
+     * it.
+     *
+     * <>Set a parameter to {@link android.provider.Settings.Global#APP_OPS_CONSTANTS}
+     *
+     * @hide
+     */
+    @TestApi
+    public static final String KEY_BG_STATE_SETTLE_TIME = "bg_state_settle_time";
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(flag = true, prefix = { "HISTORICAL_MODE_" }, value = {
@@ -2803,7 +2854,7 @@
          * @param flags The op flags
          *
          * @return the last access time (in milliseconds since epoch start (January 1, 1970
-         * 00:00:00.000 GMT - Gregorian)) or {@code -1}
+         * 00:00:00.000 GMT - Gregorian)) or {@code -1} if there was no access
          *
          * @see #getLastAccessForegroundTime(int)
          * @see #getLastAccessBackgroundTime(int)
@@ -2820,7 +2871,7 @@
          * @param flags The op flags
          *
          * @return the last access time (in milliseconds since epoch start (January 1, 1970
-         * 00:00:00.000 GMT - Gregorian)) or {@code -1}
+         * 00:00:00.000 GMT - Gregorian)) or {@code -1} if there was no foreground access
          *
          * @see #getLastAccessTime(int)
          * @see #getLastAccessBackgroundTime(int)
@@ -2838,7 +2889,7 @@
          * @param flags The op flags
          *
          * @return the last access time (in milliseconds since epoch start (January 1, 1970
-         * 00:00:00.000 GMT - Gregorian)) or {@code -1}
+         * 00:00:00.000 GMT - Gregorian)) or {@code -1} if there was no background access
          *
          * @see #getLastAccessTime(int)
          * @see #getLastAccessForegroundTime(int)
@@ -2855,7 +2906,7 @@
          *
          * @param flags The op flags
          *
-         * @return the last access event of {@code null}
+         * @return the last access event of {@code null} if there was no access
          */
         private @Nullable NoteOpEvent getLastAccessEvent(@UidState int fromUidState,
                 @UidState int toUidState, @OpFlags int flags) {
@@ -2870,7 +2921,7 @@
          * @param flags The op flags
          *
          * @return the last access time (in milliseconds since epoch start (January 1, 1970
-         * 00:00:00.000 GMT - Gregorian)) or {@code -1}
+         * 00:00:00.000 GMT - Gregorian)) or {@code -1} if there was no access
          *
          * @see #getLastAccessTime(int)
          * @see #getLastAccessForegroundTime(int)
@@ -2893,7 +2944,7 @@
          * @param flags The op flags
          *
          * @return the last rejection time (in milliseconds since epoch start (January 1, 1970
-         * 00:00:00.000 GMT - Gregorian)) or {@code -1}
+         * 00:00:00.000 GMT - Gregorian)) or {@code -1} if there was no rejection
          *
          * @see #getLastRejectForegroundTime(int)
          * @see #getLastRejectBackgroundTime(int)
@@ -2910,7 +2961,7 @@
          * @param flags The op flags
          *
          * @return the last rejection time (in milliseconds since epoch start (January 1, 1970
-         * 00:00:00.000 GMT - Gregorian)) or {@code -1}
+         * 00:00:00.000 GMT - Gregorian)) or {@code -1} if there was no foreground rejection
          *
          * @see #getLastRejectTime(int)
          * @see #getLastRejectBackgroundTime(int)
@@ -2928,7 +2979,7 @@
          * @param flags The op flags
          *
          * @return the last rejection time (in milliseconds since epoch start (January 1, 1970
-         * 00:00:00.000 GMT - Gregorian)) or {@code -1}
+         * 00:00:00.000 GMT - Gregorian)) or {@code -1} if there was no background rejection
          *
          * @see #getLastRejectTime(int)
          * @see #getLastRejectForegroundTime(int)
@@ -2945,8 +2996,7 @@
          *
          * @param flags The op flags
          *
-         * @return the last rejection time (in milliseconds since epoch start (January 1, 1970
-         * 00:00:00.000 GMT - Gregorian)) or {@code -1}
+         * @return the last rejection event of {@code null} if there was no rejection
          *
          * @see #getLastRejectTime(int)
          * @see #getLastRejectForegroundTime(int)
@@ -2965,7 +3015,8 @@
          * @param toUidState The highest UID state for which to query (inclusive)
          * @param flags The op flags
          *
-         * @return the last access time (in milliseconds since epoch) or {@code -1}
+         * @return the last access time (in milliseconds since epoch) or {@code -1} if there was no
+         * rejection
          *
          * @see #getLastRejectTime(int)
          * @see #getLastRejectForegroundTime(int)
@@ -2988,7 +3039,7 @@
          *
          * @param flags The op flags
          *
-         * @return the duration in milliseconds or {@code -1}
+         * @return the duration in milliseconds or {@code -1} if there was no rejection
          *
          * @see #getLastForegroundDuration(int)
          * @see #getLastBackgroundDuration(int)
@@ -3004,7 +3055,7 @@
          *
          * @param flags The op flags
          *
-         * @return the duration in milliseconds or {@code -1}
+         * @return the duration in milliseconds or {@code -1} if there was no foreground rejection
          *
          * @see #getLastDuration(int)
          * @see #getLastBackgroundDuration(int)
@@ -3021,7 +3072,7 @@
          *
          * @param flags The op flags
          *
-         * @return the duration in milliseconds or {@code -1}
+         * @return the duration in milliseconds or {@code -1} if there was no background rejection
          *
          * @see #getLastDuration(int)
          * @see #getLastForegroundDuration(int)
@@ -3040,7 +3091,7 @@
          * @param toUidState The highest UID state for which to query (inclusive)
          * @param flags The op flags
          *
-         * @return the duration in milliseconds or {@code -1}
+         * @return the duration in milliseconds or {@code -1} if there was no rejection
          *
          * @see #getLastDuration(int)
          * @see #getLastForegroundDuration(int)
@@ -3064,7 +3115,7 @@
          *
          * @param flags The op flags
          *
-         * @return The proxy name or {@code null}
+         * @return The proxy info or {@code null} if there was no proxy access
          *
          * @see #getLastForegroundProxyInfo(int)
          * @see #getLastBackgroundProxyInfo(int)
@@ -3081,7 +3132,7 @@
          *
          * @param flags The op flags
          *
-         * @return The proxy name or {@code null}
+         * @return The proxy info or {@code null} if there was no proxy access
          *
          * @see #getLastProxyInfo(int)
          * @see #getLastBackgroundProxyInfo(int)
@@ -3099,7 +3150,7 @@
          *
          * @param flags The op flags
          *
-         * @return The proxy name or {@code null}
+         * @return The proxy info or {@code null} if there was no proxy background access
          *
          * @see #getLastProxyInfo(int)
          * @see #getLastForegroundProxyInfo(int)
@@ -3119,7 +3170,7 @@
          * @param toUidState The highest UID state for which to query (inclusive)
          * @param flags The op flags
          *
-         * @return The proxy name or {@code null}
+         * @return The proxy info or {@code null} if there was no proxy foreground access
          *
          * @see #getLastProxyInfo(int)
          * @see #getLastForegroundProxyInfo(int)
@@ -3375,7 +3426,7 @@
          * @param flags The op flags
          *
          * @return the last access time (in milliseconds since epoch start (January 1, 1970
-         * 00:00:00.000 GMT - Gregorian)) or {@code -1}
+         * 00:00:00.000 GMT - Gregorian)) or {@code -1} if there was no access
          *
          * @see #getLastAccessForegroundTime(int)
          * @see #getLastAccessBackgroundTime(int)
@@ -3392,7 +3443,7 @@
          * @param flags The op flags
          *
          * @return the last access time (in milliseconds since epoch start (January 1, 1970
-         * 00:00:00.000 GMT - Gregorian)) or {@code -1}
+         * 00:00:00.000 GMT - Gregorian)) or {@code -1} if there was no foreground access
          *
          * @see #getLastAccessTime(int)
          * @see #getLastAccessBackgroundTime(int)
@@ -3410,7 +3461,7 @@
          * @param flags The op flags
          *
          * @return the last access time (in milliseconds since epoch start (January 1, 1970
-         * 00:00:00.000 GMT - Gregorian)) or {@code -1}
+         * 00:00:00.000 GMT - Gregorian)) or {@code -1} if there was no background access
          *
          * @see #getLastAccessTime(int)
          * @see #getLastAccessForegroundTime(int)
@@ -3427,7 +3478,7 @@
          *
          * @param flags The op flags
          *
-         * @return the last access event of {@code null}
+         * @return the last access event of {@code null} if there was no access
          */
         private @Nullable NoteOpEvent getLastAccessEvent(@UidState int fromUidState,
                 @UidState int toUidState, @OpFlags int flags) {
@@ -3453,7 +3504,7 @@
          * @param flags The op flags
          *
          * @return the last access time (in milliseconds since epoch start (January 1, 1970
-         * 00:00:00.000 GMT - Gregorian)) or {@code -1}
+         * 00:00:00.000 GMT - Gregorian)) or {@code -1} if there was no access
          *
          * @see #getLastAccessTime(int)
          * @see #getLastAccessForegroundTime(int)
@@ -3489,7 +3540,7 @@
          * @param flags The op flags
          *
          * @return the last rejection time (in milliseconds since epoch start (January 1, 1970
-         * 00:00:00.000 GMT - Gregorian)) or {@code -1}
+         * 00:00:00.000 GMT - Gregorian)) or {@code -1} if there was no rejection
          *
          * @see #getLastRejectForegroundTime(int)
          * @see #getLastRejectBackgroundTime(int)
@@ -3506,7 +3557,7 @@
          * @param flags The op flags
          *
          * @return the last rejection time (in milliseconds since epoch start (January 1, 1970
-         * 00:00:00.000 GMT - Gregorian)) or {@code -1}
+         * 00:00:00.000 GMT - Gregorian)) or {@code -1} if there was no foreground rejection
          *
          * @see #getLastRejectTime(int)
          * @see #getLastRejectBackgroundTime(int)
@@ -3524,7 +3575,7 @@
          * @param flags The op flags
          *
          * @return the last rejection time (in milliseconds since epoch start (January 1, 1970
-         * 00:00:00.000 GMT - Gregorian)) or {@code -1}
+         * 00:00:00.000 GMT - Gregorian)) or {@code -1} if there was no background rejection
          *
          * @see #getLastRejectTime(int)
          * @see #getLastRejectForegroundTime(int)
@@ -3541,7 +3592,7 @@
          *
          * @param flags The op flags
          *
-         * @return the last reject event of {@code null}
+         * @return the last reject event of {@code null} if there was no rejection
          */
         private @Nullable NoteOpEvent getLastRejectEvent(@UidState int fromUidState,
                 @UidState int toUidState, @OpFlags int flags) {
@@ -3567,7 +3618,7 @@
          * @param flags The op flags
          *
          * @return the last rejection time (in milliseconds since epoch start (January 1, 1970
-         * 00:00:00.000 GMT - Gregorian)) or {@code -1}
+         * 00:00:00.000 GMT - Gregorian)) or {@code -1} if there was no rejection
          *
          * @see #getLastRejectTime(int)
          * @see #getLastRejectForegroundTime(int)
@@ -3611,7 +3662,7 @@
          *
          * @param flags The op flags
          *
-         * @return the duration in milliseconds or {@code -1}
+         * @return the duration in milliseconds or {@code -1} if there was no access
          *
          * @see #getLastForegroundDuration(int)
          * @see #getLastBackgroundDuration(int)
@@ -3627,7 +3678,7 @@
          *
          * @param flags The op flags
          *
-         * @return the duration in milliseconds or {@code -1}
+         * @return the duration in milliseconds or {@code -1} if there was no foreground access
          *
          * @see #getLastDuration(int)
          * @see #getLastBackgroundDuration(int)
@@ -3644,7 +3695,7 @@
          *
          * @param flags The op flags
          *
-         * @return the duration in milliseconds or {@code -1}
+         * @return the duration in milliseconds or {@code -1} if there was no background access
          *
          * @see #getLastDuration(int)
          * @see #getLastForegroundDuration(int)
@@ -3663,7 +3714,7 @@
          * @param toUidState The highest UID state for which to query (inclusive)
          * @param flags The op flags
          *
-         * @return the duration in milliseconds or {@code -1}
+         * @return the duration in milliseconds or {@code -1} if there was no access
          *
          * @see #getLastDuration(int)
          * @see #getLastForegroundDuration(int)
@@ -3738,7 +3789,7 @@
          *
          * @param flags The op flags
          *
-         * @return The proxy name or {@code null}
+         * @return The proxy info or {@code null} if there was no proxy access
          *
          * @see #getLastForegroundProxyInfo(int)
          * @see #getLastBackgroundProxyInfo(int)
@@ -3755,7 +3806,7 @@
          *
          * @param flags The op flags
          *
-         * @return The proxy name or {@code null}
+         * @return The proxy info or {@code null} if there was no foreground proxy access
          *
          * @see #getLastProxyInfo(int)
          * @see #getLastBackgroundProxyInfo(int)
@@ -3773,7 +3824,7 @@
          *
          * @param flags The op flags
          *
-         * @return The proxy name or {@code null}
+         * @return The proxy info or {@code null} if there was no background proxy access
          *
          * @see #getLastProxyInfo(int)
          * @see #getLastForegroundProxyInfo(int)
@@ -3793,7 +3844,7 @@
          * @param toUidState The highest UID state for which to query (inclusive)
          * @param flags The op flags
          *
-         * @return The proxy name or {@code null}
+         * @return The proxy info or {@code null} if there was no proxy access
          *
          * @see #getLastProxyInfo(int)
          * @see #getLastForegroundProxyInfo(int)
@@ -6774,6 +6825,9 @@
      * succeeds, the last execution time of the operation for this app will be updated to
      * the current time.
      *
+     * <p>If this is a check that is not preceding the protected operation, use
+     * {@link #unsafeCheckOp} instead.
+     *
      * @param op The operation to note.  One of the OPSTR_* constants.
      * @param uid The user id of the application attempting to perform the operation.
      * @param packageName The name of the application attempting to perform the operation.
@@ -6798,6 +6852,9 @@
      * succeeds, the last execution time of the operation for this app will be updated to
      * the current time.
      *
+     * <p>If this is a check that is not preceding the protected operation, use
+     * {@link #unsafeCheckOp} instead.
+     *
      * @param op The operation to note.  One of the OP_* constants.
      * @param uid The user id of the application attempting to perform the operation.
      * @param packageName The name of the application attempting to perform the operation.
@@ -7757,9 +7814,38 @@
 
     /**
      * Callback an app can choose to {@link #setNotedAppOpsCollector register} to monitor it's noted
-     * appops.
+     * appops. I.e. each time any app calls {@link #noteOp} or {@link #startOp} one of the callback
+     * methods of this object is called.
      *
      * <p><b>Only appops related to dangerous permissions are collected.</b>
+     *
+     * <pre>
+     * setNotedAppOpsCollector(new AppOpsCollector() {
+     *     ArraySet<Pair<String, String>> opsNotedForThisProcess = new ArraySet<>();
+     *
+     *     private synchronized void addAccess(String op, String accessLocation) {
+     *         // Ops are often noted when permission protected APIs were called.
+     *         // In this case permissionToOp() allows to resolve the permission<->op
+     *         opsNotedForThisProcess.add(new Pair(accessType, accessLocation));
+     *     }
+     *
+     *     public void onNoted(SyncNotedAppOp op) {
+     *         // Accesses is currently happening, hence stack trace describes location of access
+     *         addAccess(op.getOp(), Arrays.toString(Thread.currentThread().getStackTrace()));
+     *     }
+     *
+     *     public void onSelfNoted(SyncNotedAppOp op) {
+     *         onNoted(op);
+     *     }
+     *
+     *     public void onAsyncNoted(AsyncNotedAppOp asyncOp) {
+     *         // Stack trace is not useful for async ops as accessed happened on different thread
+     *         addAccess(asyncOp.getOp(), asyncOp.getMessage());
+     *     }
+     * });
+     * </pre>
+     *
+     * @see #setNotedAppOpsCollector
      */
     public abstract static class AppOpsCollector {
         /** Callback registered with the system. This will receive the async notes ops */
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 71cb4a4..cd05e2c 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -3229,18 +3229,18 @@
     }
 
     @Override
-    public String getSystemTextClassifierPackageName() {
+    public String getDefaultTextClassifierPackageName() {
         try {
-            return mPM.getSystemTextClassifierPackageName();
+            return mPM.getDefaultTextClassifierPackageName();
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         }
     }
 
     @Override
-    public String[] getSystemTextClassifierPackages() {
+    public String getSystemTextClassifierPackageName() {
         try {
-            return mPM.getSystemTextClassifierPackages();
+            return mPM.getSystemTextClassifierPackageName();
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         }
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 4b1ba02..16c0910 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -48,6 +48,8 @@
     void clearData(String pkg, int uid, boolean fromApp);
     void enqueueTextToast(String pkg, IBinder token, CharSequence text, int duration, int displayId, @nullable ITransientNotificationCallback callback);
     void enqueueToast(String pkg, IBinder token, ITransientNotification callback, int duration, int displayId);
+    // TODO(b/144152069): Remove this after assessing impact on dogfood.
+    void enqueueTextOrCustomToast(String pkg, IBinder token, ITransientNotification callback, int duration, int displayId, boolean isCustom);
     void cancelToast(String pkg, IBinder token);
     void finishToken(String pkg, IBinder token);
 
diff --git a/core/java/android/app/ITaskOrganizerController.aidl b/core/java/android/app/ITaskOrganizerController.aidl
index 168f782..bfc42ef 100644
--- a/core/java/android/app/ITaskOrganizerController.aidl
+++ b/core/java/android/app/ITaskOrganizerController.aidl
@@ -31,8 +31,19 @@
      */
     void registerTaskOrganizer(ITaskOrganizer organizer, int windowingMode);
 
-    /** Apply multiple WindowContainer operations at once. */
-    void applyContainerTransaction(in WindowContainerTransaction t);
+    /**
+     * Apply multiple WindowContainer operations at once.
+     * @param organizer If non-null this transaction will use the synchronization
+     *        scheme described in BLASTSyncEngine.java. The SurfaceControl transaction
+     *        containing the effects of this WindowContainer transaction will be passed
+     *        to the organizers Transaction ready callback. If null the transaction
+     *        will apply with non particular synchronization constraints (other than
+     *        it will all apply at once).
+     * @return If organizer was non-null returns an ID for the sync operation which will
+     *         later be passed to transactionReady. This lets TaskOrganizer implementations
+     *         differentiate overlapping sync operations.
+     */
+    int applyContainerTransaction(in WindowContainerTransaction t, ITaskOrganizer organizer);
 
     /** Creates a persistent root task in WM for a particular windowing-mode. */
     ActivityManager.RunningTaskInfo createRootTask(int displayId, int windowingMode);
diff --git a/core/java/android/app/IUiAutomationConnection.aidl b/core/java/android/app/IUiAutomationConnection.aidl
index 80ba464..8c3180b 100644
--- a/core/java/android/app/IUiAutomationConnection.aidl
+++ b/core/java/android/app/IUiAutomationConnection.aidl
@@ -39,6 +39,7 @@
     boolean injectInputEvent(in InputEvent event, boolean sync);
     void syncInputTransactions();
     boolean setRotation(int rotation);
+    Bitmap takeScreenshot(in Rect crop, int rotation);
     boolean clearWindowContentFrameStats(int windowId);
     WindowContentFrameStats getWindowContentFrameStats(int windowId);
     void clearWindowAnimationFrameStats();
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 4b24e09..7212be8 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -31,7 +31,6 @@
 import android.provider.Settings;
 import android.service.notification.NotificationListenerService;
 import android.text.TextUtils;
-import android.util.Log;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.util.Preconditions;
@@ -105,6 +104,7 @@
     private static final String ATT_ORIG_IMP = "orig_imp";
     private static final String ATT_PARENT_CHANNEL = "parent";
     private static final String ATT_CONVERSATION_ID = "conv_id";
+    private static final String ATT_IMP_CONVERSATION = "imp_conv";
     private static final String ATT_DEMOTE = "dem";
     private static final String DELIMITER = ",";
 
@@ -196,6 +196,7 @@
     private String mParentId = null;
     private String mConversationId = null;
     private boolean mDemoted = false;
+    private boolean mImportantConvo = false;
 
     /**
      * Creates a notification channel.
@@ -263,6 +264,7 @@
         mParentId = in.readString();
         mConversationId = in.readString();
         mDemoted = in.readBoolean();
+        mImportantConvo = in.readBoolean();
     }
 
     @Override
@@ -321,6 +323,7 @@
         dest.writeString(mParentId);
         dest.writeString(mConversationId);
         dest.writeBoolean(mDemoted);
+        dest.writeBoolean(mImportantConvo);
     }
 
     /**
@@ -354,6 +357,14 @@
     }
 
     /**
+     * @hide
+     */
+    @TestApi
+    public void setImportantConversation(boolean importantConvo) {
+        mImportantConvo = importantConvo;
+    }
+
+    /**
      * Allows users to block notifications sent through this channel, if this channel belongs to
      * a package that is signed with the system signature. If the channel does not belong to a
      * package that is signed with the system signature, this method does nothing.
@@ -601,6 +612,18 @@
     }
 
     /**
+     * Whether or not notifications in this conversation are considered important.
+     *
+     * <p>Important conversations may get special visual treatment, and might be able to bypass DND.
+     *
+     * <p>This is only valid for channels that represent conversations, that is, those with a valid
+     * {@link #getConversationId() conversation id}.
+     */
+    public boolean isImportantConversation() {
+        return mImportantConvo;
+    }
+
+    /**
      * Returns the notification sound for this channel.
      */
     public Uri getSound() {
@@ -852,6 +875,7 @@
         setConversationId(parser.getAttributeValue(null, ATT_PARENT_CHANNEL),
                 parser.getAttributeValue(null, ATT_CONVERSATION_ID));
         setDemoted(safeBool(parser, ATT_DEMOTE, false));
+        setImportantConversation(safeBool(parser, ATT_IMP_CONVERSATION, false));
     }
 
     @Nullable
@@ -985,6 +1009,9 @@
         if (isDemoted()) {
             out.attribute(null, ATT_DEMOTE, Boolean.toString(isDemoted()));
         }
+        if (isImportantConversation()) {
+            out.attribute(null, ATT_IMP_CONVERSATION, Boolean.toString(isImportantConversation()));
+        }
 
         // mImportanceLockedDefaultApp and mImportanceLockedByOEM have a different source of
         // truth and so aren't written to this xml file
@@ -1145,7 +1172,8 @@
                 && mOriginalImportance == that.mOriginalImportance
                 && Objects.equals(getParentChannelId(), that.getParentChannelId())
                 && Objects.equals(getConversationId(), that.getConversationId())
-                && isDemoted() == that.isDemoted();
+                && isDemoted() == that.isDemoted()
+                && isImportantConversation() == that.isImportantConversation();
     }
 
     @Override
@@ -1156,7 +1184,7 @@
                 isFgServiceShown(), mVibrationEnabled, mShowBadge, isDeleted(), getGroup(),
                 getAudioAttributes(), isBlockableSystem(), mAllowBubbles,
                 mImportanceLockedByOEM, mImportanceLockedDefaultApp, mOriginalImportance,
-                mParentId, mConversationId, mDemoted);
+                mParentId, mConversationId, mDemoted, mImportantConvo);
         result = 31 * result + Arrays.hashCode(mVibration);
         return result;
     }
@@ -1204,7 +1232,8 @@
                 + ", mOriginalImp=" + mOriginalImportance
                 + ", mParent=" + mParentId
                 + ", mConversationId=" + mConversationId
-                + ", mDemoted=" + mDemoted;
+                + ", mDemoted=" + mDemoted
+                + ", mImportantConvo=" + mImportantConvo;
     }
 
     /** @hide */
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 1a8e15c..528b508 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -46,6 +46,7 @@
 import android.service.notification.Condition;
 import android.service.notification.StatusBarNotification;
 import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenPolicy;
 import android.util.Log;
 import android.util.proto.ProtoOutputStream;
 
@@ -1555,19 +1556,24 @@
         public static final int PRIORITY_CATEGORY_MEDIA = 1 << 6;
         /**System (catch-all for non-never suppressible sounds) are prioritized */
         public static final int PRIORITY_CATEGORY_SYSTEM = 1 << 7;
+        /**
+         * Conversations are allowed through DND.
+         */
+        public static final int PRIORITY_CATEGORY_CONVERSATIONS = 1 << 8;
 
         /**
          * @hide
          */
         public static final int[] ALL_PRIORITY_CATEGORIES = {
-            PRIORITY_CATEGORY_ALARMS,
-            PRIORITY_CATEGORY_MEDIA,
-            PRIORITY_CATEGORY_SYSTEM,
-            PRIORITY_CATEGORY_REMINDERS,
-            PRIORITY_CATEGORY_EVENTS,
-            PRIORITY_CATEGORY_MESSAGES,
-            PRIORITY_CATEGORY_CALLS,
-            PRIORITY_CATEGORY_REPEAT_CALLERS,
+                PRIORITY_CATEGORY_ALARMS,
+                PRIORITY_CATEGORY_MEDIA,
+                PRIORITY_CATEGORY_SYSTEM,
+                PRIORITY_CATEGORY_REMINDERS,
+                PRIORITY_CATEGORY_EVENTS,
+                PRIORITY_CATEGORY_MESSAGES,
+                PRIORITY_CATEGORY_CALLS,
+                PRIORITY_CATEGORY_REPEAT_CALLERS,
+                PRIORITY_CATEGORY_CONVERSATIONS,
         };
 
         /** Any sender is prioritized. */
@@ -1577,6 +1583,31 @@
         /** Only starred contacts are prioritized. */
         public static final int PRIORITY_SENDERS_STARRED = 2;
 
+
+        /** @hide */
+        @IntDef(prefix = { "CONVERSATION_SENDERS_" }, value = {
+                CONVERSATION_SENDERS_ANYONE,
+                CONVERSATION_SENDERS_IMPORTANT,
+                CONVERSATION_SENDERS_NONE,
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface ConversationSenders {}
+        /**
+         * Used to indicate all conversations can bypass dnd.
+         */
+        public static final int CONVERSATION_SENDERS_ANYONE = ZenPolicy.CONVERSATION_SENDERS_ANYONE;
+
+        /**
+         * Used to indicate important conversations can bypass dnd.
+         */
+        public static final int CONVERSATION_SENDERS_IMPORTANT =
+                ZenPolicy.CONVERSATION_SENDERS_IMPORTANT;
+
+        /**
+         * Used to indicate no conversations can bypass dnd.
+         */
+        public static final int CONVERSATION_SENDERS_NONE = ZenPolicy.CONVERSATION_SENDERS_NONE;
+
         /** Notification categories to prioritize. Bitmask of PRIORITY_CATEGORY_* constants. */
         public final int priorityCategories;
 
@@ -1589,6 +1620,18 @@
         public final int priorityMessageSenders;
 
         /**
+         * Notification senders to prioritize for conversations. One of:
+         * {@link #CONVERSATION_SENDERS_NONE}, {@link #CONVERSATION_SENDERS_IMPORTANT},
+         * {@link #CONVERSATION_SENDERS_ANYONE}.
+         */
+        public final int priorityConversationSenders;
+
+        /**
+         * @hide
+         */
+        public static final int CONVERSATION_SENDERS_UNSET = -1;
+
+        /**
          * @hide
          */
         public static final int SUPPRESSED_EFFECTS_UNSET = -1;
@@ -1665,21 +1708,6 @@
                 SUPPRESSED_EFFECT_NOTIFICATION_LIST
         };
 
-        private static final int[] SCREEN_OFF_SUPPRESSED_EFFECTS = {
-                SUPPRESSED_EFFECT_SCREEN_OFF,
-                SUPPRESSED_EFFECT_FULL_SCREEN_INTENT,
-                SUPPRESSED_EFFECT_LIGHTS,
-                SUPPRESSED_EFFECT_AMBIENT,
-        };
-
-        private static final int[] SCREEN_ON_SUPPRESSED_EFFECTS = {
-                SUPPRESSED_EFFECT_SCREEN_ON,
-                SUPPRESSED_EFFECT_PEEK,
-                SUPPRESSED_EFFECT_STATUS_BAR,
-                SUPPRESSED_EFFECT_BADGE,
-                SUPPRESSED_EFFECT_NOTIFICATION_LIST
-        };
-
         /**
          * Visual effects to suppress for a notification that is filtered by Do Not Disturb mode.
          * Bitmask of SUPPRESSED_EFFECT_* constants.
@@ -1718,17 +1746,16 @@
          */
         public Policy(int priorityCategories, int priorityCallSenders, int priorityMessageSenders) {
             this(priorityCategories, priorityCallSenders, priorityMessageSenders,
-                    SUPPRESSED_EFFECTS_UNSET, STATE_UNSET);
+                    SUPPRESSED_EFFECTS_UNSET, STATE_UNSET, CONVERSATION_SENDERS_UNSET);
         }
 
         /**
          * Constructs a policy for Do Not Disturb priority mode behavior.
          *
          * <p>
-         *     Apps that target API levels below {@link Build.VERSION_CODES#P} cannot
+         *     Apps that target API levels below {@link Build.VERSION_CODES#R} cannot
          *     change user-designated values to allow or disallow
-         *     {@link Policy#PRIORITY_CATEGORY_ALARMS}, {@link Policy#PRIORITY_CATEGORY_SYSTEM}, and
-         *     {@link Policy#PRIORITY_CATEGORY_MEDIA} from bypassing dnd.
+         *     {@link Policy#PRIORITY_CATEGORY_CONVERSATIONS}, from bypassing dnd.
          * <p>
          *     Additionally, apps that target API levels below {@link Build.VERSION_CODES#P} can
          *     only modify the {@link #SUPPRESSED_EFFECT_SCREEN_ON} and
@@ -1752,27 +1779,68 @@
          */
         public Policy(int priorityCategories, int priorityCallSenders, int priorityMessageSenders,
                 int suppressedVisualEffects) {
-            this.priorityCategories = priorityCategories;
-            this.priorityCallSenders = priorityCallSenders;
-            this.priorityMessageSenders = priorityMessageSenders;
-            this.suppressedVisualEffects = suppressedVisualEffects;
-            this.state = STATE_UNSET;
+            this(priorityCategories, priorityCallSenders, priorityMessageSenders,
+                    suppressedVisualEffects, STATE_UNSET, CONVERSATION_SENDERS_UNSET);
+        }
+
+        /**
+         * Constructs a policy for Do Not Disturb priority mode behavior.
+         *
+         * <p>
+         *     Apps that target API levels below {@link Build.VERSION_CODES#P} cannot
+         *     change user-designated values to allow or disallow
+         *     {@link Policy#PRIORITY_CATEGORY_CONVERSATIONS} from bypassing dnd. If you do need
+         *     to change them, use a {@link ZenPolicy} associated with an {@link AutomaticZenRule}
+         *     instead of changing the global setting.
+         * <p>
+         *     Apps that target API levels below {@link Build.VERSION_CODES#P} cannot
+         *     change user-designated values to allow or disallow
+         *     {@link Policy#PRIORITY_CATEGORY_ALARMS},
+         *     {@link Policy#PRIORITY_CATEGORY_SYSTEM}, and
+         *     {@link Policy#PRIORITY_CATEGORY_MEDIA} from bypassing dnd.
+         * <p>
+         *     Additionally, apps that target API levels below {@link Build.VERSION_CODES#P} can
+         *     only modify the {@link #SUPPRESSED_EFFECT_SCREEN_ON} and
+         *     {@link #SUPPRESSED_EFFECT_SCREEN_OFF} bits of the suppressed visual effects field.
+         *     All other suppressed effects will be ignored and reconstituted from the screen on
+         *     and screen off values.
+         * <p>
+         *     Apps that target {@link Build.VERSION_CODES#P} or above can set any
+         *     suppressed visual effects. However, if any suppressed effects >
+         *     {@link #SUPPRESSED_EFFECT_SCREEN_ON} are set, {@link #SUPPRESSED_EFFECT_SCREEN_ON}
+         *     and {@link #SUPPRESSED_EFFECT_SCREEN_OFF} will be ignored and reconstituted from
+         *     the more specific suppressed visual effect bits. Apps should migrate to targeting
+         *     specific effects instead of the deprecated {@link #SUPPRESSED_EFFECT_SCREEN_ON} and
+         *     {@link #SUPPRESSED_EFFECT_SCREEN_OFF} effects.
+         *
+         * @param priorityCategories bitmask of categories of notifications that can bypass DND.
+         * @param priorityCallSenders which callers can bypass DND.
+         * @param priorityMessageSenders which message senders can bypass DND.
+         * @param suppressedVisualEffects which visual interruptions should be suppressed from
+         *                                notifications that are filtered by DND.
+         */
+        public Policy(int priorityCategories, int priorityCallSenders, int priorityMessageSenders,
+                int suppressedVisualEffects, int priorityConversationSenders) {
+            this(priorityCategories, priorityCallSenders, priorityMessageSenders,
+                    suppressedVisualEffects, STATE_UNSET, priorityConversationSenders);
         }
 
         /** @hide */
         public Policy(int priorityCategories, int priorityCallSenders, int priorityMessageSenders,
-                int suppressedVisualEffects, int state) {
+                int suppressedVisualEffects, int state, int priorityConversationSenders) {
             this.priorityCategories = priorityCategories;
             this.priorityCallSenders = priorityCallSenders;
             this.priorityMessageSenders = priorityMessageSenders;
             this.suppressedVisualEffects = suppressedVisualEffects;
             this.state = state;
+            this.priorityConversationSenders = priorityConversationSenders;
         }
 
+
         /** @hide */
         public Policy(Parcel source) {
             this(source.readInt(), source.readInt(), source.readInt(), source.readInt(),
-                    source.readInt());
+                    source.readInt(), source.readInt());
         }
 
         @Override
@@ -1782,6 +1850,7 @@
             dest.writeInt(priorityMessageSenders);
             dest.writeInt(suppressedVisualEffects);
             dest.writeInt(state);
+            dest.writeInt(priorityConversationSenders);
         }
 
         @Override
@@ -1792,7 +1861,7 @@
         @Override
         public int hashCode() {
             return Objects.hash(priorityCategories, priorityCallSenders, priorityMessageSenders,
-                    suppressedVisualEffects, state);
+                    suppressedVisualEffects, state, priorityConversationSenders);
         }
 
         @Override
@@ -1805,7 +1874,8 @@
                     && other.priorityMessageSenders == priorityMessageSenders
                     && suppressedVisualEffectsEqual(suppressedVisualEffects,
                     other.suppressedVisualEffects)
-                    && other.state == this.state;
+                    && other.state == this.state
+                    && other.priorityConversationSenders == this.priorityConversationSenders;
         }
 
         private boolean suppressedVisualEffectsEqual(int suppressedEffects,
@@ -1867,6 +1937,8 @@
                     + "priorityCategories=" + priorityCategoriesToString(priorityCategories)
                     + ",priorityCallSenders=" + prioritySendersToString(priorityCallSenders)
                     + ",priorityMessageSenders=" + prioritySendersToString(priorityMessageSenders)
+                    + ",priorityConvSenders="
+                    + conversationSendersToString(priorityConversationSenders)
                     + ",suppressedVisualEffects="
                     + suppressedEffectsToString(suppressedVisualEffects)
                     + ",areChannelsBypassingDnd=" + (((state & STATE_CHANNELS_BYPASSING_DND) != 0)
@@ -2003,6 +2075,7 @@
                 case PRIORITY_CATEGORY_ALARMS: return "PRIORITY_CATEGORY_ALARMS";
                 case PRIORITY_CATEGORY_MEDIA: return "PRIORITY_CATEGORY_MEDIA";
                 case PRIORITY_CATEGORY_SYSTEM: return "PRIORITY_CATEGORY_SYSTEM";
+                case PRIORITY_CATEGORY_CONVERSATIONS: return "PRIORITY_CATEGORY_CONVERSATIONS";
                 default: return "PRIORITY_CATEGORY_UNKNOWN_" + priorityCategory;
             }
         }
@@ -2016,7 +2089,25 @@
             }
         }
 
-        public static final @android.annotation.NonNull Parcelable.Creator<Policy> CREATOR = new Parcelable.Creator<Policy>() {
+        /**
+         * @hide
+         */
+        public static @NonNull String conversationSendersToString(int priorityConversationSenders) {
+            switch (priorityConversationSenders) {
+                case CONVERSATION_SENDERS_ANYONE:
+                    return "anyone";
+                case CONVERSATION_SENDERS_IMPORTANT:
+                    return "important";
+                case CONVERSATION_SENDERS_NONE:
+                    return "none";
+                case CONVERSATION_SENDERS_UNSET:
+                    return "unset";
+            }
+            return "invalidConversationType{" + priorityConversationSenders + "}";
+        }
+
+        public static final @android.annotation.NonNull Parcelable.Creator<Policy> CREATOR
+                = new Parcelable.Creator<Policy>() {
             @Override
             public Policy createFromParcel(Parcel in) {
                 return new Policy(in);
@@ -2054,6 +2145,11 @@
         }
 
         /** @hide **/
+        public boolean allowConversations() {
+            return (priorityCategories & PRIORITY_CATEGORY_CONVERSATIONS) != 0;
+        }
+
+        /** @hide **/
         public boolean allowMessages() {
             return (priorityCategories & PRIORITY_CATEGORY_MESSAGES) != 0;
         }
@@ -2079,6 +2175,11 @@
         }
 
         /** @hide **/
+        public int allowConversationsFrom() {
+            return priorityConversationSenders;
+        }
+
+        /** @hide **/
         public boolean showFullScreenIntents() {
             return (suppressedVisualEffects & SUPPRESSED_EFFECT_FULL_SCREEN_INTENT) == 0;
         }
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 65a5f6b..50b9d6b 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -163,7 +163,7 @@
 import android.os.health.SystemHealthManager;
 import android.os.image.DynamicSystemManager;
 import android.os.image.IDynamicSystemService;
-import android.os.incremental.IIncrementalManagerNative;
+import android.os.incremental.IIncrementalService;
 import android.os.incremental.IncrementalManager;
 import android.os.storage.StorageManager;
 import android.permission.PermissionControllerManager;
@@ -1299,7 +1299,7 @@
                             return null;
                         }
                         return new IncrementalManager(
-                                IIncrementalManagerNative.Stub.asInterface(b));
+                                IIncrementalService.Stub.asInterface(b));
                     }});
 
         registerService(Context.FILE_INTEGRITY_SERVICE, FileIntegrityManager.class,
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 35cf687..a9a06da 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -27,7 +27,10 @@
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.graphics.Rect;
 import android.graphics.Region;
+import android.hardware.display.DisplayManagerGlobal;
 import android.os.Build;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -828,20 +831,39 @@
     }
 
     /**
-     * Takes a screenshot of the default display and returns it by {@link Bitmap.Config#HARDWARE}
-     * format.
+     * Takes a screenshot.
      *
      * @return The screenshot bitmap on success, null otherwise.
      */
     public Bitmap takeScreenshot() {
-        final int connectionId;
         synchronized (mLock) {
             throwIfNotConnectedLocked();
-            connectionId = mConnectionId;
         }
-        // Calling out without a lock held.
-        return AccessibilityInteractionClient.getInstance()
-                .takeScreenshot(connectionId, Display.DEFAULT_DISPLAY);
+        Display display = DisplayManagerGlobal.getInstance()
+                .getRealDisplay(Display.DEFAULT_DISPLAY);
+        Point displaySize = new Point();
+        display.getRealSize(displaySize);
+
+        int rotation = display.getRotation();
+
+        // Take the screenshot
+        Bitmap screenShot = null;
+        try {
+            // Calling out without a lock held.
+            screenShot = mUiAutomationConnection.takeScreenshot(
+                    new Rect(0, 0, displaySize.x, displaySize.y), rotation);
+            if (screenShot == null) {
+                return null;
+            }
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error while taking screnshot!", re);
+            return null;
+        }
+
+        // Optimization
+        screenShot.setHasAlpha(false);
+
+        return screenShot;
     }
 
     /**
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index 4fb9743..82e9881 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -21,6 +21,8 @@
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
 import android.hardware.input.InputManager;
 import android.os.Binder;
 import android.os.IBinder;
@@ -178,6 +180,23 @@
     }
 
     @Override
+    public Bitmap takeScreenshot(Rect crop, int rotation) {
+        synchronized (mLock) {
+            throwIfCalledByNotTrustedUidLocked();
+            throwIfShutdownLocked();
+            throwIfNotConnectedLocked();
+        }
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            int width = crop.width();
+            int height = crop.height();
+            return SurfaceControl.screenshot(crop, width, height, rotation);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    @Override
     public boolean clearWindowContentFrameStats(int windowId) throws RemoteException {
         synchronized (mLock) {
             throwIfCalledByNotTrustedUidLocked();
@@ -430,8 +449,7 @@
         info.setCapabilities(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT
                 | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
                 | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY
-                | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS
-                | AccessibilityServiceInfo.CAPABILITY_CAN_TAKE_SCREENSHOT);
+                | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS);
         try {
             // Calling out with a lock held is fine since if the system
             // process is gone the client calling in will be killed.
diff --git a/core/java/android/app/admin/DevicePolicyKeyguardService.java b/core/java/android/app/admin/DevicePolicyKeyguardService.java
index c2a76c5..2ac5ebf 100644
--- a/core/java/android/app/admin/DevicePolicyKeyguardService.java
+++ b/core/java/android/app/admin/DevicePolicyKeyguardService.java
@@ -22,7 +22,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
-import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
 
 /**
  * Client interface for providing the SystemUI with secondary lockscreen information.
@@ -43,14 +43,14 @@
         @Override
         public void onSurfaceReady(@Nullable IBinder hostInputToken, IKeyguardCallback callback) {
             mCallback = callback;
-            SurfaceControl surfaceControl =
+            SurfaceControlViewHost.SurfacePackage surfacePackage =
                     DevicePolicyKeyguardService.this.onSurfaceReady(hostInputToken);
 
             if (mCallback != null) {
                 try {
-                    mCallback.onSurfaceControlCreated(surfaceControl);
+                    mCallback.onRemoteContentReady(surfacePackage);
                 } catch (RemoteException e) {
-                    Log.e(TAG, "Failed to return created SurfaceControl", e);
+                    Log.e(TAG, "Failed to return created SurfacePackage", e);
                 }
             }
         }
@@ -65,11 +65,11 @@
     /**
      * Called by keyguard once the host surface for the secondary lockscreen is ready to display
      * remote content.
-     * @return the {@link SurfaceControl} for the Surface the secondary lockscreen content is
-     *      attached to.
+     * @return the {@link SurfaceControlViewHost.SurfacePackage} for the Surface the
+     *      secondary lockscreen content is attached to.
      */
     @Nullable
-    public SurfaceControl onSurfaceReady(@Nullable IBinder hostInputToken) {
+    public SurfaceControlViewHost.SurfacePackage onSurfaceReady(@Nullable IBinder hostInputToken) {
         return null;
     }
 
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 3676a9b..7599791 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2090,6 +2090,13 @@
     public static final int LOCK_TASK_FEATURE_KEYGUARD = 1 << 5;
 
     /**
+     * Enable blocking of non-whitelisted activities from being started into a locked task.
+     *
+     * @see #setLockTaskFeatures(ComponentName, int)
+     */
+    public static final int LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK = 1 << 6;
+
+    /**
      * Flags supplied to {@link #setLockTaskFeatures(ComponentName, int)}.
      *
      * @hide
@@ -2102,7 +2109,8 @@
             LOCK_TASK_FEATURE_HOME,
             LOCK_TASK_FEATURE_OVERVIEW,
             LOCK_TASK_FEATURE_GLOBAL_ACTIONS,
-            LOCK_TASK_FEATURE_KEYGUARD
+            LOCK_TASK_FEATURE_KEYGUARD,
+            LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK
     })
     public @interface LockTaskFeature {}
 
@@ -3722,6 +3730,11 @@
      * requires that you request both {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN} and
      * {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA}}.
      * <p>
+     * When this policy is set by a device owner, profile owner of an organization-owned device or
+     * an admin on the primary user, the device will be factory reset after too many incorrect
+     * password attempts. When set by a profile owner or an admin on a secondary user or a managed
+     * profile, only the corresponding user or profile will be wiped.
+     * <p>
      * To implement any other policy (e.g. wiping data for a particular application only, erasing or
      * revoking credentials, or reporting the failure to a server), you should implement
      * {@link DeviceAdminReceiver#onPasswordFailed(Context, android.content.Intent)} instead. Do not
@@ -3790,10 +3803,12 @@
     }
 
     /**
-     * Returns the profile with the smallest maximum failed passwords for wipe,
-     * for the given user. So for primary user, it might return the primary or
-     * a managed profile. For a secondary user, it would be the same as the
-     * user passed in.
+     * Returns the user that will be wiped first when too many failed attempts are made to unlock
+     * user {@code userHandle}. That user is either the same as {@code userHandle} or belongs to the
+     * same profile group. When there is no such policy, returns {@code UserHandle.USER_NULL}.
+     * E.g. managed profile user may be wiped as a result of failed primary profile password
+     * attempts when using unified challenge. Primary user may be wiped as a result of failed
+     * password attempts on the managed profile on an organization-owned device.
      * @hide Used only by Keyguard
      */
     @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
diff --git a/core/java/android/app/admin/IKeyguardCallback.aidl b/core/java/android/app/admin/IKeyguardCallback.aidl
index 81e7d4d..856033d 100644
--- a/core/java/android/app/admin/IKeyguardCallback.aidl
+++ b/core/java/android/app/admin/IKeyguardCallback.aidl
@@ -15,13 +15,13 @@
  */
 package android.app.admin;
 
-import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
 
 /**
  * Internal IPC interface for informing the keyguard of events on the secondary lockscreen.
  * @hide
  */
 interface IKeyguardCallback {
-    oneway void onSurfaceControlCreated(in SurfaceControl remoteSurfaceControl);
+    oneway void onRemoteContentReady(in SurfaceControlViewHost.SurfacePackage surfacePackage);
     oneway void onDismiss();
 }
diff --git a/core/java/android/app/compat/CompatChanges.java b/core/java/android/app/compat/CompatChanges.java
new file mode 100644
index 0000000..5b36789
--- /dev/null
+++ b/core/java/android/app/compat/CompatChanges.java
@@ -0,0 +1,108 @@
+/*
+ * 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 android.app.compat;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.compat.Compatibility;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+
+import com.android.internal.compat.IPlatformCompat;
+
+/**
+ * CompatChanges APIs - to be used by platform code only (including mainline
+ * modules).
+ *
+ * @hide
+ */
+@SystemApi
+public final class CompatChanges {
+    private CompatChanges() {}
+
+    /**
+     * Query if a given compatibility change is enabled for the current process. This method is
+     * intended to be called by code running inside a process of the affected app only.
+     *
+     * <p>If this method returns {@code true}, the calling code should implement the compatibility
+     * change, resulting in differing behaviour compared to earlier releases. If this method returns
+     * {@code false}, the calling code should behave as it did in earlier releases.
+     *
+     * @param changeId The ID of the compatibility change in question.
+     * @return {@code true} if the change is enabled for the current app.
+     */
+    public static boolean isChangeEnabled(long changeId) {
+        return Compatibility.isChangeEnabled(changeId);
+    }
+
+    /**
+     * Same as {@code #isChangeEnabled(long)}, except this version should be called on behalf of an
+     * app from a different process that's performing work for the app.
+     *
+     * <p> Note that this involves a binder call to the system server (unless running in the system
+     * server). If the binder call fails, a {@code RuntimeException} will be thrown.
+     *
+     * <p> Caller must have android.permission.READ_COMPAT_CHANGE_CONFIG permission. If it
+     * doesn't, a {@code RuntimeException} will be thrown.
+     *
+     * @param changeId    The ID of the compatibility change in question.
+     * @param packageName The package name of the app in question.
+     * @param user        The user that the operation is done for.
+     * @return {@code true} if the change is enabled for the current app.
+     */
+    public static boolean isChangeEnabled(long changeId, @NonNull String packageName,
+            @NonNull UserHandle user) {
+        IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(
+                ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+        try {
+            return platformCompat.isChangeEnabledByPackageName(changeId, packageName,
+                    user.getIdentifier());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Same as {@code #isChangeEnabled(long)}, except this version should be called on behalf of an
+     * app from a different process that's performing work for the app.
+     *
+     * <p> Note that this involves a binder call to the system server (unless running in the system
+     * server). If the binder call fails, {@code RuntimeException}  will be thrown.
+     *
+     * <p> Caller must have android.permission.READ_COMPAT_CHANGE_CONFIG permission. If it
+     * doesn't, a {@code RuntimeException} will be thrown.
+     *
+     * <p> Returns {@code true} if there are no installed packages for the required UID, or if the
+     * change is enabled for ALL of the installed packages associated with the provided UID. Please
+     * use a more specific API if you want a different behaviour for multi-package UIDs.
+     *
+     * @param changeId The ID of the compatibility change in question.
+     * @param uid      The UID of the app in question.
+     * @return {@code true} if the change is enabled for the current app.
+     */
+    public static boolean isChangeEnabled(long changeId, int uid) {
+        IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(
+                ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+        try {
+            return platformCompat.isChangeEnabledByUid(changeId, uid);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+}
diff --git a/core/java/android/app/timedetector/ITimeDetectorService.aidl b/core/java/android/app/timedetector/ITimeDetectorService.aidl
index de8f470..5ead0c9 100644
--- a/core/java/android/app/timedetector/ITimeDetectorService.aidl
+++ b/core/java/android/app/timedetector/ITimeDetectorService.aidl
@@ -18,7 +18,7 @@
 
 import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.NetworkTimeSuggestion;
-import android.app.timedetector.PhoneTimeSuggestion;
+import android.app.timedetector.TelephonyTimeSuggestion;
 
 /**
  * System private API to communicate with time detector service.
@@ -34,7 +34,7 @@
  * {@hide}
  */
 interface ITimeDetectorService {
-  void suggestPhoneTime(in PhoneTimeSuggestion timeSuggestion);
   void suggestManualTime(in ManualTimeSuggestion timeSuggestion);
   void suggestNetworkTime(in NetworkTimeSuggestion timeSuggestion);
+  void suggestTelephonyTime(in TelephonyTimeSuggestion timeSuggestion);
 }
diff --git a/core/java/android/app/timedetector/PhoneTimeSuggestion.aidl b/core/java/android/app/timedetector/TelephonyTimeSuggestion.aidl
similarity index 94%
rename from core/java/android/app/timedetector/PhoneTimeSuggestion.aidl
rename to core/java/android/app/timedetector/TelephonyTimeSuggestion.aidl
index f5e2405..d9b0386 100644
--- a/core/java/android/app/timedetector/PhoneTimeSuggestion.aidl
+++ b/core/java/android/app/timedetector/TelephonyTimeSuggestion.aidl
@@ -16,4 +16,4 @@
 
 package android.app.timedetector;
 
-parcelable PhoneTimeSuggestion;
+parcelable TelephonyTimeSuggestion;
diff --git a/core/java/android/app/timedetector/PhoneTimeSuggestion.java b/core/java/android/app/timedetector/TelephonyTimeSuggestion.java
similarity index 78%
rename from core/java/android/app/timedetector/PhoneTimeSuggestion.java
rename to core/java/android/app/timedetector/TelephonyTimeSuggestion.java
index 16288e8..c0e8957 100644
--- a/core/java/android/app/timedetector/PhoneTimeSuggestion.java
+++ b/core/java/android/app/timedetector/TelephonyTimeSuggestion.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.TimestampedValue;
@@ -51,19 +50,17 @@
  *
  * @hide
  */
-@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
-public final class PhoneTimeSuggestion implements Parcelable {
+public final class TelephonyTimeSuggestion implements Parcelable {
 
     /** @hide */
-    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
-    public static final @NonNull Parcelable.Creator<PhoneTimeSuggestion> CREATOR =
-            new Parcelable.Creator<PhoneTimeSuggestion>() {
-                public PhoneTimeSuggestion createFromParcel(Parcel in) {
-                    return PhoneTimeSuggestion.createFromParcel(in);
+    public static final @NonNull Parcelable.Creator<TelephonyTimeSuggestion> CREATOR =
+            new Parcelable.Creator<TelephonyTimeSuggestion>() {
+                public TelephonyTimeSuggestion createFromParcel(Parcel in) {
+                    return TelephonyTimeSuggestion.createFromParcel(in);
                 }
 
-                public PhoneTimeSuggestion[] newArray(int size) {
-                    return new PhoneTimeSuggestion[size];
+                public TelephonyTimeSuggestion[] newArray(int size) {
+                    return new TelephonyTimeSuggestion[size];
                 }
             };
 
@@ -71,15 +68,15 @@
     @Nullable private final TimestampedValue<Long> mUtcTime;
     @Nullable private ArrayList<String> mDebugInfo;
 
-    private PhoneTimeSuggestion(Builder builder) {
+    private TelephonyTimeSuggestion(Builder builder) {
         mSlotIndex = builder.mSlotIndex;
         mUtcTime = builder.mUtcTime;
         mDebugInfo = builder.mDebugInfo != null ? new ArrayList<>(builder.mDebugInfo) : null;
     }
 
-    private static PhoneTimeSuggestion createFromParcel(Parcel in) {
+    private static TelephonyTimeSuggestion createFromParcel(Parcel in) {
         int slotIndex = in.readInt();
-        PhoneTimeSuggestion suggestion = new PhoneTimeSuggestion.Builder(slotIndex)
+        TelephonyTimeSuggestion suggestion = new TelephonyTimeSuggestion.Builder(slotIndex)
                 .setUtcTime(in.readParcelable(null /* classLoader */))
                 .build();
         @SuppressWarnings("unchecked")
@@ -105,7 +102,7 @@
     /**
      * Returns an identifier for the source of this suggestion.
      *
-     * <p>See {@link PhoneTimeSuggestion} for more information about {@code slotIndex}.
+     * <p>See {@link TelephonyTimeSuggestion} for more information about {@code slotIndex}.
      */
     public int getSlotIndex() {
         return mSlotIndex;
@@ -114,7 +111,7 @@
     /**
      * Returns the suggested time or {@code null} if there isn't one.
      *
-     * <p>See {@link PhoneTimeSuggestion} for more information about {@code utcTime}.
+     * <p>See {@link TelephonyTimeSuggestion} for more information about {@code utcTime}.
      */
     @Nullable
     public TimestampedValue<Long> getUtcTime() {
@@ -124,7 +121,7 @@
     /**
      * Returns debug metadata for the suggestion.
      *
-     * <p>See {@link PhoneTimeSuggestion} for more information about {@code debugInfo}.
+     * <p>See {@link TelephonyTimeSuggestion} for more information about {@code debugInfo}.
      */
     @NonNull
     public List<String> getDebugInfo() {
@@ -135,7 +132,7 @@
     /**
      * Associates information with the instance that can be useful for debugging / logging.
      *
-     * <p>See {@link PhoneTimeSuggestion} for more information about {@code debugInfo}.
+     * <p>See {@link TelephonyTimeSuggestion} for more information about {@code debugInfo}.
      */
     public void addDebugInfo(@NonNull String debugInfo) {
         if (mDebugInfo == null) {
@@ -147,7 +144,7 @@
     /**
      * Associates information with the instance that can be useful for debugging / logging.
      *
-     * <p>See {@link PhoneTimeSuggestion} for more information about {@code debugInfo}.
+     * <p>See {@link TelephonyTimeSuggestion} for more information about {@code debugInfo}.
      */
     public void addDebugInfo(@NonNull List<String> debugInfo) {
         if (mDebugInfo == null) {
@@ -164,7 +161,7 @@
         if (o == null || getClass() != o.getClass()) {
             return false;
         }
-        PhoneTimeSuggestion that = (PhoneTimeSuggestion) o;
+        TelephonyTimeSuggestion that = (TelephonyTimeSuggestion) o;
         return mSlotIndex == that.mSlotIndex
                 && Objects.equals(mUtcTime, that.mUtcTime);
     }
@@ -176,7 +173,7 @@
 
     @Override
     public String toString() {
-        return "PhoneTimeSuggestion{"
+        return "TelephonyTimeSuggestion{"
                 + "mSlotIndex='" + mSlotIndex + '\''
                 + ", mUtcTime=" + mUtcTime
                 + ", mDebugInfo=" + mDebugInfo
@@ -184,11 +181,10 @@
     }
 
     /**
-     * Builds {@link PhoneTimeSuggestion} instances.
+     * Builds {@link TelephonyTimeSuggestion} instances.
      *
      * @hide
      */
-    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final class Builder {
         private final int mSlotIndex;
         @Nullable private TimestampedValue<Long> mUtcTime;
@@ -197,7 +193,7 @@
         /**
          * Creates a builder with the specified {@code slotIndex}.
          *
-         * <p>See {@link PhoneTimeSuggestion} for more information about {@code slotIndex}.
+         * <p>See {@link TelephonyTimeSuggestion} for more information about {@code slotIndex}.
          */
         public Builder(int slotIndex) {
             mSlotIndex = slotIndex;
@@ -206,7 +202,7 @@
         /**
          * Returns the builder for call chaining.
          *
-         * <p>See {@link PhoneTimeSuggestion} for more information about {@code utcTime}.
+         * <p>See {@link TelephonyTimeSuggestion} for more information about {@code utcTime}.
          */
         @NonNull
         public Builder setUtcTime(@Nullable TimestampedValue<Long> utcTime) {
@@ -222,7 +218,7 @@
         /**
          * Returns the builder for call chaining.
          *
-         * <p>See {@link PhoneTimeSuggestion} for more information about {@code debugInfo}.
+         * <p>See {@link TelephonyTimeSuggestion} for more information about {@code debugInfo}.
          */
         @NonNull
         public Builder addDebugInfo(@NonNull String debugInfo) {
@@ -233,10 +229,10 @@
             return this;
         }
 
-        /** Returns the {@link PhoneTimeSuggestion}. */
+        /** Returns the {@link TelephonyTimeSuggestion}. */
         @NonNull
-        public PhoneTimeSuggestion build() {
-            return new PhoneTimeSuggestion(this);
+        public TelephonyTimeSuggestion build() {
+            return new TelephonyTimeSuggestion(this);
         }
     }
 }
diff --git a/core/java/android/app/timedetector/TimeDetector.java b/core/java/android/app/timedetector/TimeDetector.java
index 2412fb3..84ad495 100644
--- a/core/java/android/app/timedetector/TimeDetector.java
+++ b/core/java/android/app/timedetector/TimeDetector.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
-import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.content.Context;
 import android.os.SystemClock;
@@ -29,7 +28,6 @@
  *
  * @hide
  */
-@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
 @SystemService(Context.TIME_DETECTOR_SERVICE)
 public interface TimeDetector {
 
@@ -47,12 +45,12 @@
     }
 
     /**
-     * Suggests the current phone-signal derived time to the detector. The detector may ignore the
-     * signal if better signals are available such as those that come from more reliable sources or
-     * were determined more recently.
+     * Suggests a telephony-signal derived time to the detector. The detector may ignore the signal
+     * if better signals are available such as those that come from more reliable sources or were
+     * determined more recently.
      */
-    @RequiresPermission(android.Manifest.permission.SUGGEST_PHONE_TIME_AND_ZONE)
-    void suggestPhoneTime(@NonNull PhoneTimeSuggestion timeSuggestion);
+    @RequiresPermission(android.Manifest.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE)
+    void suggestTelephonyTime(@NonNull TelephonyTimeSuggestion timeSuggestion);
 
     /**
      * Suggests the user's manually entered current time to the detector.
diff --git a/core/java/android/app/timedetector/TimeDetectorImpl.java b/core/java/android/app/timedetector/TimeDetectorImpl.java
index 1683817..c1d6667 100644
--- a/core/java/android/app/timedetector/TimeDetectorImpl.java
+++ b/core/java/android/app/timedetector/TimeDetectorImpl.java
@@ -40,12 +40,12 @@
     }
 
     @Override
-    public void suggestPhoneTime(@NonNull PhoneTimeSuggestion timeSuggestion) {
+    public void suggestTelephonyTime(@NonNull TelephonyTimeSuggestion timeSuggestion) {
         if (DEBUG) {
-            Log.d(TAG, "suggestPhoneTime called: " + timeSuggestion);
+            Log.d(TAG, "suggestTelephonyTime called: " + timeSuggestion);
         }
         try {
-            mITimeDetectorService.suggestPhoneTime(timeSuggestion);
+            mITimeDetectorService.suggestTelephonyTime(timeSuggestion);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl b/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl
index df643831..b06f4b8 100644
--- a/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl
+++ b/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl
@@ -17,7 +17,7 @@
 package android.app.timezonedetector;
 
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
-import android.app.timezonedetector.PhoneTimeZoneSuggestion;
+import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
 
 /**
  * System private API to communicate with time zone detector service.
@@ -34,5 +34,5 @@
  */
 interface ITimeZoneDetectorService {
   void suggestManualTimeZone(in ManualTimeZoneSuggestion timeZoneSuggestion);
-  void suggestPhoneTimeZone(in PhoneTimeZoneSuggestion timeZoneSuggestion);
+  void suggestTelephonyTimeZone(in TelephonyTimeZoneSuggestion timeZoneSuggestion);
 }
diff --git a/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.aidl b/core/java/android/app/timezonedetector/TelephonyTimeZoneSuggestion.aidl
similarity index 94%
rename from core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.aidl
rename to core/java/android/app/timezonedetector/TelephonyTimeZoneSuggestion.aidl
index 3ad903b..b57ad20 100644
--- a/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.aidl
+++ b/core/java/android/app/timezonedetector/TelephonyTimeZoneSuggestion.aidl
@@ -16,4 +16,4 @@
 
 package android.app.timezonedetector;
 
-parcelable PhoneTimeZoneSuggestion;
+parcelable TelephonyTimeZoneSuggestion;
diff --git a/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java b/core/java/android/app/timezonedetector/TelephonyTimeZoneSuggestion.java
similarity index 83%
rename from core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java
rename to core/java/android/app/timezonedetector/TelephonyTimeZoneSuggestion.java
index 0544ccd..150c01d 100644
--- a/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java
+++ b/core/java/android/app/timezonedetector/TelephonyTimeZoneSuggestion.java
@@ -19,7 +19,6 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -57,20 +56,18 @@
  *
  * @hide
  */
-@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
-public final class PhoneTimeZoneSuggestion implements Parcelable {
+public final class TelephonyTimeZoneSuggestion implements Parcelable {
 
     /** @hide */
-    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     @NonNull
-    public static final Creator<PhoneTimeZoneSuggestion> CREATOR =
-            new Creator<PhoneTimeZoneSuggestion>() {
-                public PhoneTimeZoneSuggestion createFromParcel(Parcel in) {
-                    return PhoneTimeZoneSuggestion.createFromParcel(in);
+    public static final Creator<TelephonyTimeZoneSuggestion> CREATOR =
+            new Creator<TelephonyTimeZoneSuggestion>() {
+                public TelephonyTimeZoneSuggestion createFromParcel(Parcel in) {
+                    return TelephonyTimeZoneSuggestion.createFromParcel(in);
                 }
 
-                public PhoneTimeZoneSuggestion[] newArray(int size) {
-                    return new PhoneTimeZoneSuggestion[size];
+                public TelephonyTimeZoneSuggestion[] newArray(int size) {
+                    return new TelephonyTimeZoneSuggestion[size];
                 }
             };
 
@@ -79,7 +76,7 @@
      * the same {@code slotIndex}.
      */
     @NonNull
-    public static PhoneTimeZoneSuggestion createEmptySuggestion(
+    public static TelephonyTimeZoneSuggestion createEmptySuggestion(
             int slotIndex, @NonNull String debugInfo) {
         return new Builder(slotIndex).addDebugInfo(debugInfo).build();
     }
@@ -147,7 +144,7 @@
     @Quality private final int mQuality;
     @Nullable private List<String> mDebugInfo;
 
-    private PhoneTimeZoneSuggestion(Builder builder) {
+    private TelephonyTimeZoneSuggestion(Builder builder) {
         mSlotIndex = builder.mSlotIndex;
         mZoneId = builder.mZoneId;
         mMatchType = builder.mMatchType;
@@ -156,15 +153,16 @@
     }
 
     @SuppressWarnings("unchecked")
-    private static PhoneTimeZoneSuggestion createFromParcel(Parcel in) {
+    private static TelephonyTimeZoneSuggestion createFromParcel(Parcel in) {
         // Use the Builder so we get validation during build().
         int slotIndex = in.readInt();
-        PhoneTimeZoneSuggestion suggestion = new Builder(slotIndex)
+        TelephonyTimeZoneSuggestion suggestion = new Builder(slotIndex)
                 .setZoneId(in.readString())
                 .setMatchType(in.readInt())
                 .setQuality(in.readInt())
                 .build();
-        List<String> debugInfo = in.readArrayList(PhoneTimeZoneSuggestion.class.getClassLoader());
+        List<String> debugInfo =
+                in.readArrayList(TelephonyTimeZoneSuggestion.class.getClassLoader());
         if (debugInfo != null) {
             suggestion.addDebugInfo(debugInfo);
         }
@@ -188,7 +186,7 @@
     /**
      * Returns an identifier for the source of this suggestion.
      *
-     * <p>See {@link PhoneTimeZoneSuggestion} for more information about {@code slotIndex}.
+     * <p>See {@link TelephonyTimeZoneSuggestion} for more information about {@code slotIndex}.
      */
     public int getSlotIndex() {
         return mSlotIndex;
@@ -198,7 +196,7 @@
      * Returns the suggested time zone Olson ID, e.g. "America/Los_Angeles". {@code null} means that
      * the caller is no longer sure what the current time zone is.
      *
-     * <p>See {@link PhoneTimeZoneSuggestion} for more information about {@code zoneId}.
+     * <p>See {@link TelephonyTimeZoneSuggestion} for more information about {@code zoneId}.
      */
     @Nullable
     public String getZoneId() {
@@ -209,7 +207,7 @@
      * Returns information about how the suggestion was determined which could be used to rank
      * suggestions when several are available from different sources.
      *
-     * <p>See {@link PhoneTimeZoneSuggestion} for more information about {@code matchType}.
+     * <p>See {@link TelephonyTimeZoneSuggestion} for more information about {@code matchType}.
      */
     @MatchType
     public int getMatchType() {
@@ -219,7 +217,7 @@
     /**
      * Returns information about the likelihood of the suggested zone being correct.
      *
-     * <p>See {@link PhoneTimeZoneSuggestion} for more information about {@code quality}.
+     * <p>See {@link TelephonyTimeZoneSuggestion} for more information about {@code quality}.
      */
     @Quality
     public int getQuality() {
@@ -229,7 +227,7 @@
     /**
      * Returns debug metadata for the suggestion.
      *
-     * <p>See {@link PhoneTimeZoneSuggestion} for more information about {@code debugInfo}.
+     * <p>See {@link TelephonyTimeZoneSuggestion} for more information about {@code debugInfo}.
      */
     @NonNull
     public List<String> getDebugInfo() {
@@ -240,7 +238,7 @@
     /**
      * Associates information with the instance that can be useful for debugging / logging.
      *
-     * <p>See {@link PhoneTimeZoneSuggestion} for more information about {@code debugInfo}.
+     * <p>See {@link TelephonyTimeZoneSuggestion} for more information about {@code debugInfo}.
      */
     public void addDebugInfo(@NonNull String debugInfo) {
         if (mDebugInfo == null) {
@@ -252,7 +250,7 @@
     /**
      * Associates information with the instance that can be useful for debugging / logging.
      *
-     * <p>See {@link PhoneTimeZoneSuggestion} for more information about {@code debugInfo}.
+     * <p>See {@link TelephonyTimeZoneSuggestion} for more information about {@code debugInfo}.
      */
     public void addDebugInfo(@NonNull List<String> debugInfo) {
         if (mDebugInfo == null) {
@@ -269,7 +267,7 @@
         if (o == null || getClass() != o.getClass()) {
             return false;
         }
-        PhoneTimeZoneSuggestion that = (PhoneTimeZoneSuggestion) o;
+        TelephonyTimeZoneSuggestion that = (TelephonyTimeZoneSuggestion) o;
         return mSlotIndex == that.mSlotIndex
                 && mMatchType == that.mMatchType
                 && mQuality == that.mQuality
@@ -283,7 +281,7 @@
 
     @Override
     public String toString() {
-        return "PhoneTimeZoneSuggestion{"
+        return "TelephonyTimeZoneSuggestion{"
                 + "mSlotIndex=" + mSlotIndex
                 + ", mZoneId='" + mZoneId + '\''
                 + ", mMatchType=" + mMatchType
@@ -293,11 +291,10 @@
     }
 
     /**
-     * Builds {@link PhoneTimeZoneSuggestion} instances.
+     * Builds {@link TelephonyTimeZoneSuggestion} instances.
      *
      * @hide
      */
-    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final class Builder {
         private final int mSlotIndex;
         @Nullable private String mZoneId;
@@ -308,7 +305,7 @@
         /**
          * Creates a builder with the specified {@code slotIndex}.
          *
-         * <p>See {@link PhoneTimeZoneSuggestion} for more information about {@code slotIndex}.
+         * <p>See {@link TelephonyTimeZoneSuggestion} for more information about {@code slotIndex}.
          */
         public Builder(int slotIndex) {
             mSlotIndex = slotIndex;
@@ -317,7 +314,7 @@
         /**
          * Returns the builder for call chaining.
          *
-         * <p>See {@link PhoneTimeZoneSuggestion} for more information about {@code zoneId}.
+         * <p>See {@link TelephonyTimeZoneSuggestion} for more information about {@code zoneId}.
          */
         @NonNull
         public Builder setZoneId(@Nullable String zoneId) {
@@ -328,7 +325,7 @@
         /**
          * Returns the builder for call chaining.
          *
-         * <p>See {@link PhoneTimeZoneSuggestion} for more information about {@code matchType}.
+         * <p>See {@link TelephonyTimeZoneSuggestion} for more information about {@code matchType}.
          */
         @NonNull
         public Builder setMatchType(@MatchType int matchType) {
@@ -339,7 +336,7 @@
         /**
          * Returns the builder for call chaining.
          *
-         * <p>See {@link PhoneTimeZoneSuggestion} for more information about {@code quality}.
+         * <p>See {@link TelephonyTimeZoneSuggestion} for more information about {@code quality}.
          */
         @NonNull
         public Builder setQuality(@Quality int quality) {
@@ -350,7 +347,7 @@
         /**
          * Returns the builder for call chaining.
          *
-         * <p>See {@link PhoneTimeZoneSuggestion} for more information about {@code debugInfo}.
+         * <p>See {@link TelephonyTimeZoneSuggestion} for more information about {@code debugInfo}.
          */
         @NonNull
         public Builder addDebugInfo(@NonNull String debugInfo) {
@@ -388,11 +385,11 @@
             }
         }
 
-        /** Returns the {@link PhoneTimeZoneSuggestion}. */
+        /** Returns the {@link TelephonyTimeZoneSuggestion}. */
         @NonNull
-        public PhoneTimeZoneSuggestion build() {
+        public TelephonyTimeZoneSuggestion build() {
             validate();
-            return new PhoneTimeZoneSuggestion(this);
+            return new TelephonyTimeZoneSuggestion(this);
         }
     }
 }
diff --git a/core/java/android/app/timezonedetector/TimeZoneDetector.java b/core/java/android/app/timezonedetector/TimeZoneDetector.java
index b4f6087..20761ad 100644
--- a/core/java/android/app/timezonedetector/TimeZoneDetector.java
+++ b/core/java/android/app/timezonedetector/TimeZoneDetector.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
-import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.content.Context;
 
@@ -27,7 +26,6 @@
  *
  * @hide
  */
-@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
 @SystemService(Context.TIME_ZONE_DETECTOR_SERVICE)
 public interface TimeZoneDetector {
 
@@ -49,9 +47,8 @@
      *
      * @hide
      */
-    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
-    @RequiresPermission(android.Manifest.permission.SUGGEST_PHONE_TIME_AND_ZONE)
-    void suggestPhoneTimeZone(@NonNull PhoneTimeZoneSuggestion timeZoneSuggestion);
+    @RequiresPermission(android.Manifest.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE)
+    void suggestTelephonyTimeZone(@NonNull TelephonyTimeZoneSuggestion timeZoneSuggestion);
 
     /**
      * Suggests the current time zone, determined for the user's manually information, to the
diff --git a/core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java b/core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java
index 27b8374..0ada885 100644
--- a/core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java
+++ b/core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java
@@ -40,12 +40,12 @@
     }
 
     @Override
-    public void suggestPhoneTimeZone(@NonNull PhoneTimeZoneSuggestion timeZoneSuggestion) {
+    public void suggestTelephonyTimeZone(@NonNull TelephonyTimeZoneSuggestion timeZoneSuggestion) {
         if (DEBUG) {
-            Log.d(TAG, "suggestPhoneTimeZone called: " + timeZoneSuggestion);
+            Log.d(TAG, "suggestTelephonyTimeZone called: " + timeZoneSuggestion);
         }
         try {
-            mITimeZoneDetectorService.suggestPhoneTimeZone(timeZoneSuggestion);
+            mITimeZoneDetectorService.suggestTelephonyTimeZone(timeZoneSuggestion);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 2e93d43..01ccb86 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -1861,15 +1861,19 @@
     }
 
     /**
-     * Connects all enabled and supported bluetooth profiles between the local and remote device
+     * Connects all enabled and supported bluetooth profiles between the local and remote device.
+     * Connection is asynchronous and you should listen to each profile's broadcast intent
+     * ACTION_CONNECTION_STATE_CHANGED to verify whether connection was successful. For example,
+     * to verify a2dp is connected, you would listen for
+     * {@link BluetoothA2dp#ACTION_CONNECTION_STATE_CHANGED}
      *
      * @param device is the remote device with which to connect these profiles
-     * @return true if all profiles successfully connected, false if an error occurred
+     * @return true if message sent to try to connect all profiles, false if an error occurred
      *
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
     public boolean connectAllEnabledProfiles(@NonNull BluetoothDevice device) {
         try {
             mServiceLock.readLock().lock();
@@ -1886,15 +1890,19 @@
     }
 
     /**
-     * Disconnects all enabled and supported bluetooth profiles between the local and remote device
+     * Disconnects all enabled and supported bluetooth profiles between the local and remote device.
+     * Disconnection is asynchronous and you should listen to each profile's broadcast intent
+     * ACTION_CONNECTION_STATE_CHANGED to verify whether disconnection was successful. For example,
+     * to verify a2dp is disconnected, you would listen for
+     * {@link BluetoothA2dp#ACTION_CONNECTION_STATE_CHANGED}
      *
      * @param device is the remote device with which to disconnect these profiles
-     * @return true if all profiles successfully disconnected, false if an error occurred
+     * @return true if message sent to try to disconnect all profiles, false if an error occurred
      *
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
     public boolean disconnectAllEnabledProfiles(@NonNull BluetoothDevice device) {
         try {
             mServiceLock.readLock().lock();
diff --git a/core/java/android/content/integrity/AtomicFormula.java b/core/java/android/content/integrity/AtomicFormula.java
index 439d536..d25f413 100644
--- a/core/java/android/content/integrity/AtomicFormula.java
+++ b/core/java/android/content/integrity/AtomicFormula.java
@@ -332,9 +332,12 @@
          * Constructs a new {@link StringAtomicFormula} together with handling the necessary
          * hashing for the given key.
          *
-         * <p> The value will be hashed with SHA256 and the hex digest will be computed; for
-         * all cases except when the key is PACKAGE_NAME or INSTALLER_NAME and the value
-         * is less than 33 characters.
+         * <p> The value will be automatically hashed with SHA256 and the hex digest will be
+         * computed when the key is PACKAGE_NAME or INSTALLER_NAME and the value is more than 32
+         * characters.
+         *
+         * <p> The APP_CERTIFICATES and INSTALLER_CERTIFICATES are always delivered in hashed
+         * form. So the isHashedValue is set to true by default.
          *
          * @throws IllegalArgumentException if {@code key} cannot be used with string value.
          */
@@ -348,7 +351,10 @@
                     String.format(
                             "Key %s cannot be used with StringAtomicFormula", keyToString(key)));
             mValue = hashValue(key, value);
-            mIsHashedValue = !mValue.equals(value);
+            mIsHashedValue =
+                    key == APP_CERTIFICATE || key == INSTALLER_CERTIFICATE
+                            ? true
+                            : !mValue.equals(value);
         }
 
         StringAtomicFormula(Parcel in) {
diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java
index de153d0..edc20d9 100644
--- a/core/java/android/content/pm/CrossProfileApps.java
+++ b/core/java/android/content/pm/CrossProfileApps.java
@@ -323,6 +323,7 @@
      */
     @RequiresPermission(
             allOf={android.Manifest.permission.MANAGE_APP_OPS_MODES,
+                    android.Manifest.permission.UPDATE_APP_OPS_STATS,
                     android.Manifest.permission.INTERACT_ACROSS_USERS})
     public void setInteractAcrossProfilesAppOp(@NonNull String packageName, @Mode int newMode) {
         try {
@@ -363,6 +364,7 @@
      */
     @RequiresPermission(
             allOf={android.Manifest.permission.MANAGE_APP_OPS_MODES,
+                    android.Manifest.permission.UPDATE_APP_OPS_STATS,
                     android.Manifest.permission.INTERACT_ACROSS_USERS})
     public void resetInteractAcrossProfilesAppOps(
             @NonNull Collection<String> previousCrossProfilePackages,
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index 50bb3c7..0492359 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -20,11 +20,13 @@
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.IntentSender;
+import android.content.LocusId;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IOnAppsChangedListener;
 import android.content.pm.LauncherApps;
 import android.content.pm.IPackageInstallerCallback;
+import android.content.pm.IShortcutChangeCallback;
 import android.content.pm.PackageInstaller;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ResolveInfo;
@@ -66,7 +68,8 @@
             in UserHandle user);
 
     ParceledListSlice getShortcuts(String callingPackage, long changedSince, String packageName,
-            in List shortcutIds, in ComponentName componentName, int flags, in UserHandle user);
+            in List shortcutIds, in List<LocusId> locusIds, in ComponentName componentName,
+            int flags, in UserHandle user);
     void pinShortcuts(String callingPackage, String packageName, in List<String> shortcutIds,
             in UserHandle user);
     boolean startShortcut(String callingPackage, String packageName, String id,
@@ -89,4 +92,10 @@
     void registerPackageInstallerCallback(String callingPackage,
             in IPackageInstallerCallback callback);
     ParceledListSlice getAllSessions(String callingPackage);
+
+    void registerShortcutChangeCallback(String callingPackage, long changedSince,
+            String packageName, in List shortcutIds, in List<LocusId> locusIds,
+            in ComponentName componentName, int flags, in IShortcutChangeCallback callback,
+            int callbackId);
+    void unregisterShortcutChangeCallback(String callingPackage, int callbackId);
 }
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 93126b8..6552d1b 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -679,9 +679,9 @@
 
     boolean hasUidSigningCertificate(int uid, in byte[] signingCertificate, int flags);
 
-    String getSystemTextClassifierPackageName();
+    String getDefaultTextClassifierPackageName();
 
-    String[] getSystemTextClassifierPackages();
+    String getSystemTextClassifierPackageName();
 
     String getAttentionServicePackageName();
 
diff --git a/core/java/android/content/pm/IShortcutChangeCallback.aidl b/core/java/android/content/pm/IShortcutChangeCallback.aidl
new file mode 100644
index 0000000..fed4e4a
--- /dev/null
+++ b/core/java/android/content/pm/IShortcutChangeCallback.aidl
@@ -0,0 +1,37 @@
+/**
+ * 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 android.content.pm;
+
+import android.content.pm.ParceledListSlice;
+import android.content.pm.ShortcutInfo;
+import android.os.UserHandle;
+
+import java.util.List;
+
+/**
+ * Interface for LauncherApps#ShortcutChangeCallbackProxy.
+ *
+ * @hide
+ */
+oneway interface IShortcutChangeCallback
+{
+    void onShortcutsAddedOrUpdated(String packageName, in List<ShortcutInfo> shortcuts,
+            in UserHandle user);
+
+    void onShortcutsRemoved(String packageName, in List<ShortcutInfo> shortcuts,
+            in UserHandle user);
+}
\ No newline at end of file
diff --git a/core/java/android/content/pm/IShortcutService.aidl b/core/java/android/content/pm/IShortcutService.aidl
index 747e929..9e85fc3 100644
--- a/core/java/android/content/pm/IShortcutService.aidl
+++ b/core/java/android/content/pm/IShortcutService.aidl
@@ -29,10 +29,6 @@
     boolean setDynamicShortcuts(String packageName, in ParceledListSlice shortcutInfoList,
             int userId);
 
-    ParceledListSlice getDynamicShortcuts(String packageName, int userId);
-
-    ParceledListSlice getManifestShortcuts(String packageName, int userId);
-
     boolean addDynamicShortcuts(String packageName, in ParceledListSlice shortcutInfoList,
             int userId);
 
@@ -40,8 +36,6 @@
 
     void removeAllDynamicShortcuts(String packageName, int userId);
 
-    ParceledListSlice getPinnedShortcuts(String packageName, int userId);
-
     boolean updateShortcuts(String packageName, in ParceledListSlice shortcuts, int userId);
 
     boolean requestPinShortcut(String packageName, in ShortcutInfo shortcut,
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index cea0b6b..73c9e4d 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -34,6 +34,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentSender;
+import android.content.LocusId;
 import android.content.pm.PackageInstaller.SessionCallback;
 import android.content.pm.PackageInstaller.SessionCallbackDelegate;
 import android.content.pm.PackageInstaller.SessionInfo;
@@ -61,15 +62,21 @@
 import android.os.UserManager;
 import android.util.DisplayMetrics;
 import android.util.Log;
+import android.util.Pair;
+
+import com.android.internal.util.function.pooled.PooledLambda;
 
 import java.io.IOException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 
@@ -152,6 +159,9 @@
     private final List<CallbackMessageHandler> mCallbacks = new ArrayList<>();
     private final List<SessionCallbackDelegate> mDelegates = new ArrayList<>();
 
+    private final Map<Integer, Pair<Executor, ShortcutChangeCallback>>
+            mShortcutChangeCallbacks = new HashMap<>();
+
     /**
      * Callbacks for package changes to this and related managed profiles.
      */
@@ -406,6 +416,9 @@
         List<String> mShortcutIds;
 
         @Nullable
+        List<LocusId> mLocusIds;
+
+        @Nullable
         ComponentName mActivity;
 
         @QueryFlags
@@ -442,6 +455,19 @@
         }
 
         /**
+         * If non-null, return only the specified shortcuts by locus ID.  When setting this field,
+         * a package name must also be set with {@link #setPackage}.
+         *
+         * @hide
+         */
+        @SystemApi
+        @NonNull
+        public ShortcutQuery setLocusIds(@Nullable List<LocusId> locusIds) {
+            mLocusIds = locusIds;
+            return this;
+        }
+
+        /**
          * If non-null, returns only shortcuts associated with the activity; i.e.
          * {@link ShortcutInfo}s whose {@link ShortcutInfo#getActivity()} are equal
          * to {@code activity}.
@@ -469,6 +495,95 @@
         }
     }
 
+    /**
+     * Callbacks for shortcut changes to this and related managed profiles.
+     *
+     * @hide
+     */
+    public interface ShortcutChangeCallback {
+        /**
+         * Indicates that one or more shortcuts, that match the {@link ShortcutQuery} used to
+         * register this callback, have been added or updated.
+         * @see LauncherApps#registerShortcutChangeCallback(ShortcutChangeCallback, ShortcutQuery)
+         *
+         * <p>Only the applications that are allowed to access the shortcut information,
+         * as defined in {@link #hasShortcutHostPermission()}, will receive it.
+         *
+         * @param packageName The name of the package that has the shortcuts.
+         * @param shortcuts Shortcuts from the package that have updated or added. Only "key"
+         *    information will be provided, as defined in {@link ShortcutInfo#hasKeyFieldsOnly()}.
+         * @param user The UserHandle of the profile that generated the change.
+         *
+         * @see ShortcutManager
+         */
+        default void onShortcutsAddedOrUpdated(@NonNull String packageName,
+                @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {}
+
+        /**
+         * Indicates that one or more shortcuts, that match the {@link ShortcutQuery} used to
+         * register this callback, have been removed.
+         * @see LauncherApps#registerShortcutChangeCallback(ShortcutChangeCallback, ShortcutQuery)
+         *
+         * <p>Only the applications that are allowed to access the shortcut information,
+         * as defined in {@link #hasShortcutHostPermission()}, will receive it.
+         *
+         * @param packageName The name of the package that has the shortcuts.
+         * @param shortcuts Shortcuts from the package that have been removed. Only "key"
+         *    information will be provided, as defined in {@link ShortcutInfo#hasKeyFieldsOnly()}.
+         * @param user The UserHandle of the profile that generated the change.
+         *
+         * @see ShortcutManager
+         */
+        default void onShortcutsRemoved(@NonNull String packageName,
+                @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {}
+    }
+
+    /**
+     * Callback proxy class for {@link ShortcutChangeCallback}
+     *
+     * @hide
+     */
+    private static class ShortcutChangeCallbackProxy extends
+            android.content.pm.IShortcutChangeCallback.Stub {
+        private final WeakReference<Pair<Executor, ShortcutChangeCallback>> mRemoteReferences;
+
+        ShortcutChangeCallbackProxy(Pair<Executor, ShortcutChangeCallback> remoteReferences) {
+            mRemoteReferences = new WeakReference<>(remoteReferences);
+        }
+
+        @Override
+        public void onShortcutsAddedOrUpdated(@NonNull String packageName,
+                @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {
+            Pair<Executor, ShortcutChangeCallback> remoteReferences = mRemoteReferences.get();
+            if (remoteReferences == null) {
+                // Binder is dead.
+                return;
+            }
+
+            final Executor executor = remoteReferences.first;
+            final ShortcutChangeCallback callback = remoteReferences.second;
+            executor.execute(
+                    PooledLambda.obtainRunnable(ShortcutChangeCallback::onShortcutsAddedOrUpdated,
+                            callback, packageName, shortcuts, user).recycleOnUse());
+        }
+
+        @Override
+        public void onShortcutsRemoved(@NonNull String packageName,
+                @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {
+            Pair<Executor, ShortcutChangeCallback> remoteReferences = mRemoteReferences.get();
+            if (remoteReferences == null) {
+                // Binder is dead.
+                return;
+            }
+
+            final Executor executor = remoteReferences.first;
+            final ShortcutChangeCallback callback = remoteReferences.second;
+            executor.execute(
+                    PooledLambda.obtainRunnable(ShortcutChangeCallback::onShortcutsRemoved,
+                            callback, packageName, shortcuts, user).recycleOnUse());
+        }
+    }
+
     /** @hide */
     public LauncherApps(Context context, ILauncherApps service) {
         mContext = context;
@@ -924,8 +1039,8 @@
             // changed callback, but that only returns shortcuts with the "key" information, so
             // that won't return disabled message.
             return maybeUpdateDisabledMessage(mService.getShortcuts(mContext.getPackageName(),
-                    query.mChangedSince, query.mPackage, query.mShortcutIds, query.mActivity,
-                    query.mQueryFlags, user)
+                    query.mChangedSince, query.mPackage, query.mShortcutIds, query.mLocusIds,
+                    query.mActivity, query.mQueryFlags, user)
                     .getList());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -1560,6 +1675,63 @@
     }
 
     /**
+     * Register a callback to watch for shortcut change events in this user and managed profiles.
+     *
+     * @param callback The callback to register.
+     * @param query {@link ShortcutQuery} to match and filter the shortcut events. Only matching
+     * shortcuts will be returned by the callback.
+     * @param executor {@link Executor} to handle the callbacks. To dispatch callbacks to the main
+     * thread of your application, you can use {@link android.content.Context#getMainExecutor()}.
+     *
+     * @hide
+     */
+    public void registerShortcutChangeCallback(@NonNull ShortcutChangeCallback callback,
+            @NonNull ShortcutQuery query, @NonNull @CallbackExecutor Executor executor) {
+        Objects.requireNonNull(callback, "Callback cannot be null");
+        Objects.requireNonNull(query, "Query cannot be null");
+        Objects.requireNonNull(executor, "Executor cannot be null");
+
+        synchronized (mShortcutChangeCallbacks) {
+            final int callbackId = callback.hashCode();
+            final Pair<Executor, ShortcutChangeCallback> state = new Pair<>(executor, callback);
+            mShortcutChangeCallbacks.put(callbackId, state);
+            try {
+                mService.registerShortcutChangeCallback(mContext.getPackageName(),
+                        query.mChangedSince, query.mPackage, query.mShortcutIds, query.mLocusIds,
+                        query.mActivity, query.mQueryFlags, new ShortcutChangeCallbackProxy(state),
+                        callbackId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Unregisters a callback that was previously registered.
+     * @see #registerShortcutChangeCallback(ShortcutChangeCallback, ShortcutQuery, Executor)
+     *
+     * @param callback Callback to be unregistered.
+     *
+     * @hide
+     */
+    public void unregisterShortcutChangeCallback(@NonNull ShortcutChangeCallback callback) {
+        Objects.requireNonNull(callback, "Callback cannot be null");
+
+        synchronized (mShortcutChangeCallbacks) {
+            final int callbackId = callback.hashCode();
+            if (mShortcutChangeCallbacks.containsKey(callbackId)) {
+                mShortcutChangeCallbacks.remove(callbackId);
+                try {
+                    mService.unregisterShortcutChangeCallback(mContext.getPackageName(),
+                            callbackId);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+        }
+    }
+
+    /**
      * A helper method to extract a {@link PinItemRequest} set to
      * the {@link #EXTRA_PIN_ITEM_REQUEST} extra.
      */
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 6d5e8fb..5a0bcf0 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -3354,6 +3354,22 @@
     public static final int FLAG_PERMISSION_ONE_TIME = 1 << 16;
 
     /**
+     * Permission flag: The permission is whitelisted to not be auto-revoked when app goes unused.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int FLAG_PERMISSION_DONT_AUTO_REVOKE = 1 << 17;
+
+    /**
+     * Permission flag: Whether {@link #FLAG_PERMISSION_DONT_AUTO_REVOKE} state was set by user.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int FLAG_PERMISSION_DONT_AUTO_REVOKE_USER_SET = 1 << 18;
+
+    /**
      * Permission flags: Reserved for use by the permission controller.
      *
      * @hide
@@ -3404,7 +3420,9 @@
             | FLAG_PERMISSION_APPLY_RESTRICTION
             | FLAG_PERMISSION_GRANTED_BY_ROLE
             | FLAG_PERMISSION_REVOKED_COMPAT
-            | FLAG_PERMISSION_ONE_TIME;
+            | FLAG_PERMISSION_ONE_TIME
+            | FLAG_PERMISSION_DONT_AUTO_REVOKE
+            | FLAG_PERMISSION_DONT_AUTO_REVOKE_USER_SET;
 
     /**
      * Injected activity in app that forwards user to setting activity of that app.
@@ -4227,7 +4245,8 @@
             FLAG_PERMISSION_APPLY_RESTRICTION,
             FLAG_PERMISSION_GRANTED_BY_ROLE,
             FLAG_PERMISSION_REVOKED_COMPAT,
-            FLAG_PERMISSION_ONE_TIME
+            FLAG_PERMISSION_ONE_TIME,
+            FLAG_PERMISSION_DONT_AUTO_REVOKE
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface PermissionFlags {}
@@ -7364,6 +7383,8 @@
             case FLAG_PERMISSION_GRANTED_BY_ROLE: return "GRANTED_BY_ROLE";
             case FLAG_PERMISSION_REVOKED_COMPAT: return "REVOKED_COMPAT";
             case FLAG_PERMISSION_ONE_TIME: return "ONE_TIME";
+            case FLAG_PERMISSION_DONT_AUTO_REVOKE: return "DONT_AUTO_REVOKE";
+            case FLAG_PERMISSION_DONT_AUTO_REVOKE_USER_SET: return "DONT_AUTO_REVOKE_USER_SET";
             default: return Integer.toString(flag);
         }
     }
@@ -7602,14 +7623,15 @@
     }
 
     /**
-     * @return the system defined text classifier package name, or null if there's none.
+     * @return the default text classifier package name, or null if there's none.
      *
      * @hide
      */
     @Nullable
-    public String getSystemTextClassifierPackageName() {
+    @TestApi
+    public String getDefaultTextClassifierPackageName() {
         throw new UnsupportedOperationException(
-                "getSystemTextClassifierPackageName not implemented in subclass");
+                "getDefaultTextClassifierPackageName not implemented in subclass");
     }
 
     /**
@@ -7617,10 +7639,11 @@
      *
      * @hide
      */
-    @NonNull
-    public String[] getSystemTextClassifierPackages() {
+    @Nullable
+    @TestApi
+    public String getSystemTextClassifierPackageName() {
         throw new UnsupportedOperationException(
-                "getSystemTextClassifierPackages not implemented in subclass");
+                "getSystemTextClassifierPackageName not implemented in subclass");
     }
 
     /**
diff --git a/core/java/android/content/pm/ShortcutServiceInternal.java b/core/java/android/content/pm/ShortcutServiceInternal.java
index e6f682d..a11a1dd 100644
--- a/core/java/android/content/pm/ShortcutServiceInternal.java
+++ b/core/java/android/content/pm/ShortcutServiceInternal.java
@@ -23,6 +23,7 @@
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.IntentSender;
+import android.content.LocusId;
 import android.content.pm.LauncherApps.ShortcutQuery;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
@@ -45,8 +46,8 @@
             getShortcuts(int launcherUserId,
             @NonNull String callingPackage, long changedSince,
             @Nullable String packageName, @Nullable List<String> shortcutIds,
-            @Nullable ComponentName componentName, @ShortcutQuery.QueryFlags int flags,
-            int userId, int callingPid, int callingUid);
+            @Nullable List<LocusId> locusIds, @Nullable ComponentName componentName,
+            @ShortcutQuery.QueryFlags int flags, int userId, int callingPid, int callingUid);
 
     public abstract boolean
             isPinnedByCaller(int launcherUserId, @NonNull String callingPackage,
diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING
index 0549c34..6f30ecd 100644
--- a/core/java/android/content/pm/TEST_MAPPING
+++ b/core/java/android/content/pm/TEST_MAPPING
@@ -15,5 +15,15 @@
       "name": "FrameworksInstantAppResolverTests",
       "file_patterns": ["(/|^)InstantApp[^/]*"]
     }
+  ],
+  "postsubmit": [
+    {
+      "name": "CtsAppSecurityHostTestCases",
+      "options": [
+        {
+          "include-filter": "android.appsecurity.cts.AppSecurityTests#testPermissionDiffCert"
+        }
+      ]
+    }
   ]
 }
diff --git a/core/java/android/hardware/biometrics/BiometricNativeHandleUtils.java b/core/java/android/hardware/biometrics/BiometricNativeHandleUtils.java
new file mode 100644
index 0000000..5544eae
--- /dev/null
+++ b/core/java/android/hardware/biometrics/BiometricNativeHandleUtils.java
@@ -0,0 +1,79 @@
+/*
+ * 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 android.hardware.biometrics;
+
+import android.os.NativeHandle;
+import android.os.ParcelFileDescriptor;
+
+import java.io.IOException;
+
+/**
+ * A class that contains utilities for IBiometricNativeHandle.
+ *
+ * @hide
+ */
+public final class BiometricNativeHandleUtils {
+
+    private BiometricNativeHandleUtils() {
+    }
+
+    /**
+     * Converts a {@link NativeHandle} into an {@link IBiometricNativeHandle} by duplicating the
+     * underlying file descriptors.
+     *
+     * Both the original and new handle must be closed after use.
+     *
+     * @param h {@link NativeHandle}. Usually used to identify a WindowManager window. Can be null.
+     * @return A {@link IBiometricNativeHandle} representation of {@code h}. Will be null if
+     * {@code h} or its raw file descriptors are null.
+     */
+    public static IBiometricNativeHandle dup(NativeHandle h) {
+        IBiometricNativeHandle handle = null;
+        if (h != null && h.getFileDescriptors() != null && h.getInts() != null) {
+            handle = new IBiometricNativeHandle();
+            handle.ints = h.getInts().clone();
+            handle.fds = new ParcelFileDescriptor[h.getFileDescriptors().length];
+            for (int i = 0; i < h.getFileDescriptors().length; ++i) {
+                try {
+                    handle.fds[i] = ParcelFileDescriptor.dup(h.getFileDescriptors()[i]);
+                } catch (IOException e) {
+                    return null;
+                }
+            }
+        }
+        return handle;
+    }
+
+    /**
+     * Closes the handle's file descriptors.
+     *
+     * @param h {@link IBiometricNativeHandle} handle.
+     */
+    public static void close(IBiometricNativeHandle h) {
+        if (h != null) {
+            for (ParcelFileDescriptor fd : h.fds) {
+                if (fd != null) {
+                    try {
+                        fd.close();
+                    } catch (IOException e) {
+                        // do nothing.
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.aidl b/core/java/android/hardware/biometrics/IBiometricNativeHandle.aidl
similarity index 64%
copy from core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.aidl
copy to core/java/android/hardware/biometrics/IBiometricNativeHandle.aidl
index 3ad903b..6dcdc1b 100644
--- a/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricNativeHandle.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,7 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package android.hardware.biometrics;
 
-package android.app.timezonedetector;
-
-parcelable PhoneTimeZoneSuggestion;
+/**
+ * Representation of a native handle.
+ * Copied from /common/aidl/android/hardware/common/NativeHandle.aidl
+ * @hide
+ */
+parcelable IBiometricNativeHandle {
+    ParcelFileDescriptor[] fds;
+    int[] ints;
+}
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 55ebe28..6bda46b 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -29,7 +29,9 @@
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricFaceConstants;
+import android.hardware.biometrics.BiometricNativeHandleUtils;
 import android.hardware.biometrics.CryptoObject;
+import android.hardware.biometrics.IBiometricNativeHandle;
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
 import android.os.Binder;
 import android.os.CancellationSignal;
@@ -38,6 +40,7 @@
 import android.os.IBinder;
 import android.os.IRemoteCallback;
 import android.os.Looper;
+import android.os.NativeHandle;
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.Trace;
@@ -245,6 +248,19 @@
     }
 
     /**
+     * Defaults to {@link FaceManager#enroll(int, byte[], CancellationSignal, EnrollmentCallback,
+     * int[], NativeHandle)} with {@code windowId} set to null.
+     *
+     * @see FaceManager#enroll(int, byte[], CancellationSignal, EnrollmentCallback, int[],
+     * NativeHandle)
+     */
+    @RequiresPermission(MANAGE_BIOMETRIC)
+    public void enroll(int userId, byte[] token, CancellationSignal cancel,
+            EnrollmentCallback callback, int[] disabledFeatures) {
+        enroll(userId, token, cancel, callback, disabledFeatures, null /* windowId */);
+    }
+
+    /**
      * Request face authentication enrollment. This call operates the face authentication hardware
      * and starts capturing images. Progress will be indicated by callbacks to the
      * {@link EnrollmentCallback} object. It terminates when
@@ -259,11 +275,13 @@
      * @param flags    optional flags
      * @param userId   the user to whom this face will belong to
      * @param callback an object to receive enrollment events
+     * @param windowId optional ID of a camera preview window for a single-camera device. Must be
+     *                 null if not used.
      * @hide
      */
     @RequiresPermission(MANAGE_BIOMETRIC)
     public void enroll(int userId, byte[] token, CancellationSignal cancel,
-            EnrollmentCallback callback, int[] disabledFeatures) {
+            EnrollmentCallback callback, int[] disabledFeatures, @Nullable NativeHandle windowId) {
         if (callback == null) {
             throw new IllegalArgumentException("Must supply an enrollment callback");
         }
@@ -278,20 +296,72 @@
         }
 
         if (mService != null) {
+            IBiometricNativeHandle handle = BiometricNativeHandleUtils.dup(windowId);
             try {
                 mEnrollmentCallback = callback;
                 Trace.beginSection("FaceManager#enroll");
                 mService.enroll(userId, mToken, token, mServiceReceiver,
-                        mContext.getOpPackageName(), disabledFeatures);
+                        mContext.getOpPackageName(), disabledFeatures, handle);
             } catch (RemoteException e) {
                 Log.w(TAG, "Remote exception in enroll: ", e);
-                if (callback != null) {
-                    // Though this may not be a hardware issue, it will cause apps to give up or
-                    // try again later.
-                    callback.onEnrollmentError(FACE_ERROR_HW_UNAVAILABLE,
-                            getErrorString(mContext, FACE_ERROR_HW_UNAVAILABLE,
+                // Though this may not be a hardware issue, it will cause apps to give up or
+                // try again later.
+                callback.onEnrollmentError(FACE_ERROR_HW_UNAVAILABLE,
+                        getErrorString(mContext, FACE_ERROR_HW_UNAVAILABLE,
                                 0 /* vendorCode */));
-                }
+            } finally {
+                Trace.endSection();
+                BiometricNativeHandleUtils.close(handle);
+            }
+        }
+    }
+
+    /**
+     * Request face authentication enrollment for a remote client, for example Android Auto.
+     * This call operates the face authentication hardware and starts capturing images.
+     * Progress will be indicated by callbacks to the
+     * {@link EnrollmentCallback} object. It terminates when
+     * {@link EnrollmentCallback#onEnrollmentError(int, CharSequence)} or
+     * {@link EnrollmentCallback#onEnrollmentProgress(int) is called with remaining == 0, at
+     * which point the object is no longer valid. The operation can be canceled by using the
+     * provided cancel object.
+     *
+     * @param token    a unique token provided by a recent creation or verification of device
+     *                 credentials (e.g. pin, pattern or password).
+     * @param cancel   an object that can be used to cancel enrollment
+     * @param userId   the user to whom this face will belong to
+     * @param callback an object to receive enrollment events
+     * @hide
+     */
+    @RequiresPermission(MANAGE_BIOMETRIC)
+    public void enrollRemotely(int userId, byte[] token, CancellationSignal cancel,
+            EnrollmentCallback callback, int[] disabledFeatures) {
+        if (callback == null) {
+            throw new IllegalArgumentException("Must supply an enrollment callback");
+        }
+
+        if (cancel != null) {
+            if (cancel.isCanceled()) {
+                Log.w(TAG, "enrollRemotely is already canceled.");
+                return;
+            } else {
+                cancel.setOnCancelListener(new OnEnrollCancelListener());
+            }
+        }
+
+        if (mService != null) {
+            try {
+                mEnrollmentCallback = callback;
+                Trace.beginSection("FaceManager#enrollRemotely");
+                mService.enrollRemotely(userId, mToken, token, mServiceReceiver,
+                        mContext.getOpPackageName(), disabledFeatures);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Remote exception in enrollRemotely: ", e);
+                // Though this may not be a hardware issue, it will cause apps to give up or
+                // try again later.
+                callback.onEnrollmentError(FACE_ERROR_HW_UNAVAILABLE,
+                        getErrorString(mContext, FACE_ERROR_HW_UNAVAILABLE,
+                                0 /* vendorCode */));
             } finally {
                 Trace.endSection();
             }
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index 68a4aef..8ba2473 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -15,6 +15,7 @@
  */
 package android.hardware.face;
 
+import android.hardware.biometrics.IBiometricNativeHandle;
 import android.hardware.biometrics.IBiometricServiceReceiverInternal;
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
 import android.hardware.face.IFaceServiceReceiver;
@@ -51,6 +52,10 @@
 
     // Start face enrollment
     void enroll(int userId, IBinder token, in byte [] cryptoToken, IFaceServiceReceiver receiver,
+            String opPackageName, in int [] disabledFeatures, in IBiometricNativeHandle windowId);
+
+    // Start remote face enrollment
+    void enrollRemotely(int userId, IBinder token, in byte [] cryptoToken, IFaceServiceReceiver receiver,
             String opPackageName, in int [] disabledFeatures);
 
     // Cancel enrollment in progress
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index ff9d145..f6717c7 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -32,7 +32,9 @@
 import android.content.pm.PackageManager;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricFingerprintConstants;
+import android.hardware.biometrics.BiometricNativeHandleUtils;
 import android.hardware.biometrics.BiometricPrompt;
+import android.hardware.biometrics.IBiometricNativeHandle;
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
 import android.os.Binder;
 import android.os.CancellationSignal;
@@ -41,6 +43,7 @@
 import android.os.IBinder;
 import android.os.IRemoteCallback;
 import android.os.Looper;
+import android.os.NativeHandle;
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -403,15 +406,33 @@
     }
 
     /**
-     * Per-user version, see {@link FingerprintManager#authenticate(CryptoObject,
-     * CancellationSignal, int, AuthenticationCallback, Handler)}. This version does not
-     * display the BiometricPrompt.
-     * @param userId the user ID that the fingerprint hardware will authenticate for.
+     * Defaults to {@link FingerprintManager#authenticate(CryptoObject, CancellationSignal, int,
+     * AuthenticationCallback, Handler, int, NativeHandle)} with {@code windowId} set to null.
+     *
+     * @see FingerprintManager#authenticate(CryptoObject, CancellationSignal, int,
+     * AuthenticationCallback, Handler, int, NativeHandle)
+     *
      * @hide
      */
     @RequiresPermission(anyOf = {USE_BIOMETRIC, USE_FINGERPRINT})
     public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel,
             int flags, @NonNull AuthenticationCallback callback, Handler handler, int userId) {
+        authenticate(crypto, cancel, flags, callback, handler, userId, null /* windowId */);
+    }
+
+    /**
+     * Per-user version, see {@link FingerprintManager#authenticate(CryptoObject,
+     * CancellationSignal, int, AuthenticationCallback, Handler)}. This version does not
+     * display the BiometricPrompt.
+     * @param userId the user ID that the fingerprint hardware will authenticate for.
+     * @param windowId for optical fingerprint sensors that require active illumination by the OLED
+     *        display. Should be null for devices that don't require illumination.
+     * @hide
+     */
+    @RequiresPermission(anyOf = {USE_BIOMETRIC, USE_FINGERPRINT})
+    public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel,
+            int flags, @NonNull AuthenticationCallback callback, Handler handler, int userId,
+            @Nullable NativeHandle windowId) {
         if (callback == null) {
             throw new IllegalArgumentException("Must supply an authentication callback");
         }
@@ -425,26 +446,44 @@
             }
         }
 
-        if (mService != null) try {
-            useHandler(handler);
-            mAuthenticationCallback = callback;
-            mCryptoObject = crypto;
-            long sessionId = crypto != null ? crypto.getOpId() : 0;
-            mService.authenticate(mToken, sessionId, userId, mServiceReceiver, flags,
-                    mContext.getOpPackageName());
-        } catch (RemoteException e) {
-            Slog.w(TAG, "Remote exception while authenticating: ", e);
-            if (callback != null) {
+        if (mService != null) {
+            IBiometricNativeHandle handle = BiometricNativeHandleUtils.dup(windowId);
+            try {
+                useHandler(handler);
+                mAuthenticationCallback = callback;
+                mCryptoObject = crypto;
+                long sessionId = crypto != null ? crypto.getOpId() : 0;
+                mService.authenticate(mToken, sessionId, userId, mServiceReceiver, flags,
+                        mContext.getOpPackageName(), handle);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Remote exception while authenticating: ", e);
                 // Though this may not be a hardware issue, it will cause apps to give up or try
                 // again later.
                 callback.onAuthenticationError(FINGERPRINT_ERROR_HW_UNAVAILABLE,
                         getErrorString(mContext, FINGERPRINT_ERROR_HW_UNAVAILABLE,
-                            0 /* vendorCode */));
+                                0 /* vendorCode */));
+            } finally {
+                BiometricNativeHandleUtils.close(handle);
             }
         }
     }
 
     /**
+     * Defaults to {@link FingerprintManager#enroll(byte[], CancellationSignal, int, int,
+     * EnrollmentCallback, NativeHandle)} with {@code windowId} set to null.
+     *
+     * @see FingerprintManager#enroll(byte[], CancellationSignal, int, int, EnrollmentCallback,
+     * NativeHandle)
+     *
+     * @hide
+     */
+    @RequiresPermission(MANAGE_FINGERPRINT)
+    public void enroll(byte [] token, CancellationSignal cancel, int flags,
+            int userId, EnrollmentCallback callback) {
+        enroll(token, cancel, flags, userId, callback, null /* windowId */);
+    }
+
+    /**
      * Request fingerprint enrollment. This call warms up the fingerprint hardware
      * and starts scanning for fingerprints. Progress will be indicated by callbacks to the
      * {@link EnrollmentCallback} object. It terminates when
@@ -462,7 +501,7 @@
      */
     @RequiresPermission(MANAGE_FINGERPRINT)
     public void enroll(byte [] token, CancellationSignal cancel, int flags,
-            int userId, EnrollmentCallback callback) {
+            int userId, EnrollmentCallback callback, @Nullable NativeHandle windowId) {
         if (userId == UserHandle.USER_CURRENT) {
             userId = getCurrentUserId();
         }
@@ -479,18 +518,21 @@
             }
         }
 
-        if (mService != null) try {
-            mEnrollmentCallback = callback;
-            mService.enroll(mToken, token, userId, mServiceReceiver, flags,
-                    mContext.getOpPackageName());
-        } catch (RemoteException e) {
-            Slog.w(TAG, "Remote exception in enroll: ", e);
-            if (callback != null) {
+        if (mService != null) {
+            IBiometricNativeHandle handle = BiometricNativeHandleUtils.dup(windowId);
+            try {
+                mEnrollmentCallback = callback;
+                mService.enroll(mToken, token, userId, mServiceReceiver, flags,
+                        mContext.getOpPackageName(), handle);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Remote exception in enroll: ", e);
                 // Though this may not be a hardware issue, it will cause apps to give up or try
                 // again later.
                 callback.onEnrollmentError(FINGERPRINT_ERROR_HW_UNAVAILABLE,
                         getErrorString(mContext, FINGERPRINT_ERROR_HW_UNAVAILABLE,
-                            0 /* vendorCode */));
+                                0 /* vendorCode */));
+            } finally {
+                BiometricNativeHandleUtils.close(handle);
             }
         }
     }
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index 1a7e128..f2ffd08d 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -15,6 +15,7 @@
  */
 package android.hardware.fingerprint;
 
+import android.hardware.biometrics.IBiometricNativeHandle;
 import android.hardware.biometrics.IBiometricServiceReceiverInternal;
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
 import android.hardware.fingerprint.IFingerprintClientActiveCallback;
@@ -31,7 +32,8 @@
     // USE_FINGERPRINT/USE_BIOMETRIC permission. This is effectively deprecated, since it only comes
     // through FingerprintManager now.
     void authenticate(IBinder token, long sessionId, int userId,
-            IFingerprintServiceReceiver receiver, int flags, String opPackageName);
+            IFingerprintServiceReceiver receiver, int flags, String opPackageName,
+            in IBiometricNativeHandle windowId);
 
     // This method prepares the service to start authenticating, but doesn't start authentication.
     // This is protected by the MANAGE_BIOMETRIC signatuer permission. This method should only be
@@ -40,7 +42,7 @@
     // startPreparedClient().
     void prepareForAuthentication(IBinder token, long sessionId, int userId,
             IBiometricServiceReceiverInternal wrapperReceiver, String opPackageName, int cookie,
-            int callingUid, int callingPid, int callingUserId);
+            int callingUid, int callingPid, int callingUserId, in IBiometricNativeHandle windowId);
 
     // Starts authentication with the previously prepared client.
     void startPreparedClient(int cookie);
@@ -55,7 +57,7 @@
 
     // Start fingerprint enrollment
     void enroll(IBinder token, in byte [] cryptoToken, int groupId, IFingerprintServiceReceiver receiver,
-            int flags, String opPackageName);
+            int flags, String opPackageName, in IBiometricNativeHandle windowId);
 
     // Cancel enrollment in progress
     void cancelEnrollment(IBinder token);
diff --git a/core/java/android/hardware/soundtrigger/ConversionUtil.java b/core/java/android/hardware/soundtrigger/ConversionUtil.java
index a30fd6b..dbf33ca 100644
--- a/core/java/android/hardware/soundtrigger/ConversionUtil.java
+++ b/core/java/android/hardware/soundtrigger/ConversionUtil.java
@@ -17,7 +17,6 @@
 package android.hardware.soundtrigger;
 
 import android.annotation.Nullable;
-import android.hardware.soundtrigger.ModelParams;
 import android.media.AudioFormat;
 import android.media.audio.common.AudioConfig;
 import android.media.soundtrigger_middleware.AudioCapabilities;
@@ -333,20 +332,22 @@
     public static int aidl2apiAudioCapabilities(int aidlCapabilities) {
         int result = 0;
         if ((aidlCapabilities & AudioCapabilities.ECHO_CANCELLATION) != 0) {
-            result |= SoundTrigger.ModuleProperties.CAPABILITY_ECHO_CANCELLATION;
+            result |= SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_ECHO_CANCELLATION;
         }
         if ((aidlCapabilities & AudioCapabilities.NOISE_SUPPRESSION) != 0) {
-            result |= SoundTrigger.ModuleProperties.CAPABILITY_NOISE_SUPPRESSION;
+            result |= SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_NOISE_SUPPRESSION;
         }
         return result;
     }
 
     public static int api2aidlAudioCapabilities(int apiCapabilities) {
         int result = 0;
-        if ((apiCapabilities & SoundTrigger.ModuleProperties.CAPABILITY_ECHO_CANCELLATION) != 0) {
+        if ((apiCapabilities & SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_ECHO_CANCELLATION)
+                != 0) {
             result |= AudioCapabilities.ECHO_CANCELLATION;
         }
-        if ((apiCapabilities & SoundTrigger.ModuleProperties.CAPABILITY_NOISE_SUPPRESSION) != 0) {
+        if ((apiCapabilities & SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_NOISE_SUPPRESSION)
+                != 0) {
             result |= AudioCapabilities.NOISE_SUPPRESSION;
         }
         return result;
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index d505ae5..a74871d2 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -97,8 +97,8 @@
          */
         @Retention(RetentionPolicy.SOURCE)
         @IntDef(flag = true, prefix = { "AUDIO_CAPABILITY_" }, value = {
-                CAPABILITY_ECHO_CANCELLATION,
-                CAPABILITY_NOISE_SUPPRESSION
+                AUDIO_CAPABILITY_ECHO_CANCELLATION,
+                AUDIO_CAPABILITY_NOISE_SUPPRESSION
         })
         public @interface AudioCapabilities {}
 
@@ -106,12 +106,12 @@
          * If set the underlying module supports AEC.
          * Describes bit field {@link ModuleProperties#audioCapabilities}
          */
-        public static final int CAPABILITY_ECHO_CANCELLATION = 0x1;
+        public static final int AUDIO_CAPABILITY_ECHO_CANCELLATION = 0x1;
         /**
          * If set, the underlying module supports noise suppression.
          * Describes bit field {@link ModuleProperties#audioCapabilities}
          */
-        public static final int CAPABILITY_NOISE_SUPPRESSION = 0x2;
+        public static final int AUDIO_CAPABILITY_NOISE_SUPPRESSION = 0x2;
 
         /** Unique module ID provided by the native service */
         public final int id;
@@ -735,22 +735,40 @@
         /**
          * The inclusive start of supported range.
          */
-        public final int start;
+        private final int mStart;
 
         /**
          * The inclusive end of supported range.
          */
-        public final int end;
+        private final int mEnd;
 
         ModelParamRange(int start, int end) {
-            this.start = start;
-            this.end = end;
+            this.mStart = start;
+            this.mEnd = end;
         }
 
         /** @hide */
         private ModelParamRange(@NonNull Parcel in) {
-            this.start = in.readInt();
-            this.end = in.readInt();
+            this.mStart = in.readInt();
+            this.mEnd = in.readInt();
+        }
+
+        /**
+         * Get the beginning of the param range
+         *
+         * @return The inclusive start of the supported range.
+         */
+        public int getStart() {
+            return mStart;
+        }
+
+        /**
+         * Get the end of the param range
+         *
+         * @return The inclusive end of the supported range.
+         */
+        public int getEnd() {
+            return mEnd;
         }
 
         @NonNull
@@ -780,8 +798,8 @@
         public int hashCode() {
             final int prime = 31;
             int result = 1;
-            result = prime * result + (start);
-            result = prime * result + (end);
+            result = prime * result + (mStart);
+            result = prime * result + (mEnd);
             return result;
         }
 
@@ -797,10 +815,10 @@
                 return false;
             }
             ModelParamRange other = (ModelParamRange) obj;
-            if (start != other.start) {
+            if (mStart != other.mStart) {
                 return false;
             }
-            if (end != other.end) {
+            if (mEnd != other.mEnd) {
                 return false;
             }
             return true;
@@ -808,14 +826,14 @@
 
         @Override
         public void writeToParcel(@NonNull Parcel dest, int flags) {
-            dest.writeInt(start);
-            dest.writeInt(end);
+            dest.writeInt(mStart);
+            dest.writeInt(mEnd);
         }
 
         @Override
         @NonNull
         public String toString() {
-            return "ModelParamRange [start=" + start + ", end=" + end + "]";
+            return "ModelParamRange [start=" + mStart + ", end=" + mEnd + "]";
         }
     }
 
diff --git a/core/java/android/net/ConnectivityDiagnosticsManager.java b/core/java/android/net/ConnectivityDiagnosticsManager.java
index 140363c..b128ea7 100644
--- a/core/java/android/net/ConnectivityDiagnosticsManager.java
+++ b/core/java/android/net/ConnectivityDiagnosticsManager.java
@@ -676,7 +676,8 @@
         }
 
         try {
-            mService.registerConnectivityDiagnosticsCallback(binder, request);
+            mService.registerConnectivityDiagnosticsCallback(
+                    binder, request, mContext.getOpPackageName());
         } catch (RemoteException exception) {
             exception.rethrowFromSystemServer();
         }
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index fa12c08..f644f14 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -3750,6 +3750,7 @@
         checkCallbackNotNull(callback);
         Preconditions.checkArgument(action == REQUEST || need != null, "null NetworkCapabilities");
         final NetworkRequest request;
+        final String callingPackageName = mContext.getOpPackageName();
         try {
             synchronized(sCallbacks) {
                 if (callback.networkRequest != null
@@ -3761,10 +3762,11 @@
                 Messenger messenger = new Messenger(handler);
                 Binder binder = new Binder();
                 if (action == LISTEN) {
-                    request = mService.listenForNetwork(need, messenger, binder);
+                    request = mService.listenForNetwork(
+                            need, messenger, binder, callingPackageName);
                 } else {
                     request = mService.requestNetwork(
-                            need, messenger, timeoutMs, binder, legacyType);
+                            need, messenger, timeoutMs, binder, legacyType, callingPackageName);
                 }
                 if (request != null) {
                     sCallbacks.put(request, callback);
@@ -4037,8 +4039,10 @@
             @NonNull PendingIntent operation) {
         printStackTrace();
         checkPendingIntentNotNull(operation);
+        final String callingPackageName = mContext.getOpPackageName();
         try {
-            mService.pendingRequestForNetwork(request.networkCapabilities, operation);
+            mService.pendingRequestForNetwork(
+                    request.networkCapabilities, operation, callingPackageName);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         } catch (ServiceSpecificException e) {
@@ -4150,8 +4154,10 @@
             @NonNull PendingIntent operation) {
         printStackTrace();
         checkPendingIntentNotNull(operation);
+        final String callingPackageName = mContext.getOpPackageName();
         try {
-            mService.pendingListenForNetwork(request.networkCapabilities, operation);
+            mService.pendingListenForNetwork(
+                    request.networkCapabilities, operation, callingPackageName);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         } catch (ServiceSpecificException e) {
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 1089a19..1c7628f 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -167,18 +167,19 @@
             in int factorySerialNumber);
 
     NetworkRequest requestNetwork(in NetworkCapabilities networkCapabilities,
-            in Messenger messenger, int timeoutSec, in IBinder binder, int legacy);
+            in Messenger messenger, int timeoutSec, in IBinder binder, int legacy,
+            String callingPackageName);
 
     NetworkRequest pendingRequestForNetwork(in NetworkCapabilities networkCapabilities,
-            in PendingIntent operation);
+            in PendingIntent operation, String callingPackageName);
 
     void releasePendingNetworkRequest(in PendingIntent operation);
 
     NetworkRequest listenForNetwork(in NetworkCapabilities networkCapabilities,
-            in Messenger messenger, in IBinder binder);
+            in Messenger messenger, in IBinder binder, String callingPackageName);
 
     void pendingListenForNetwork(in NetworkCapabilities networkCapabilities,
-            in PendingIntent operation);
+            in PendingIntent operation, String callingPackageName);
 
     void releaseNetworkRequest(in NetworkRequest networkRequest);
 
@@ -222,7 +223,7 @@
     boolean isCallerCurrentAlwaysOnVpnLockdownApp();
 
     void registerConnectivityDiagnosticsCallback(in IConnectivityDiagnosticsCallback callback,
-            in NetworkRequest request);
+            in NetworkRequest request, String callingPackageName);
     void unregisterConnectivityDiagnosticsCallback(in IConnectivityDiagnosticsCallback callback);
 
     IBinder startOrGetTestNetworkService();
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index 4f4e27b..f8b51dd 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -27,6 +27,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.Process;
+import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.proto.ProtoOutputStream;
 
@@ -63,6 +64,16 @@
     // Set to true when private DNS is broken.
     private boolean mPrivateDnsBroken;
 
+    /**
+     * Uid of the app making the request.
+     */
+    private int mRequestorUid;
+
+    /**
+     * Package name of the app making the request.
+     */
+    private String mRequestorPackageName;
+
     public NetworkCapabilities() {
         clearAll();
         mNetworkCapabilities = DEFAULT_CAPABILITIES;
@@ -89,6 +100,8 @@
         mOwnerUid = Process.INVALID_UID;
         mSSID = null;
         mPrivateDnsBroken = false;
+        mRequestorUid = Process.INVALID_UID;
+        mRequestorPackageName = null;
     }
 
     /**
@@ -109,6 +122,8 @@
         mUnwantedNetworkCapabilities = nc.mUnwantedNetworkCapabilities;
         mSSID = nc.mSSID;
         mPrivateDnsBroken = nc.mPrivateDnsBroken;
+        mRequestorUid = nc.mRequestorUid;
+        mRequestorPackageName = nc.mRequestorPackageName;
     }
 
     /**
@@ -810,7 +825,7 @@
     }
 
     /**
-     * UID of the app that owns this network, or INVALID_UID if none/unknown.
+     * UID of the app that owns this network, or Process#INVALID_UID if none/unknown.
      *
      * <p>This field keeps track of the UID of the app that created this network and is in charge of
      * its lifecycle. This could be the UID of apps such as the Wifi network suggestor, the running
@@ -821,8 +836,9 @@
     /**
      * Set the UID of the owner app.
      */
-    public void setOwnerUid(final int uid) {
+    public @NonNull NetworkCapabilities setOwnerUid(final int uid) {
         mOwnerUid = uid;
+        return this;
     }
 
     /**
@@ -858,16 +874,18 @@
      *
      * <p>In general, user-supplied networks (such as WiFi networks) do not have an administrator.
      *
-     * <p>An app is granted owner privileges over Networks that it supplies. Owner privileges
-     * implicitly include administrator privileges.
+     * <p>An app is granted owner privileges over Networks that it supplies. The owner UID MUST
+     * always be included in administratorUids.
      *
      * @param administratorUids the UIDs to be set as administrators of this Network.
      * @hide
      */
     @SystemApi
-    public void setAdministratorUids(@NonNull final List<Integer> administratorUids) {
+    public @NonNull NetworkCapabilities setAdministratorUids(
+            @NonNull final List<Integer> administratorUids) {
         mAdministratorUids.clear();
         mAdministratorUids.addAll(administratorUids);
+        return this;
     }
 
     /**
@@ -1385,6 +1403,7 @@
         combineSignalStrength(nc);
         combineUids(nc);
         combineSSIDs(nc);
+        combineRequestor(nc);
     }
 
     /**
@@ -1404,7 +1423,8 @@
                 && satisfiedBySpecifier(nc)
                 && (onlyImmutable || satisfiedBySignalStrength(nc))
                 && (onlyImmutable || satisfiedByUids(nc))
-                && (onlyImmutable || satisfiedBySSID(nc)));
+                && (onlyImmutable || satisfiedBySSID(nc)))
+                && (onlyImmutable || satisfiedByRequestor(nc));
     }
 
     /**
@@ -1488,7 +1508,7 @@
     public boolean equals(@Nullable Object obj) {
         if (obj == null || (obj instanceof NetworkCapabilities == false)) return false;
         NetworkCapabilities that = (NetworkCapabilities) obj;
-        return (equalsNetCapabilities(that)
+        return equalsNetCapabilities(that)
                 && equalsTransportTypes(that)
                 && equalsLinkBandwidths(that)
                 && equalsSignalStrength(that)
@@ -1496,7 +1516,8 @@
                 && equalsTransportInfo(that)
                 && equalsUids(that)
                 && equalsSSID(that)
-                && equalsPrivateDnsBroken(that));
+                && equalsPrivateDnsBroken(that)
+                && equalsRequestor(that);
     }
 
     @Override
@@ -1514,7 +1535,9 @@
                 + Objects.hashCode(mUids) * 31
                 + Objects.hashCode(mSSID) * 37
                 + Objects.hashCode(mTransportInfo) * 41
-                + Objects.hashCode(mPrivateDnsBroken) * 43;
+                + Objects.hashCode(mPrivateDnsBroken) * 43
+                + Objects.hashCode(mRequestorUid) * 47
+                + Objects.hashCode(mRequestorPackageName) * 53;
     }
 
     @Override
@@ -1537,6 +1560,8 @@
         dest.writeBoolean(mPrivateDnsBroken);
         dest.writeList(mAdministratorUids);
         dest.writeInt(mOwnerUid);
+        dest.writeInt(mRequestorUid);
+        dest.writeString(mRequestorPackageName);
     }
 
     public static final @android.annotation.NonNull Creator<NetworkCapabilities> CREATOR =
@@ -1559,6 +1584,8 @@
                 netCap.mPrivateDnsBroken = in.readBoolean();
                 netCap.setAdministratorUids(in.readArrayList(null));
                 netCap.mOwnerUid = in.readInt();
+                netCap.mRequestorUid = in.readInt();
+                netCap.mRequestorPackageName = in.readString();
                 return netCap;
             }
             @Override
@@ -1624,6 +1651,9 @@
             sb.append(" Private DNS is broken");
         }
 
+        sb.append(" RequestorUid: ").append(mRequestorUid);
+        sb.append(" RequestorPackageName: ").append(mRequestorPackageName);
+
         sb.append("]");
         return sb.toString();
     }
@@ -1632,6 +1662,7 @@
     private interface NameOf {
         String nameOf(int value);
     }
+
     /**
      * @hide
      */
@@ -1799,4 +1830,120 @@
     private boolean equalsPrivateDnsBroken(NetworkCapabilities nc) {
         return mPrivateDnsBroken == nc.mPrivateDnsBroken;
     }
+
+    /**
+     * Set the uid of the app making the request.
+     *
+     * Note: This works only for {@link NetworkAgent} instances. Any capabilities passed in
+     * via the public {@link ConnectivityManager} API's will have this field overwritten.
+     *
+     * @param uid UID of the app.
+     * @hide
+     */
+    @SystemApi
+    public @NonNull NetworkCapabilities setRequestorUid(int uid) {
+        mRequestorUid = uid;
+        return this;
+    }
+
+    /**
+     * @return the uid of the app making the request.
+     *
+     * Note: This could return {@link Process#INVALID_UID} if the {@link NetworkRequest}
+     * object was not obtained from {@link ConnectivityManager}.
+     * @hide
+     */
+    public int getRequestorUid() {
+        return mRequestorUid;
+    }
+
+    /**
+     * Set the package name of the app making the request.
+     *
+     * Note: This works only for {@link NetworkAgent} instances. Any capabilities passed in
+     * via the public {@link ConnectivityManager} API's will have this field overwritten.
+     *
+     * @param packageName package name of the app.
+     * @hide
+     */
+    @SystemApi
+    public @NonNull NetworkCapabilities setRequestorPackageName(@NonNull String packageName) {
+        mRequestorPackageName = packageName;
+        return this;
+    }
+
+    /**
+     * @return the package name of the app making the request.
+     *
+     * Note: This could return {@code null} if the {@link NetworkRequest} object was not obtained
+     * from {@link ConnectivityManager}.
+     * @hide
+     */
+    @Nullable
+    public String getRequestorPackageName() {
+        return mRequestorPackageName;
+    }
+
+    /**
+     * Set the uid and package name of the app making the request.
+     *
+     * Note: This is intended to be only invoked from within connectivitiy service.
+     *
+     * @param uid UID of the app.
+     * @param packageName package name of the app.
+     * @hide
+     */
+    public @NonNull NetworkCapabilities setRequestorUidAndPackageName(
+            int uid, @NonNull String packageName) {
+        return setRequestorUid(uid).setRequestorPackageName(packageName);
+    }
+
+    /**
+     * Test whether the passed NetworkCapabilities satisfies the requestor restrictions of this
+     * capabilities.
+     *
+     * This method is called on the NetworkCapabilities embedded in a request with the
+     * capabilities of an available network. If the available network, sets a specific
+     * requestor (by uid and optionally package name), then this will only match a request from the
+     * same app. If either of the capabilities have an unset uid or package name, then it matches
+     * everything.
+     * <p>
+     * nc is assumed nonnull. Else, NPE.
+     */
+    private boolean satisfiedByRequestor(NetworkCapabilities nc) {
+        // No uid set, matches everything.
+        if (mRequestorUid == Process.INVALID_UID || nc.mRequestorUid == Process.INVALID_UID) {
+            return true;
+        }
+        // uids don't match.
+        if (mRequestorUid != nc.mRequestorUid) return false;
+        // No package names set, matches everything
+        if (null == nc.mRequestorPackageName || null == mRequestorPackageName) return true;
+        // check for package name match.
+        return TextUtils.equals(mRequestorPackageName, nc.mRequestorPackageName);
+    }
+
+    /**
+     * Combine requestor info of the capabilities.
+     * <p>
+     * This is only legal if either the requestor info of this object is reset, or both info are
+     * equal.
+     * nc is assumed nonnull.
+     */
+    private void combineRequestor(@NonNull NetworkCapabilities nc) {
+        if (mRequestorUid != Process.INVALID_UID && mRequestorUid != nc.mOwnerUid) {
+            throw new IllegalStateException("Can't combine two uids");
+        }
+        if (mRequestorPackageName != null
+                && !mRequestorPackageName.equals(nc.mRequestorPackageName)) {
+            throw new IllegalStateException("Can't combine two package names");
+        }
+        setRequestorUid(nc.mRequestorUid);
+        setRequestorPackageName(nc.mRequestorPackageName);
+    }
+
+    private boolean equalsRequestor(NetworkCapabilities nc) {
+        return mRequestorUid == nc.mRequestorUid
+                && TextUtils.equals(mRequestorPackageName, nc.mRequestorPackageName);
+    }
 }
diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java
index 301d203..964f13f 100644
--- a/core/java/android/net/NetworkRequest.java
+++ b/core/java/android/net/NetworkRequest.java
@@ -380,6 +380,7 @@
         dest.writeInt(requestId);
         dest.writeString(type.name());
     }
+
     public static final @android.annotation.NonNull Creator<NetworkRequest> CREATOR =
         new Creator<NetworkRequest>() {
             public NetworkRequest createFromParcel(Parcel in) {
@@ -494,6 +495,31 @@
         return networkCapabilities.getNetworkSpecifier();
     }
 
+    /**
+     * @return the uid of the app making the request.
+     *
+     * Note: This could return {@link Process#INVALID_UID} if the {@link NetworkRequest} object was
+     * not obtained from {@link ConnectivityManager}.
+     * @hide
+     */
+    @SystemApi
+    public int getRequestorUid() {
+        return networkCapabilities.getRequestorUid();
+    }
+
+    /**
+     * @return the package name of the app making the request.
+     *
+     * Note: This could return {@code null} if the {@link NetworkRequest} object was not obtained
+     * from {@link ConnectivityManager}.
+     * @hide
+     */
+    @SystemApi
+    @Nullable
+    public String getRequestorPackageName() {
+        return networkCapabilities.getRequestorPackageName();
+    }
+
     public String toString() {
         return "NetworkRequest [ " + type + " id=" + requestId +
                 (legacyType != ConnectivityManager.TYPE_NONE ? ", legacyType=" + legacyType : "") +
diff --git a/core/java/android/net/NetworkSpecifier.java b/core/java/android/net/NetworkSpecifier.java
index cf31d21..2dd0c4e 100644
--- a/core/java/android/net/NetworkSpecifier.java
+++ b/core/java/android/net/NetworkSpecifier.java
@@ -39,23 +39,6 @@
 
     /**
      * Optional method which can be overridden by concrete implementations of NetworkSpecifier to
-     * check a self-reported UID. A concrete implementation may contain a UID which would be self-
-     * reported by the caller (since NetworkSpecifier implementations should be non-mutable). This
-     * function is called by ConnectivityService and is passed the actual UID of the caller -
-     * allowing the verification of the self-reported UID. In cases of mismatch the implementation
-     * should throw a SecurityException.
-     *
-     * @param requestorUid The UID of the requestor as obtained from its binder.
-     *
-     * @hide
-     */
-    @SystemApi
-    public void assertValidFromUid(int requestorUid) {
-        // empty
-    }
-
-    /**
-     * Optional method which can be overridden by concrete implementations of NetworkSpecifier to
      * perform any redaction of information from the NetworkSpecifier, e.g. if it contains
      * sensitive information. The default implementation simply returns the object itself - i.e.
      * no information is redacted. A concrete implementation may return a modified (copy) of the
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index a291734..70b2db7 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -240,6 +240,13 @@
         public static final String RELEASE = getString("ro.build.version.release");
 
         /**
+         * The version string we show to the user; may be {@link #RELEASE} or
+         * {@link #CODENAME} if not a final release build.
+         */
+        @NonNull public static final String RELEASE_OR_CODENAME = getString(
+                "ro.build.version.release_or_codename");
+
+        /**
          * The base OS build the product is based on.
          */
         public static final String BASE_OS = SystemProperties.get("ro.build.version.base_os", "");
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 44f12a6..21a1e0f 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -34,9 +34,11 @@
 import android.util.Log;
 
 import java.io.File;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.LinkedList;
+import java.util.List;
 
 /**
  * Provides access to environment variables.
@@ -539,12 +541,21 @@
     @SystemApi
     public static @NonNull Collection<File> getInternalMediaDirectories() {
         final ArrayList<File> res = new ArrayList<>();
-        res.add(new File(Environment.getRootDirectory(), "media"));
-        res.add(new File(Environment.getOemDirectory(), "media"));
-        res.add(new File(Environment.getProductDirectory(), "media"));
+        addCanonicalFile(res, new File(Environment.getRootDirectory(), "media"));
+        addCanonicalFile(res, new File(Environment.getOemDirectory(), "media"));
+        addCanonicalFile(res, new File(Environment.getProductDirectory(), "media"));
         return res;
     }
 
+    private static void addCanonicalFile(List<File> list, File file) {
+        try {
+            list.add(file.getCanonicalFile());
+        } catch (IOException e) {
+            Log.w(TAG, "Failed to resolve " + file + ": " + e);
+            list.add(file);
+        }
+    }
+
     /**
      * Return the primary shared/external storage directory. This directory may
      * not currently be accessible if it has been mounted by the user on their
diff --git a/core/java/android/os/TimestampedValue.java b/core/java/android/os/TimestampedValue.java
index f4c87ac..4c4335b 100644
--- a/core/java/android/os/TimestampedValue.java
+++ b/core/java/android/os/TimestampedValue.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.SystemApi;
 
 import java.util.Objects;
 
@@ -36,7 +35,6 @@
  * @param <T> the type of the value with an associated timestamp
  * @hide
  */
-@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
 public final class TimestampedValue<T> implements Parcelable {
     private final long mReferenceTimeMillis;
     @Nullable
@@ -96,7 +94,6 @@
     }
 
     /** @hide */
-    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final @NonNull Parcelable.Creator<TimestampedValue<?>> CREATOR =
             new Parcelable.ClassLoaderCreator<TimestampedValue<?>>() {
 
diff --git a/core/java/android/os/incremental/IIncrementalManagerNative.aidl b/core/java/android/os/incremental/IIncrementalService.aidl
similarity index 98%
rename from core/java/android/os/incremental/IIncrementalManagerNative.aidl
rename to core/java/android/os/incremental/IIncrementalService.aidl
index 2b6cd14..21434a2 100644
--- a/core/java/android/os/incremental/IIncrementalManagerNative.aidl
+++ b/core/java/android/os/incremental/IIncrementalService.aidl
@@ -20,7 +20,7 @@
 import android.os.incremental.IncrementalNewFileParams;
 
 /** @hide */
-interface IIncrementalManagerNative {
+interface IIncrementalService {
     /**
      * A set of flags for the |createMode| parameters when creating a new Incremental storage.
      */
diff --git a/core/java/android/os/incremental/IncrementalManager.java b/core/java/android/os/incremental/IncrementalManager.java
index 9c6672d..35fa37a 100644
--- a/core/java/android/os/incremental/IncrementalManager.java
+++ b/core/java/android/os/incremental/IncrementalManager.java
@@ -23,8 +23,6 @@
 import android.content.Context;
 import android.content.pm.DataLoaderParams;
 import android.os.RemoteException;
-import android.system.ErrnoException;
-import android.system.Os;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
@@ -37,7 +35,7 @@
 import java.nio.file.Paths;
 
 /**
- * Provides operations to open or create an IncrementalStorage, using IIncrementalManagerNative
+ * Provides operations to open or create an IncrementalStorage, using IIncrementalService
  * service. Example Usage:
  *
  * <blockquote><pre>
@@ -52,13 +50,13 @@
     private static final String TAG = "IncrementalManager";
 
     public static final int CREATE_MODE_TEMPORARY_BIND =
-            IIncrementalManagerNative.CREATE_MODE_TEMPORARY_BIND;
+            IIncrementalService.CREATE_MODE_TEMPORARY_BIND;
     public static final int CREATE_MODE_PERMANENT_BIND =
-            IIncrementalManagerNative.CREATE_MODE_PERMANENT_BIND;
+            IIncrementalService.CREATE_MODE_PERMANENT_BIND;
     public static final int CREATE_MODE_CREATE =
-            IIncrementalManagerNative.CREATE_MODE_CREATE;
+            IIncrementalService.CREATE_MODE_CREATE;
     public static final int CREATE_MODE_OPEN_EXISTING =
-            IIncrementalManagerNative.CREATE_MODE_OPEN_EXISTING;
+            IIncrementalService.CREATE_MODE_OPEN_EXISTING;
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"CREATE_MODE_"}, value = {
@@ -70,12 +68,12 @@
     public @interface CreateMode {
     }
 
-    private final @Nullable IIncrementalManagerNative mNativeService;
+    private final @Nullable IIncrementalService mService;
     @GuardedBy("mStorages")
     private final SparseArray<IncrementalStorage> mStorages = new SparseArray<>();
 
-    public IncrementalManager(IIncrementalManagerNative nativeService) {
-        mNativeService = nativeService;
+    public IncrementalManager(IIncrementalService service) {
+        mService = service;
     }
 
     /**
@@ -108,11 +106,11 @@
             @NonNull DataLoaderParams params, @CreateMode int createMode,
             boolean autoStartDataLoader) {
         try {
-            final int id = mNativeService.createStorage(path, params.getData(), createMode);
+            final int id = mService.createStorage(path, params.getData(), createMode);
             if (id < 0) {
                 return null;
             }
-            final IncrementalStorage storage = new IncrementalStorage(mNativeService, id);
+            final IncrementalStorage storage = new IncrementalStorage(mService, id);
             synchronized (mStorages) {
                 mStorages.put(id, storage);
             }
@@ -135,11 +133,11 @@
     @Nullable
     public IncrementalStorage openStorage(@NonNull String path) {
         try {
-            final int id = mNativeService.openStorage(path);
+            final int id = mService.openStorage(path);
             if (id < 0) {
                 return null;
             }
-            final IncrementalStorage storage = new IncrementalStorage(mNativeService, id);
+            final IncrementalStorage storage = new IncrementalStorage(mService, id);
             synchronized (mStorages) {
                 mStorages.put(id, storage);
             }
@@ -158,12 +156,12 @@
     public IncrementalStorage createStorage(@NonNull String path,
             @NonNull IncrementalStorage linkedStorage, @CreateMode int createMode) {
         try {
-            final int id = mNativeService.createLinkedStorage(
+            final int id = mService.createLinkedStorage(
                     path, linkedStorage.getId(), createMode);
             if (id < 0) {
                 return null;
             }
-            final IncrementalStorage storage = new IncrementalStorage(mNativeService, id);
+            final IncrementalStorage storage = new IncrementalStorage(mService, id);
             synchronized (mStorages) {
                 mStorages.put(id, storage);
             }
@@ -193,116 +191,54 @@
     }
 
     /**
-     * Renames an Incremental path to a new path. If source path is a file, make a link from the old
-     * Incremental file to the new one. If source path is a dir, unbind old dir from Incremental
-     * Storage and bind the new one.
-     * <ol>
-     *     <li> For renaming a dir, dest dir will be created if not exists, and does not need to
-     *          be on the same Incremental storage as the source. </li>
-     *     <li> For renaming a file, dest file must be on the same Incremental storage as source.
-     *     </li>
-     * </ol>
+     * Set up an app's code path. The expected outcome of this method is:
+     * 1) The actual apk directory under /data/incremental is bind-mounted to the parent directory
+     * of {@code afterCodeFile}.
+     * 2) All the files under {@code beforeCodeFile} will show up under {@code afterCodeFile}.
      *
-     * @param sourcePath Absolute path to the source. Should be the same type as the destPath (file
-     *                   or dir). Expected to already exist and is an Incremental path.
-     * @param destPath   Absolute path to the destination.
-     * @throws IllegalArgumentException when 1) source does not exist, or 2) source and dest type
-     *                                  mismatch (one is file and the other is dir), or 3) source
-     *                                  path is not on Incremental File System,
-     * @throws IOException              when 1) cannot find the root path of the Incremental storage
-     *                                  of source, or 2) cannot retrieve the Incremental storage
-     *                                  instance of the source, or 3) renaming a file, but dest is
-     *                                  not on the same Incremental Storage, or 4) renaming a dir,
-     *                                  dest dir does not exist but fails to be created.
-     *                                  <p>
-     *                                  TODO(b/136132412): add unit tests
+     * @param beforeCodeFile Path that is currently bind-mounted and have APKs under it.
+     *                       Should no longer have any APKs after this method is called.
+     *                       Example: /data/app/vmdl*tmp
+     * @param afterCodeFile Path that should will have APKs after this method is called. Its parent
+     *                      directory should be bind-mounted to a directory under /data/incremental.
+     *                      Example: /data/app/~~[randomStringA]/[packageName]-[randomStringB]
+     * @throws IllegalArgumentException
+     * @throws IOException
+     * TODO(b/147371381): add unit tests
      */
-    public void rename(@NonNull String sourcePath, @NonNull String destPath) throws IOException {
-        final File source = new File(sourcePath);
-        final File dest = new File(destPath);
-        if (!source.exists()) {
-            throw new IllegalArgumentException("Path not exist: " + sourcePath);
+    public void renameCodePath(File beforeCodeFile, File afterCodeFile)
+            throws IllegalArgumentException, IOException {
+        final String beforeCodePath = beforeCodeFile.getAbsolutePath();
+        final String afterCodePathParent = afterCodeFile.getParentFile().getAbsolutePath();
+        if (!isIncrementalPath(beforeCodePath)) {
+            throw new IllegalArgumentException("Not an Incremental path: " + beforeCodePath);
         }
-        if (dest.exists()) {
-            throw new IllegalArgumentException("Target path already exists: " + destPath);
+        final String afterCodePathName = afterCodeFile.getName();
+        final Path apkStoragePath = Paths.get(beforeCodePath);
+        if (apkStoragePath == null || apkStoragePath.toAbsolutePath() == null) {
+            throw new IOException("Invalid source storage path for: " + beforeCodePath);
         }
-        if (source.isDirectory() && dest.exists() && dest.isFile()) {
-            throw new IllegalArgumentException(
-                    "Trying to rename a dir but destination is a file: " + destPath);
-        }
-        if (source.isFile() && dest.exists() && dest.isDirectory()) {
-            throw new IllegalArgumentException(
-                    "Trying to rename a file but destination is a dir: " + destPath);
-        }
-        if (!isIncrementalPath(sourcePath)) {
-            throw new IllegalArgumentException("Not an Incremental path: " + sourcePath);
-        }
-
-        Path storagePath = Paths.get(sourcePath);
-        if (source.isFile()) {
-            storagePath = getStoragePathForFile(source);
-        }
-        if (storagePath == null || storagePath.toAbsolutePath() == null) {
-            throw new IOException("Invalid source storage path for: " + sourcePath);
-        }
-        final IncrementalStorage storage = openStorage(storagePath.toAbsolutePath().toString());
-        if (storage == null) {
+        final IncrementalStorage apkStorage =
+                openStorage(apkStoragePath.toAbsolutePath().toString());
+        if (apkStorage == null) {
             throw new IOException("Failed to retrieve storage from Incremental Service.");
         }
-
-        if (source.isFile()) {
-            renameFile(storage, storagePath, source, dest);
-        } else {
-            renameDir(storage, storagePath, source, dest);
+        final IncrementalStorage linkedApkStorage = createStorage(afterCodePathParent, apkStorage,
+                IncrementalManager.CREATE_MODE_CREATE
+                        | IncrementalManager.CREATE_MODE_PERMANENT_BIND);
+        if (linkedApkStorage == null) {
+            throw new IOException("Failed to create linked storage at dir: " + afterCodePathParent);
         }
-    }
-
-    private void renameFile(IncrementalStorage storage, Path storagePath,
-            File source, File dest) throws IOException {
-        Path sourcePath = source.toPath();
-        Path destPath = dest.toPath();
-        if (!sourcePath.startsWith(storagePath)) {
-            throw new IOException("Path: " + source.getAbsolutePath() + " is not on storage at: "
-                    + storagePath.toString());
-        }
-        if (!destPath.startsWith(storagePath)) {
-            throw new IOException("Path: " + dest.getAbsolutePath() + " is not on storage at: "
-                    + storagePath.toString());
-        }
-        final Path sourceRelativePath = storagePath.relativize(sourcePath);
-        final Path destRelativePath = storagePath.relativize(destPath);
-        storage.moveFile(sourceRelativePath.toString(), destRelativePath.toString());
-
-    }
-
-    private void renameDir(IncrementalStorage storage, Path storagePath,
-            File source, File dest) throws IOException {
-        Path destPath = dest.toPath();
-        boolean usedMkdir = false;
-        try {
-            Os.mkdir(dest.getAbsolutePath(), 0755);
-            usedMkdir = true;
-        } catch (ErrnoException e) {
-            // Traditional mkdir fails but maybe we can create it on Incremental File System if
-            // the dest path is on the same Incremental storage as the source.
-            if (destPath.startsWith(storagePath)) {
-                storage.makeDirectories(storagePath.relativize(destPath).toString());
-            } else {
-                throw new IOException("Failed to create directory: " + dest.getAbsolutePath(), e);
+        linkedApkStorage.makeDirectory(afterCodePathName);
+        File[] files = beforeCodeFile.listFiles();
+        for (int i = 0; i < files.length; i++) {
+            if (files[i].isFile()) {
+                String fileName = files[i].getName();
+                apkStorage.makeLink(
+                        fileName, linkedApkStorage, afterCodePathName + "/" + fileName);
             }
         }
-        try {
-            storage.moveDir(source.getAbsolutePath(), dest.getAbsolutePath());
-        } catch (Exception ex) {
-            if (usedMkdir) {
-                try {
-                    Os.remove(dest.getAbsolutePath());
-                } catch (ErrnoException ignored) {
-                }
-            }
-            throw new IOException(
-                    "Failed to move " + source.getAbsolutePath() + " to " + dest.getAbsolutePath());
-        }
+        apkStorage.unBind(beforeCodePath);
     }
 
     /**
@@ -311,11 +247,11 @@
      */
     public void closeStorage(@NonNull String path) {
         try {
-            final int id = mNativeService.openStorage(path);
+            final int id = mService.openStorage(path);
             if (id < 0) {
                 return;
             }
-            mNativeService.deleteStorage(id);
+            mService.deleteStorage(id);
             synchronized (mStorages) {
                 mStorages.remove(id);
             }
diff --git a/core/java/android/os/incremental/IncrementalStorage.java b/core/java/android/os/incremental/IncrementalStorage.java
index e5d1b43..c4b843b 100644
--- a/core/java/android/os/incremental/IncrementalStorage.java
+++ b/core/java/android/os/incremental/IncrementalStorage.java
@@ -40,10 +40,10 @@
 public final class IncrementalStorage {
     private static final String TAG = "IncrementalStorage";
     private final int mId;
-    private final IIncrementalManagerNative mService;
+    private final IIncrementalService mService;
 
 
-    public IncrementalStorage(@NonNull IIncrementalManagerNative is, int id) {
+    public IncrementalStorage(@NonNull IIncrementalService is, int id) {
         mService = is;
         mId = id;
     }
@@ -74,7 +74,7 @@
             throws IOException {
         try {
             int res = mService.makeBindMount(mId, sourcePath, targetPath,
-                    IIncrementalManagerNative.BIND_TEMPORARY);
+                    IIncrementalService.BIND_TEMPORARY);
             if (res < 0) {
                 throw new IOException("bind() failed with errno " + -res);
             }
@@ -105,7 +105,7 @@
             throws IOException {
         try {
             int res = mService.makeBindMount(mId, sourcePath, targetPath,
-                    IIncrementalManagerNative.BIND_PERMANENT);
+                    IIncrementalService.BIND_PERMANENT);
             if (res < 0) {
                 throw new IOException("bind() permanent failed with errno " + -res);
             }
@@ -293,7 +293,7 @@
         }
         try {
             int res = mService.makeBindMount(mId, sourcePath, destPath,
-                    IIncrementalManagerNative.BIND_PERMANENT);
+                    IIncrementalService.BIND_PERMANENT);
             if (res < 0) {
                 throw new IOException("moveDir() failed at making bind mount, errno " + -res);
             }
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index b6f5138..adfa885 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -5205,6 +5205,7 @@
         /**
          * TelephonyProvider column name for allowed network types. Indicate which network types
          * are allowed. Default is -1.
+         * <P>Type: BIGINT (long) </P>
          */
         public static final String ALLOWED_NETWORK_TYPES = "allowed_network_types";
     }
diff --git a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
index 368c94c..79852d3 100644
--- a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
+++ b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
@@ -31,7 +31,6 @@
 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;
@@ -93,7 +92,7 @@
     // Used for metrics / debug only
     private ComponentName mServiceComponentName;
 
-    private final IAugmentedAutofillService mInterface = new IAugmentedAutofillService.Stub() {
+    private final class AugmentedAutofillServiceImpl extends IAugmentedAutofillService.Stub {
 
         @Override
         public void onConnected(boolean debug, boolean verbose) {
@@ -137,7 +136,7 @@
     public final IBinder onBind(Intent intent) {
         mServiceComponentName = intent.getComponent();
         if (SERVICE_INTERFACE.equals(intent.getAction())) {
-            return mInterface.asBinder();
+            return new AugmentedAutofillServiceImpl();
         }
         Log.w(TAG, "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + ": " + intent);
         return null;
@@ -352,11 +351,13 @@
         static final int REPORT_EVENT_NO_RESPONSE = 1;
         static final int REPORT_EVENT_UI_SHOWN = 2;
         static final int REPORT_EVENT_UI_DESTROYED = 3;
+        static final int REPORT_EVENT_INLINE_RESPONSE = 4;
 
         @IntDef(prefix = { "REPORT_EVENT_" }, value = {
                 REPORT_EVENT_NO_RESPONSE,
                 REPORT_EVENT_UI_SHOWN,
-                REPORT_EVENT_UI_DESTROYED
+                REPORT_EVENT_UI_DESTROYED,
+                REPORT_EVENT_INLINE_RESPONSE
         })
         @Retention(RetentionPolicy.SOURCE)
         @interface ReportEvent{}
@@ -365,8 +366,8 @@
         private final Object mLock = new Object();
         private final IAugmentedAutofillManagerClient mClient;
         private final int mSessionId;
-        public final int taskId;
-        public final ComponentName componentName;
+        public final int mTaskId;
+        public final ComponentName mComponentName;
         // Used for metrics / debug only
         private String mServicePackageName;
         @GuardedBy("mLock")
@@ -406,8 +407,8 @@
             mSessionId = sessionId;
             mClient = IAugmentedAutofillManagerClient.Stub.asInterface(client);
             mCallback = callback;
-            this.taskId = taskId;
-            this.componentName = componentName;
+            mTaskId = taskId;
+            mComponentName = componentName;
             mServicePackageName = serviceComponentName.getPackageName();
             mFocusedId = focusedId;
             mFocusedValue = focusedValue;
@@ -514,22 +515,24 @@
             }
         }
 
-        public void onInlineSuggestionsDataReady(@NonNull List<Dataset> inlineSuggestionsData,
-                @Nullable Bundle clientState) {
+        void reportResult(@Nullable List<Dataset> inlineSuggestionsData) {
             try {
-                mCallback.onSuccess(inlineSuggestionsData.toArray(new Dataset[]{}), clientState);
+                final Dataset[] inlineSuggestions = (inlineSuggestionsData != null)
+                        ? inlineSuggestionsData.toArray(new Dataset[inlineSuggestionsData.size()])
+                        : null;
+                mCallback.onSuccess(inlineSuggestions);
             } catch (RemoteException e) {
                 Log.e(TAG, "Error calling back with the inline suggestions data: " + e);
             }
         }
 
-        // Used (mostly) for metrics.
-        public void report(@ReportEvent int event) {
-            if (sVerbose) Log.v(TAG, "report(): " + event);
+        void logEvent(@ReportEvent int event) {
+            if (sVerbose) Log.v(TAG, "returnAndLogResult(): " + event);
             long duration = -1;
             int type = MetricsEvent.TYPE_UNKNOWN;
+
             switch (event) {
-                case REPORT_EVENT_NO_RESPONSE:
+                case REPORT_EVENT_NO_RESPONSE: {
                     type = MetricsEvent.TYPE_SUCCESS;
                     if (mFirstOnSuccessTime == 0) {
                         mFirstOnSuccessTime = SystemClock.elapsedRealtime();
@@ -538,40 +541,49 @@
                             Log.d(TAG, "Service responded nothing in " + formatDuration(duration));
                         }
                     }
-                    try {
-                        mCallback.onSuccess(/* inlineSuggestionsData= */null, /* clientState=*/
-                                null);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Error reporting success: " + e);
+                } break;
+
+                case REPORT_EVENT_INLINE_RESPONSE: {
+                    // TODO: Define a constant and log this event
+                    // type = MetricsEvent.TYPE_SUCCESS_INLINE;
+                    if (mFirstOnSuccessTime == 0) {
+                        mFirstOnSuccessTime = SystemClock.elapsedRealtime();
+                        duration = mFirstOnSuccessTime - mFirstRequestTime;
+                        if (sDebug) {
+                            Log.d(TAG, "Service responded nothing in " + formatDuration(duration));
+                        }
                     }
-                    break;
-                case REPORT_EVENT_UI_SHOWN:
+                } break;
+
+                case REPORT_EVENT_UI_SHOWN: {
                     type = MetricsEvent.TYPE_OPEN;
                     if (mUiFirstShownTime == 0) {
                         mUiFirstShownTime = SystemClock.elapsedRealtime();
                         duration = mUiFirstShownTime - mFirstRequestTime;
                         if (sDebug) Log.d(TAG, "UI shown in " + formatDuration(duration));
                     }
-                    break;
-                case REPORT_EVENT_UI_DESTROYED:
+                } break;
+
+                case REPORT_EVENT_UI_DESTROYED: {
                     type = MetricsEvent.TYPE_CLOSE;
                     if (mUiFirstDestroyedTime == 0) {
                         mUiFirstDestroyedTime = SystemClock.elapsedRealtime();
-                        duration =  mUiFirstDestroyedTime - mFirstRequestTime;
+                        duration = mUiFirstDestroyedTime - mFirstRequestTime;
                         if (sDebug) Log.d(TAG, "UI destroyed in " + formatDuration(duration));
                     }
-                    break;
+                } break;
+
                 default:
                     Log.w(TAG, "invalid event reported: " + event);
             }
-            logResponse(type, mServicePackageName, componentName, mSessionId, duration);
+            logResponse(type, mServicePackageName, mComponentName, mSessionId, duration);
         }
 
         public void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
             pw.print(prefix); pw.print("sessionId: "); pw.println(mSessionId);
-            pw.print(prefix); pw.print("taskId: "); pw.println(taskId);
+            pw.print(prefix); pw.print("taskId: "); pw.println(mTaskId);
             pw.print(prefix); pw.print("component: ");
-            pw.println(componentName.flattenToShortString());
+            pw.println(mComponentName.flattenToShortString());
             pw.print(prefix); pw.print("focusedId: "); pw.println(mFocusedId);
             if (mFocusedValue != null) {
                 pw.print(prefix); pw.print("focusedValue: "); pw.println(mFocusedValue);
diff --git a/core/java/android/service/autofill/augmented/FillCallback.java b/core/java/android/service/autofill/augmented/FillCallback.java
index d0ffd7b..19eff57 100644
--- a/core/java/android/service/autofill/augmented/FillCallback.java
+++ b/core/java/android/service/autofill/augmented/FillCallback.java
@@ -54,13 +54,15 @@
         if (sDebug) Log.d(TAG, "onSuccess(): " + response);
 
         if (response == null) {
-            mProxy.report(AutofillProxy.REPORT_EVENT_NO_RESPONSE);
+            mProxy.logEvent(AutofillProxy.REPORT_EVENT_NO_RESPONSE);
+            mProxy.reportResult(null /*inlineSuggestions*/);
             return;
         }
 
         List<Dataset> inlineSuggestions = response.getInlineSuggestions();
         if (inlineSuggestions != null && !inlineSuggestions.isEmpty()) {
-            mProxy.onInlineSuggestionsDataReady(inlineSuggestions, response.getClientState());
+            mProxy.logEvent(AutofillProxy.REPORT_EVENT_INLINE_RESPONSE);
+            mProxy.reportResult(inlineSuggestions);
             return;
         }
 
diff --git a/core/java/android/service/autofill/augmented/FillController.java b/core/java/android/service/autofill/augmented/FillController.java
index 63ec2d8..7d552d6 100644
--- a/core/java/android/service/autofill/augmented/FillController.java
+++ b/core/java/android/service/autofill/augmented/FillController.java
@@ -62,12 +62,13 @@
 
         try {
             mProxy.autofill(values);
-            final FillWindow fillWindow = mProxy.getFillWindow();
-            if (fillWindow != null) {
-                fillWindow.destroy();
-            }
         } catch (RemoteException e) {
             e.rethrowAsRuntimeException();
         }
+
+        final FillWindow fillWindow = mProxy.getFillWindow();
+        if (fillWindow != null) {
+            fillWindow.destroy();
+        }
     }
 }
diff --git a/core/java/android/service/autofill/augmented/FillRequest.java b/core/java/android/service/autofill/augmented/FillRequest.java
index ca49e7d..6927cf6 100644
--- a/core/java/android/service/autofill/augmented/FillRequest.java
+++ b/core/java/android/service/autofill/augmented/FillRequest.java
@@ -53,7 +53,7 @@
      * Gets the task of the activity associated with this request.
      */
     public int getTaskId() {
-        return mProxy.taskId;
+        return mProxy.mTaskId;
     }
 
     /**
@@ -61,7 +61,7 @@
      */
     @NonNull
     public ComponentName getActivityComponent() {
-        return mProxy.componentName;
+        return mProxy.mComponentName;
     }
 
     /**
diff --git a/core/java/android/service/autofill/augmented/FillWindow.java b/core/java/android/service/autofill/augmented/FillWindow.java
index 5d00370..077df6c 100644
--- a/core/java/android/service/autofill/augmented/FillWindow.java
+++ b/core/java/android/service/autofill/augmented/FillWindow.java
@@ -21,6 +21,7 @@
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.graphics.Rect;
@@ -41,6 +42,7 @@
 import dalvik.system.CloseGuard;
 
 import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
 
 /**
  * Handle to a window used to display the augmented autofill UI.
@@ -70,23 +72,22 @@
     private final CloseGuard mCloseGuard = CloseGuard.get();
 
     private final @NonNull Handler mUiThreadHandler = new Handler(Looper.getMainLooper());
-    private final @NonNull FillWindowPresenter mFillWindowPresenter = new FillWindowPresenter();
 
     @GuardedBy("mLock")
-    private WindowManager mWm;
+    private @NonNull WindowManager mWm;
     @GuardedBy("mLock")
     private View mFillView;
     @GuardedBy("mLock")
     private boolean mShowing;
     @GuardedBy("mLock")
-    private Rect mBounds;
+    private @Nullable Rect mBounds;
 
     @GuardedBy("mLock")
     private boolean mUpdateCalled;
     @GuardedBy("mLock")
     private boolean mDestroyed;
 
-    private AutofillProxy mProxy;
+    private @NonNull AutofillProxy mProxy;
 
     /**
      * Updates the content of the window.
@@ -172,11 +173,11 @@
                 try {
                     mProxy.requestShowFillUi(mBounds.right - mBounds.left,
                             mBounds.bottom - mBounds.top,
-                            /*anchorBounds=*/ null, mFillWindowPresenter);
+                            /*anchorBounds=*/ null, new FillWindowPresenter(this));
                 } catch (RemoteException e) {
                     Log.w(TAG, "Error requesting to show fill window", e);
                 }
-                mProxy.report(AutofillProxy.REPORT_EVENT_UI_SHOWN);
+                mProxy.logEvent(AutofillProxy.REPORT_EVENT_UI_SHOWN);
             }
         }
     }
@@ -244,7 +245,7 @@
             if (mUpdateCalled) {
                 mFillView.setOnClickListener(null);
                 hide();
-                mProxy.report(AutofillProxy.REPORT_EVENT_UI_DESTROYED);
+                mProxy.logEvent(AutofillProxy.REPORT_EVENT_UI_DESTROYED);
             }
             mDestroyed = true;
             mCloseGuard.close();
@@ -254,9 +255,7 @@
     @Override
     protected void finalize() throws Throwable {
         try {
-            if (mCloseGuard != null) {
-                mCloseGuard.warnIfOpen();
-            }
+            mCloseGuard.warnIfOpen();
             destroy();
         } finally {
             super.finalize();
@@ -289,22 +288,36 @@
 
     /** @hide */
     @Override
-    public void close() throws Exception {
+    public void close() {
         destroy();
     }
 
-    private final class FillWindowPresenter extends IAutofillWindowPresenter.Stub {
+    private static final class FillWindowPresenter extends IAutofillWindowPresenter.Stub {
+        private final @NonNull WeakReference<FillWindow> mFillWindowReference;
+
+        FillWindowPresenter(@NonNull FillWindow fillWindow) {
+            mFillWindowReference = new WeakReference<>(fillWindow);
+        }
+
         @Override
         public void show(WindowManager.LayoutParams p, Rect transitionEpicenter,
                 boolean fitsSystemWindows, int layoutDirection) {
             if (sDebug) Log.d(TAG, "FillWindowPresenter.show()");
-            mUiThreadHandler.sendMessage(obtainMessage(FillWindow::handleShow, FillWindow.this, p));
+            final FillWindow fillWindow = mFillWindowReference.get();
+            if (fillWindow != null) {
+                fillWindow.mUiThreadHandler.sendMessage(
+                        obtainMessage(FillWindow::handleShow, fillWindow, p));
+            }
         }
 
         @Override
         public void hide(Rect transitionEpicenter) {
             if (sDebug) Log.d(TAG, "FillWindowPresenter.hide()");
-            mUiThreadHandler.sendMessage(obtainMessage(FillWindow::handleHide, FillWindow.this));
+            final FillWindow fillWindow = mFillWindowReference.get();
+            if (fillWindow != null) {
+                fillWindow.mUiThreadHandler.sendMessage(
+                        obtainMessage(FillWindow::handleHide, fillWindow));
+            }
         }
     }
 }
diff --git a/core/java/android/service/autofill/augmented/IFillCallback.aidl b/core/java/android/service/autofill/augmented/IFillCallback.aidl
index 31e77f3..d983721 100644
--- a/core/java/android/service/autofill/augmented/IFillCallback.aidl
+++ b/core/java/android/service/autofill/augmented/IFillCallback.aidl
@@ -28,7 +28,7 @@
  */
 interface IFillCallback {
     void onCancellable(in ICancellationSignal cancellation);
-    void onSuccess(in @nullable Dataset[] inlineSuggestionsData, in @nullable Bundle clientState);
+    void onSuccess(in @nullable Dataset[] inlineSuggestionsData);
     boolean isCompleted();
     void cancel();
 }
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 3f9462c..af91596 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -16,6 +16,9 @@
 
 package android.service.notification;
 
+import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_ANYONE;
+import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_IMPORTANT;
+import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_NONE;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
@@ -99,6 +102,8 @@
     private static final boolean DEFAULT_ALLOW_REMINDERS = false;
     private static final boolean DEFAULT_ALLOW_EVENTS = false;
     private static final boolean DEFAULT_ALLOW_REPEAT_CALLERS = true;
+    private static final boolean DEFAULT_ALLOW_CONV = true;
+    private static final int DEFAULT_ALLOW_CONV_FROM = ZenPolicy.CONVERSATION_SENDERS_IMPORTANT;
     private static final boolean DEFAULT_CHANNELS_BYPASSING_DND = false;
     private static final int DEFAULT_SUPPRESSED_VISUAL_EFFECTS = 0;
 
@@ -120,6 +125,8 @@
     private static final String ALLOW_ATT_EVENTS = "events";
     private static final String ALLOW_ATT_SCREEN_OFF = "visualScreenOff";
     private static final String ALLOW_ATT_SCREEN_ON = "visualScreenOn";
+    private static final String ALLOW_ATT_CONV = "conv";
+    private static final String ALLOW_ATT_CONV_FROM = "convFrom";
     private static final String DISALLOW_TAG = "disallow";
     private static final String DISALLOW_ATT_VISUAL_EFFECTS = "visualEffects";
     private static final String STATE_TAG = "state";
@@ -170,6 +177,8 @@
     public boolean allowEvents = DEFAULT_ALLOW_EVENTS;
     public int allowCallsFrom = DEFAULT_CALLS_SOURCE;
     public int allowMessagesFrom = DEFAULT_SOURCE;
+    public boolean allowConversations = DEFAULT_ALLOW_CONV;
+    public int allowConversationsFrom = DEFAULT_ALLOW_CONV_FROM;
     public int user = UserHandle.USER_SYSTEM;
     public int suppressedVisualEffects = DEFAULT_SUPPRESSED_VISUAL_EFFECTS;
     public boolean areChannelsBypassingDnd = DEFAULT_CHANNELS_BYPASSING_DND;
@@ -207,6 +216,8 @@
         allowSystem = source.readInt() == 1;
         suppressedVisualEffects = source.readInt();
         areChannelsBypassingDnd = source.readInt() == 1;
+        allowConversations = source.readBoolean();
+        allowConversationsFrom = source.readInt();
     }
 
     @Override
@@ -239,6 +250,8 @@
         dest.writeInt(allowSystem ? 1 : 0);
         dest.writeInt(suppressedVisualEffects);
         dest.writeInt(areChannelsBypassingDnd ? 1 : 0);
+        dest.writeBoolean(allowConversations);
+        dest.writeInt(allowConversationsFrom);
     }
 
     @Override
@@ -253,8 +266,11 @@
                 .append(",allowCalls=").append(allowCalls)
                 .append(",allowRepeatCallers=").append(allowRepeatCallers)
                 .append(",allowMessages=").append(allowMessages)
+                .append(",allowConversations=").append(allowConversations)
                 .append(",allowCallsFrom=").append(sourceToString(allowCallsFrom))
                 .append(",allowMessagesFrom=").append(sourceToString(allowMessagesFrom))
+                .append(",allowConvFrom=").append(ZenPolicy.conversationTypeToString
+                        (allowConversationsFrom))
                 .append(",suppressedVisualEffects=").append(suppressedVisualEffects)
                 .append(",areChannelsBypassingDnd=").append(areChannelsBypassingDnd)
                 .append(",\nautomaticRules=").append(rulesToString())
@@ -431,7 +447,9 @@
                 && Objects.equals(other.automaticRules, automaticRules)
                 && Objects.equals(other.manualRule, manualRule)
                 && other.suppressedVisualEffects == suppressedVisualEffects
-                && other.areChannelsBypassingDnd == areChannelsBypassingDnd;
+                && other.areChannelsBypassingDnd == areChannelsBypassingDnd
+                && other.allowConversations == allowConversations
+                && other.allowConversationsFrom == allowConversationsFrom;
     }
 
     @Override
@@ -440,7 +458,8 @@
                 allowRepeatCallers, allowMessages,
                 allowCallsFrom, allowMessagesFrom, allowReminders, allowEvents,
                 user, automaticRules, manualRule,
-                suppressedVisualEffects, areChannelsBypassingDnd);
+                suppressedVisualEffects, areChannelsBypassingDnd, allowConversations,
+                allowConversationsFrom);
     }
 
     private static String toDayList(int[] days) {
@@ -518,10 +537,13 @@
                             DEFAULT_ALLOW_MESSAGES);
                     rt.allowReminders = safeBoolean(parser, ALLOW_ATT_REMINDERS,
                             DEFAULT_ALLOW_REMINDERS);
+                    rt.allowConversations = safeBoolean(parser, ALLOW_ATT_CONV, DEFAULT_ALLOW_CONV);
                     rt.allowEvents = safeBoolean(parser, ALLOW_ATT_EVENTS, DEFAULT_ALLOW_EVENTS);
                     final int from = safeInt(parser, ALLOW_ATT_FROM, -1);
                     final int callsFrom = safeInt(parser, ALLOW_ATT_CALLS_FROM, -1);
                     final int messagesFrom = safeInt(parser, ALLOW_ATT_MESSAGES_FROM, -1);
+                    rt.allowConversationsFrom = safeInt(parser, ALLOW_ATT_CONV_FROM,
+                            DEFAULT_ALLOW_CONV_FROM);
                     if (isValidSource(callsFrom) && isValidSource(messagesFrom)) {
                         rt.allowCallsFrom = callsFrom;
                         rt.allowMessagesFrom = messagesFrom;
@@ -602,6 +624,8 @@
         out.attribute(null, ALLOW_ATT_ALARMS, Boolean.toString(allowAlarms));
         out.attribute(null, ALLOW_ATT_MEDIA, Boolean.toString(allowMedia));
         out.attribute(null, ALLOW_ATT_SYSTEM, Boolean.toString(allowSystem));
+        out.attribute(null, ALLOW_ATT_CONV, Boolean.toString(allowConversations));
+        out.attribute(null, ALLOW_ATT_CONV_FROM, Integer.toString(allowConversationsFrom));
         out.endTag(null, ALLOW_TAG);
 
         out.startTag(null, DISALLOW_TAG);
@@ -944,6 +968,7 @@
         int suppressedVisualEffects = 0;
         int callSenders = defaultPolicy.priorityCallSenders;
         int messageSenders = defaultPolicy.priorityMessageSenders;
+        int conversationSenders = defaultPolicy.priorityConversationSenders;
 
         if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_REMINDERS,
                 isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_REMINDERS, defaultPolicy))) {
@@ -962,6 +987,14 @@
                     messageSenders);
         }
 
+        if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_CONVERSATIONS,
+                isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_CONVERSATIONS, defaultPolicy))) {
+            priorityCategories |= Policy.PRIORITY_CATEGORY_CONVERSATIONS;
+            conversationSenders = getNotificationPolicySenders(
+                    zenPolicy.getPriorityConversationSenders(),
+                    conversationSenders);
+        }
+
         if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_CALLS,
                 isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_CALLS, defaultPolicy))) {
             priorityCategories |= Policy.PRIORITY_CATEGORY_CALLS;
@@ -1047,7 +1080,7 @@
         }
 
         return new NotificationManager.Policy(priorityCategories, callSenders,
-                messageSenders, suppressedVisualEffects, defaultPolicy.state);
+                messageSenders, suppressedVisualEffects, defaultPolicy.state, conversationSenders);
     }
 
     private boolean isPriorityCategoryEnabled(int categoryType, Policy policy) {
@@ -1088,11 +1121,14 @@
         }
     }
 
-
     public Policy toNotificationPolicy() {
         int priorityCategories = 0;
         int priorityCallSenders = Policy.PRIORITY_SENDERS_CONTACTS;
         int priorityMessageSenders = Policy.PRIORITY_SENDERS_CONTACTS;
+        int priorityConversationSenders = Policy.CONVERSATION_SENDERS_IMPORTANT;
+        if (allowConversations) {
+            priorityCategories |= Policy.PRIORITY_CATEGORY_CONVERSATIONS;
+        }
         if (allowCalls) {
             priorityCategories |= Policy.PRIORITY_CATEGORY_CALLS;
         }
@@ -1119,10 +1155,12 @@
         }
         priorityCallSenders = sourceToPrioritySenders(allowCallsFrom, priorityCallSenders);
         priorityMessageSenders = sourceToPrioritySenders(allowMessagesFrom, priorityMessageSenders);
+        priorityConversationSenders = allowConversationsFrom;
 
         return new Policy(priorityCategories, priorityCallSenders, priorityMessageSenders,
                 suppressedVisualEffects, areChannelsBypassingDnd
-                ? Policy.STATE_CHANNELS_BYPASSING_DND : 0);
+                ? Policy.STATE_CHANNELS_BYPASSING_DND : 0,
+                priorityConversationSenders);
     }
 
     /**
@@ -1157,6 +1195,27 @@
         }
     }
 
+    private static int normalizePrioritySenders(int prioritySenders, int def) {
+        if (!(prioritySenders == Policy.PRIORITY_SENDERS_CONTACTS
+                || prioritySenders == Policy.PRIORITY_SENDERS_STARRED
+                || prioritySenders == Policy.PRIORITY_SENDERS_ANY)) {
+            return def;
+        }
+        return prioritySenders;
+    }
+
+    private static int normalizeConversationSenders(boolean allowed, int senders, int def) {
+        if (!allowed) {
+            return CONVERSATION_SENDERS_NONE;
+        }
+        if (!(senders == CONVERSATION_SENDERS_ANYONE
+                || senders == CONVERSATION_SENDERS_IMPORTANT
+                || senders == CONVERSATION_SENDERS_NONE)) {
+            return def;
+        }
+        return senders;
+    }
+
     public void applyNotificationPolicy(Policy policy) {
         if (policy == null) return;
         allowAlarms = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_ALARMS) != 0;
@@ -1168,12 +1227,17 @@
         allowMessages = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_MESSAGES) != 0;
         allowRepeatCallers = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_REPEAT_CALLERS)
                 != 0;
-        allowCallsFrom = prioritySendersToSource(policy.priorityCallSenders, allowCallsFrom);
-        allowMessagesFrom = prioritySendersToSource(policy.priorityMessageSenders,
+        allowCallsFrom = normalizePrioritySenders(policy.priorityCallSenders, allowCallsFrom);
+        allowMessagesFrom = normalizePrioritySenders(policy.priorityMessageSenders,
                 allowMessagesFrom);
         if (policy.suppressedVisualEffects != Policy.SUPPRESSED_EFFECTS_UNSET) {
             suppressedVisualEffects = policy.suppressedVisualEffects;
         }
+        allowConversations = (policy.priorityCategories
+                & Policy.PRIORITY_CATEGORY_CONVERSATIONS) != 0;
+        allowConversationsFrom = normalizeConversationSenders(allowConversations,
+                policy.priorityConversationSenders,
+                allowConversationsFrom);
         if (policy.state != Policy.STATE_UNSET) {
             areChannelsBypassingDnd = (policy.state & Policy.STATE_CHANNELS_BYPASSING_DND) != 0;
         }
@@ -1919,10 +1983,13 @@
                 & NotificationManager.Policy.PRIORITY_CATEGORY_EVENTS) != 0;
         boolean allowRepeatCallers = (policy.priorityCategories
                 & NotificationManager.Policy.PRIORITY_CATEGORY_REPEAT_CALLERS) != 0;
+        boolean allowConversations = (policy.priorityConversationSenders
+                & Policy.PRIORITY_CATEGORY_CONVERSATIONS) != 0;
         boolean areChannelsBypassingDnd = (policy.state & Policy.STATE_CHANNELS_BYPASSING_DND) != 0;
         boolean allowSystem =  (policy.priorityCategories & Policy.PRIORITY_CATEGORY_SYSTEM) != 0;
         return !allowReminders && !allowCalls && !allowMessages && !allowEvents
-                && !allowRepeatCallers && !areChannelsBypassingDnd && !allowSystem;
+                && !allowRepeatCallers && !areChannelsBypassingDnd && !allowSystem
+                && !allowConversations;
     }
 
     /**
diff --git a/core/java/android/service/notification/ZenPolicy.java b/core/java/android/service/notification/ZenPolicy.java
index 6e2faa9..87295e1 100644
--- a/core/java/android/service/notification/ZenPolicy.java
+++ b/core/java/android/service/notification/ZenPolicy.java
@@ -41,6 +41,7 @@
     private ArrayList<Integer> mVisualEffects;
     private @PeopleType int mPriorityMessages = PEOPLE_TYPE_UNSET;
     private @PeopleType int mPriorityCalls = PEOPLE_TYPE_UNSET;
+    private @ConversationSenders int mConversationSenders = CONVERSATION_SENDERS_UNSET;
 
     /** @hide */
     @IntDef(prefix = { "PRIORITY_CATEGORY_" }, value = {
@@ -52,6 +53,7 @@
             PRIORITY_CATEGORY_ALARMS,
             PRIORITY_CATEGORY_MEDIA,
             PRIORITY_CATEGORY_SYSTEM,
+            PRIORITY_CATEGORY_CONVERSATIONS,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface PriorityCategory {}
@@ -72,6 +74,8 @@
     public static final int PRIORITY_CATEGORY_MEDIA = 6;
     /** @hide */
     public static final int PRIORITY_CATEGORY_SYSTEM = 7;
+    /** @hide */
+    public static final int PRIORITY_CATEGORY_CONVERSATIONS = 8;
 
     /** @hide */
     @IntDef(prefix = { "VISUAL_EFFECT_" }, value = {
@@ -138,6 +142,37 @@
      */
     public static final int PEOPLE_TYPE_NONE = 4;
 
+
+    /** @hide */
+    @IntDef(prefix = { "CONVERSATION_SENDERS_" }, value = {
+            CONVERSATION_SENDERS_UNSET,
+            CONVERSATION_SENDERS_ANYONE,
+            CONVERSATION_SENDERS_IMPORTANT,
+            CONVERSATION_SENDERS_NONE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ConversationSenders {}
+
+    /**
+     * Used to indicate no preference for the type of conversations that can bypass dnd.
+     */
+    public static final int CONVERSATION_SENDERS_UNSET = 0;
+
+    /**
+     * Used to indicate all conversations can bypass dnd.
+     */
+    public static final int CONVERSATION_SENDERS_ANYONE = 1;
+
+    /**
+     * Used to indicate important conversations can bypass dnd.
+     */
+    public static final int CONVERSATION_SENDERS_IMPORTANT = 2;
+
+    /**
+     * Used to indicate no conversations can bypass dnd.
+     */
+    public static final int CONVERSATION_SENDERS_NONE = 3;
+
     /** @hide */
     @IntDef(prefix = { "STATE_" }, value = {
             STATE_UNSET,
@@ -165,11 +200,20 @@
 
     /** @hide */
     public ZenPolicy() {
-        mPriorityCategories = new ArrayList<>(Collections.nCopies(8, 0));
+        mPriorityCategories = new ArrayList<>(Collections.nCopies(9, 0));
         mVisualEffects = new ArrayList<>(Collections.nCopies(7, 0));
     }
 
     /**
+     * Conversation type that can bypass DND.
+     * @return {@link #CONVERSATION_SENDERS_UNSET}, {@link #CONVERSATION_SENDERS_ANYONE},
+     * {@link #CONVERSATION_SENDERS_IMPORTANT}, {@link #CONVERSATION_SENDERS_NONE}.
+     */
+    public @PeopleType int getPriorityConversationSenders() {
+        return mConversationSenders;
+    }
+
+    /**
      * Message senders that can bypass DND.
      * @return {@link #PEOPLE_TYPE_UNSET}, {@link #PEOPLE_TYPE_ANYONE},
      * {@link #PEOPLE_TYPE_CONTACTS}, {@link #PEOPLE_TYPE_STARRED} or {@link #PEOPLE_TYPE_NONE}
@@ -188,6 +232,16 @@
     }
 
     /**
+     * Whether this policy wants to allow conversation notifications
+     * (see {@link NotificationChannel#getConversationId()}) to play sounds and visually appear
+     * or to intercept them when DND is active.
+     * @return {@link #STATE_UNSET}, {@link #STATE_ALLOW} or {@link #STATE_DISALLOW}
+     */
+    public @State int getPriorityCategoryConversations() {
+        return mPriorityCategories.get(PRIORITY_CATEGORY_CONVERSATIONS);
+    }
+
+    /**
      * Whether this policy wants to allow notifications with category
      * {@link Notification#CATEGORY_REMINDER} to play sounds and visually appear
      * or to intercept them when DND is active.
@@ -392,6 +446,7 @@
             }
             mZenPolicy.mPriorityMessages = PEOPLE_TYPE_ANYONE;
             mZenPolicy.mPriorityCalls = PEOPLE_TYPE_ANYONE;
+            mZenPolicy.mConversationSenders = CONVERSATION_SENDERS_ANYONE;
             return this;
         }
 
@@ -408,6 +463,7 @@
             }
             mZenPolicy.mPriorityMessages = PEOPLE_TYPE_NONE;
             mZenPolicy.mPriorityCalls = PEOPLE_TYPE_NONE;
+            mZenPolicy.mConversationSenders = CONVERSATION_SENDERS_NONE;
             return this;
         }
 
@@ -443,6 +499,8 @@
                 mZenPolicy.mPriorityMessages = STATE_UNSET;
             } else if (category == PRIORITY_CATEGORY_CALLS) {
                 mZenPolicy.mPriorityCalls = STATE_UNSET;
+            } else if (category == PRIORITY_CATEGORY_CONVERSATIONS) {
+                mZenPolicy.mConversationSenders = STATE_UNSET;
             }
 
             return this;
@@ -479,6 +537,31 @@
         }
 
         /**
+         * Whether to allow conversation notifications
+         * (see {@link NotificationChannel#setConversationId(String, String)})
+         * that match audienceType to play sounds and visually appear or to intercept
+         * them when DND is active.
+         * @param audienceType callers that are allowed to bypass DND
+         */
+        public @NonNull  Builder allowConversations(@ConversationSenders int audienceType) {
+            if (audienceType == STATE_UNSET) {
+                return unsetPriorityCategory(PRIORITY_CATEGORY_CONVERSATIONS);
+            }
+
+            if (audienceType == CONVERSATION_SENDERS_NONE) {
+                mZenPolicy.mPriorityCategories.set(PRIORITY_CATEGORY_CONVERSATIONS, STATE_DISALLOW);
+            } else if (audienceType == CONVERSATION_SENDERS_ANYONE
+                    || audienceType == CONVERSATION_SENDERS_IMPORTANT) {
+                mZenPolicy.mPriorityCategories.set(PRIORITY_CATEGORY_CONVERSATIONS, STATE_ALLOW);
+            } else {
+                return this;
+            }
+
+            mZenPolicy.mConversationSenders = audienceType;
+            return this;
+        }
+
+        /**
          * Whether to allow notifications with category {@link Notification#CATEGORY_MESSAGE}
          * that match audienceType to play sounds and visually appear or to intercept
          * them when DND is active.
@@ -537,7 +620,6 @@
             return this;
         }
 
-
         /**
          * Whether to allow notifications with category {@link Notification#CATEGORY_ALARM}
          * to play sounds and visually appear or to intercept them when DND is active.
@@ -712,6 +794,7 @@
         dest.writeList(mVisualEffects);
         dest.writeInt(mPriorityCalls);
         dest.writeInt(mPriorityMessages);
+        dest.writeInt(mConversationSenders);
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<ZenPolicy> CREATOR =
@@ -723,6 +806,7 @@
             policy.mVisualEffects = source.readArrayList(Integer.class.getClassLoader());
             policy.mPriorityCalls = source.readInt();
             policy.mPriorityMessages = source.readInt();
+            policy.mConversationSenders = source.readInt();
             return policy;
         }
 
@@ -738,8 +822,10 @@
                 .append('{')
                 .append("priorityCategories=[").append(priorityCategoriesToString())
                 .append("], visualEffects=[").append(visualEffectsToString())
-                .append("], priorityCalls=").append(peopleTypeToString(mPriorityCalls))
-                .append(", priorityMessages=").append(peopleTypeToString(mPriorityMessages))
+                .append("], priorityCallsSenders=").append(peopleTypeToString(mPriorityCalls))
+                .append(", priorityMessagesSenders=").append(peopleTypeToString(mPriorityMessages))
+                .append(", priorityConversationSenders=").append(
+                        conversationTypeToString(mConversationSenders))
                 .append('}')
                 .toString();
     }
@@ -811,6 +897,8 @@
                 return "media";
             case PRIORITY_CATEGORY_SYSTEM:
                 return "system";
+            case PRIORITY_CATEGORY_CONVERSATIONS:
+                return "convs";
         }
         return null;
     }
@@ -843,6 +931,23 @@
         return "invalidPeopleType{" + peopleType + "}";
     }
 
+    /**
+     * @hide
+     */
+    public static String conversationTypeToString(@ConversationSenders int conversationType) {
+        switch (conversationType) {
+            case CONVERSATION_SENDERS_ANYONE:
+                return "anyone";
+            case CONVERSATION_SENDERS_IMPORTANT:
+                return "important";
+            case CONVERSATION_SENDERS_NONE:
+                return "none";
+            case CONVERSATION_SENDERS_UNSET:
+                return "unset";
+        }
+        return "invalidConversationType{" + conversationType + "}";
+    }
+
     @Override
     public boolean equals(Object o) {
         if (!(o instanceof ZenPolicy)) return false;
@@ -852,12 +957,14 @@
         return Objects.equals(other.mPriorityCategories, mPriorityCategories)
                 && Objects.equals(other.mVisualEffects, mVisualEffects)
                 && other.mPriorityCalls == mPriorityCalls
-                && other.mPriorityMessages == mPriorityMessages;
+                && other.mPriorityMessages == mPriorityMessages
+                && other.mConversationSenders == mConversationSenders;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mPriorityCategories, mVisualEffects, mPriorityCalls, mPriorityMessages);
+        return Objects.hash(mPriorityCategories, mVisualEffects, mPriorityCalls, mPriorityMessages,
+                mConversationSenders);
     }
 
     private @ZenPolicy.State int getZenPolicyPriorityCategoryState(@PriorityCategory int
@@ -879,6 +986,8 @@
                 return getPriorityCategoryMedia();
             case PRIORITY_CATEGORY_SYSTEM:
                 return getPriorityCategorySystem();
+            case PRIORITY_CATEGORY_CONVERSATIONS:
+                return getPriorityCategoryConversations();
         }
         return -1;
     }
@@ -953,6 +1062,9 @@
                 } else if (category == PRIORITY_CATEGORY_CALLS
                         && mPriorityCalls < policyToApply.mPriorityCalls) {
                     mPriorityCalls = policyToApply.mPriorityCalls;
+                } else if (category == PRIORITY_CATEGORY_CONVERSATIONS
+                        && mConversationSenders < policyToApply.mConversationSenders) {
+                    mConversationSenders = policyToApply.mConversationSenders;
                 }
             }
         }
diff --git a/core/java/android/service/textclassifier/TextClassifierService.java b/core/java/android/service/textclassifier/TextClassifierService.java
index 8dca69f..3ff6f54 100644
--- a/core/java/android/service/textclassifier/TextClassifierService.java
+++ b/core/java/android/service/textclassifier/TextClassifierService.java
@@ -27,7 +27,6 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.os.Bundle;
@@ -394,21 +393,39 @@
      */
     @Deprecated
     public final TextClassifier getLocalTextClassifier() {
-        // Deprecated: In the future, we may not guarantee that this runs in the service's process.
-        return getDefaultTextClassifierImplementation(this);
+        return TextClassifier.NO_OP;
     }
 
     /**
      * Returns the platform's default TextClassifier implementation.
+     *
+     * @throws RuntimeException if the TextClassifier from
+     *                          PackageManager#getDefaultTextClassifierPackageName() calls
+     *                          this method.
      */
     @NonNull
     public static TextClassifier getDefaultTextClassifierImplementation(@NonNull Context context) {
         final TextClassificationManager tcm =
                 context.getSystemService(TextClassificationManager.class);
-        if (tcm != null) {
+        if (tcm == null) {
+            return TextClassifier.NO_OP;
+        }
+        TextClassificationConstants settings = new TextClassificationConstants();
+        if (settings.getUseDefaultTextClassifierAsDefaultImplementation()) {
+            final String defaultTextClassifierPackageName =
+                    context.getPackageManager().getDefaultTextClassifierPackageName();
+            if (TextUtils.isEmpty(defaultTextClassifierPackageName)) {
+                return TextClassifier.NO_OP;
+            }
+            if (defaultTextClassifierPackageName.equals(context.getPackageName())) {
+                throw new RuntimeException(
+                        "The default text classifier itself should not call the"
+                                + "getDefaultTextClassifierImplementation() method.");
+            }
+            return tcm.getTextClassifier(TextClassifier.DEFAULT_SERVICE);
+        } else {
             return tcm.getTextClassifier(TextClassifier.LOCAL);
         }
-        return TextClassifier.NO_OP;
     }
 
     /** @hide **/
@@ -434,46 +451,20 @@
     }
 
     /**
-     * Returns the component name of the system default textclassifier service if it can be found
-     * on the system. Otherwise, returns null.
+     * Returns the component name of the textclassifier service from the given package.
+     * Otherwise, returns null.
      *
-     * @param context the text classification context
+     * @param context
+     * @param packageName  the package to look for.
+     * @param resolveFlags the flags that are used by PackageManager to resolve the component name.
      * @hide
      */
     @Nullable
-    public static ComponentName getServiceComponentName(@NonNull Context context) {
-        final TextClassificationConstants settings = TextClassificationManager.getSettings(context);
-        // get override TextClassifierService package name
-        String packageName = settings.getTextClassifierServicePackageOverride();
-
-        ComponentName serviceComponent = null;
-        final boolean isOverrideService = !TextUtils.isEmpty(packageName);
-        if (isOverrideService) {
-            serviceComponent = getServiceComponentNameByPackage(context, packageName,
-                    isOverrideService);
-        }
-        if (serviceComponent != null) {
-            return serviceComponent;
-        }
-        // If no TextClassifierService override or invalid override package name, read the first
-        // package defined in the config
-        final String[] packages = context.getPackageManager().getSystemTextClassifierPackages();
-        if (packages.length == 0 || TextUtils.isEmpty(packages[0])) {
-            Slog.d(LOG_TAG, "No configured system TextClassifierService");
-            return null;
-        }
-        packageName = packages[0];
-        serviceComponent = getServiceComponentNameByPackage(context, packageName,
-                isOverrideService);
-        return serviceComponent;
-    }
-
-    private static ComponentName getServiceComponentNameByPackage(Context context,
-            String packageName, boolean isOverrideService) {
+    public static ComponentName getServiceComponentName(
+            Context context, String packageName, int resolveFlags) {
         final Intent intent = new Intent(SERVICE_INTERFACE).setPackage(packageName);
 
-        final int flags = isOverrideService ? 0 : PackageManager.MATCH_SYSTEM_ONLY;
-        final ResolveInfo ri = context.getPackageManager().resolveService(intent, flags);
+        final ResolveInfo ri = context.getPackageManager().resolveService(intent, resolveFlags);
 
         if ((ri == null) || (ri.serviceInfo == null)) {
             Slog.w(LOG_TAG, String.format("Package or service not found in package %s for user %d",
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index 1966f17..a88d389 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -181,14 +181,14 @@
      * Returned by {@link #getSupportedAudioCapabilities()}
      */
     public static final int AUDIO_CAPABILITY_ECHO_CANCELLATION =
-            SoundTrigger.ModuleProperties.CAPABILITY_ECHO_CANCELLATION;
+            SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_ECHO_CANCELLATION;
 
     /**
      * If set, the underlying module supports noise suppression.
      * Returned by {@link #getSupportedAudioCapabilities()}
      */
     public static final int AUDIO_CAPABILITY_NOISE_SUPPRESSION =
-            SoundTrigger.ModuleProperties.CAPABILITY_NOISE_SUPPRESSION;
+            SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_NOISE_SUPPRESSION;
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
@@ -249,21 +249,21 @@
         }
 
         /**
-         * The inclusive start of supported range.
+         * Get the beginning of the param range
          *
-         * @return start of range
+         * @return The inclusive start of the supported range.
          */
-        public int start() {
-            return mModelParamRange.start;
+        public int getStart() {
+            return mModelParamRange.getStart();
         }
 
         /**
-         * The inclusive end of supported range.
+         * Get the end of the param range
          *
-         * @return end of range
+         * @return The inclusive end of the supported range.
          */
-        public int end() {
-            return mModelParamRange.end;
+        public int getEnd() {
+            return mModelParamRange.getEnd();
         }
 
         @Override
diff --git a/core/java/android/timezone/CountryTimeZones.java b/core/java/android/timezone/CountryTimeZones.java
index 970acd0..ee3a8a7 100644
--- a/core/java/android/timezone/CountryTimeZones.java
+++ b/core/java/android/timezone/CountryTimeZones.java
@@ -18,8 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.SuppressLint;
-import android.annotation.SystemApi;
 import android.icu.util.TimeZone;
 
 import java.util.ArrayList;
@@ -32,7 +30,6 @@
  *
  * @hide
  */
-@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
 public final class CountryTimeZones {
 
     /**
@@ -40,7 +37,6 @@
      *
      * @hide
      */
-    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final class TimeZoneMapping {
 
         @NonNull
@@ -97,7 +93,6 @@
      *
      * @hide
      */
-    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final class OffsetResult {
 
         private final TimeZone mTimeZone;
@@ -210,27 +205,47 @@
     }
 
     /**
-     * Returns a time zone for the country, if there is one, that matches the desired properties. If
-     * there are multiple matches and the {@code bias} is one of them then it is returned, otherwise
-     * an arbitrary match is returned based on the {@link #getEffectiveTimeZoneMappingsAt(long)}
-     * ordering.
+     * Returns a time zone for the country, if there is one, that matches the supplied properties.
+     * If there are multiple matches and the {@code bias} is one of them then it is returned,
+     * otherwise an arbitrary match is returned based on the {@link
+     * #getEffectiveTimeZoneMappingsAt(long)} ordering.
      *
+     * @param whenMillis the UTC time to match against
+     * @param bias the time zone to prefer, can be {@code null} to indicate there is no preference
      * @param totalOffsetMillis the offset from UTC at {@code whenMillis}
      * @param isDst the Daylight Savings Time state at {@code whenMillis}. {@code true} means DST,
-     *     {@code false} means not DST, {@code null} means unknown
-     * @param dstOffsetMillis the part of {@code totalOffsetMillis} contributed by DST, only used if
-     *     {@code isDst} is {@code true}. The value can be {@code null} if the DST offset is
-     *     unknown
-     * @param whenMillis the UTC time to match against
-     * @param bias the time zone to prefer, can be {@code null}
+     *     {@code false} means not DST
+     * @return an {@link OffsetResult} with information about a matching zone, or {@code null} if
+     *     there is no match
      */
     @Nullable
-    public OffsetResult lookupByOffsetWithBias(int totalOffsetMillis, @Nullable Boolean isDst,
-            @SuppressLint("AutoBoxing") @Nullable Integer dstOffsetMillis, long whenMillis,
-            @Nullable TimeZone bias) {
+    public OffsetResult lookupByOffsetWithBias(long whenMillis, @Nullable TimeZone bias,
+            int totalOffsetMillis, boolean isDst) {
         libcore.timezone.CountryTimeZones.OffsetResult delegateOffsetResult =
                 mDelegate.lookupByOffsetWithBias(
-                        totalOffsetMillis, isDst, dstOffsetMillis, whenMillis, bias);
+                        whenMillis, bias, totalOffsetMillis, isDst);
+        return delegateOffsetResult == null ? null :
+                new OffsetResult(
+                        delegateOffsetResult.getTimeZone(), delegateOffsetResult.isOnlyMatch());
+    }
+
+    /**
+     * Returns a time zone for the country, if there is one, that matches the supplied properties.
+     * If there are multiple matches and the {@code bias} is one of them then it is returned,
+     * otherwise an arbitrary match is returned based on the {@link
+     * #getEffectiveTimeZoneMappingsAt(long)} ordering.
+     *
+     * @param whenMillis the UTC time to match against
+     * @param bias the time zone to prefer, can be {@code null} to indicate there is no preference
+     * @param totalOffsetMillis the offset from UTC at {@code whenMillis}
+     * @return an {@link OffsetResult} with information about a matching zone, or {@code null} if
+     *     there is no match
+     */
+    @Nullable
+    public OffsetResult lookupByOffsetWithBias(long whenMillis, @Nullable TimeZone bias,
+            int totalOffsetMillis) {
+        libcore.timezone.CountryTimeZones.OffsetResult delegateOffsetResult =
+                mDelegate.lookupByOffsetWithBias(whenMillis, bias, totalOffsetMillis);
         return delegateOffsetResult == null ? null :
                 new OffsetResult(
                         delegateOffsetResult.getTimeZone(), delegateOffsetResult.isOnlyMatch());
diff --git a/core/java/android/timezone/TelephonyLookup.java b/core/java/android/timezone/TelephonyLookup.java
index 8a5864e..a4c3fbd 100644
--- a/core/java/android/timezone/TelephonyLookup.java
+++ b/core/java/android/timezone/TelephonyLookup.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.SystemApi;
 
 import com.android.internal.annotations.GuardedBy;
 
@@ -29,7 +28,6 @@
  *
  * @hide
  */
-@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
 public final class TelephonyLookup {
 
     private static final Object sLock = new Object();
diff --git a/core/java/android/timezone/TelephonyNetwork.java b/core/java/android/timezone/TelephonyNetwork.java
index 487b3f2..823cd25 100644
--- a/core/java/android/timezone/TelephonyNetwork.java
+++ b/core/java/android/timezone/TelephonyNetwork.java
@@ -17,7 +17,6 @@
 package android.timezone;
 
 import android.annotation.NonNull;
-import android.annotation.SystemApi;
 
 import java.util.Objects;
 
@@ -26,7 +25,6 @@
  *
  * @hide
  */
-@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
 public final class TelephonyNetwork {
 
     @NonNull
diff --git a/core/java/android/timezone/TelephonyNetworkFinder.java b/core/java/android/timezone/TelephonyNetworkFinder.java
index 2ddd3d9..4bfeff8 100644
--- a/core/java/android/timezone/TelephonyNetworkFinder.java
+++ b/core/java/android/timezone/TelephonyNetworkFinder.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.SystemApi;
 
 import java.util.Objects;
 
@@ -27,7 +26,6 @@
  *
  * @hide
  */
-@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
 public final class TelephonyNetworkFinder {
 
     @NonNull
diff --git a/core/java/android/timezone/TimeZoneFinder.java b/core/java/android/timezone/TimeZoneFinder.java
index c76bb1d..03f5013 100644
--- a/core/java/android/timezone/TimeZoneFinder.java
+++ b/core/java/android/timezone/TimeZoneFinder.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.SystemApi;
 
 import com.android.internal.annotations.GuardedBy;
 
@@ -29,7 +28,6 @@
  *
  * @hide
  */
-@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
 public final class TimeZoneFinder {
 
     private static final Object sLock = new Object();
diff --git a/core/java/android/timezone/TzDataSetVersion.java b/core/java/android/timezone/TzDataSetVersion.java
index efe50a0..f993012 100644
--- a/core/java/android/timezone/TzDataSetVersion.java
+++ b/core/java/android/timezone/TzDataSetVersion.java
@@ -17,7 +17,6 @@
 package android.timezone;
 
 import android.annotation.NonNull;
-import android.annotation.SystemApi;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -45,7 +44,6 @@
  * @hide
  */
 @VisibleForTesting
-@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
 public final class TzDataSetVersion {
 
     /**
@@ -88,7 +86,6 @@
      * A checked exception used in connection with time zone data sets.
      * @hide
      */
-    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final class TzDataSetException extends Exception {
 
         /** Creates an instance with a message. */
diff --git a/core/java/android/timezone/ZoneInfoDb.java b/core/java/android/timezone/ZoneInfoDb.java
index 4612a56..9354a69 100644
--- a/core/java/android/timezone/ZoneInfoDb.java
+++ b/core/java/android/timezone/ZoneInfoDb.java
@@ -17,7 +17,6 @@
 package android.timezone;
 
 import android.annotation.NonNull;
-import android.annotation.SystemApi;
 
 import com.android.internal.annotations.GuardedBy;
 
@@ -29,7 +28,6 @@
  *
  * @hide
  */
-@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
 public final class ZoneInfoDb {
 
     private static final Object sLock = new Object();
diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java
index 36f4e53..4b3afba 100644
--- a/core/java/android/util/TimeUtils.java
+++ b/core/java/android/util/TimeUtils.java
@@ -80,7 +80,7 @@
             return null;
         }
         CountryTimeZones.OffsetResult offsetResult = countryTimeZones.lookupByOffsetWithBias(
-                offsetMillis, isDst, null /* dstOffsetMillis */, whenMillis, bias);
+                whenMillis, bias, offsetMillis, isDst);
         return offsetResult != null ? offsetResult.getTimeZone() : null;
     }
 
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index d79fc9a..f61217d 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -1514,6 +1514,7 @@
         public HdrCapabilities(int[] supportedHdrTypes, float maxLuminance,
                 float maxAverageLuminance, float minLuminance) {
             mSupportedHdrTypes = supportedHdrTypes;
+            Arrays.sort(mSupportedHdrTypes);
             mMaxLuminance = maxLuminance;
             mMaxAverageLuminance = maxAverageLuminance;
             mMinLuminance = minLuminance;
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 22d6f37..54de1bb 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -190,9 +190,7 @@
                     onAnimationFinish();
                 }
             });
-            setStartingAnimation(true);
             mAnimator.start();
-            setStartingAnimation(false);
         }
 
         @Override
@@ -203,9 +201,6 @@
             }
         }
 
-        protected void setStartingAnimation(boolean startingAnimation) {
-        }
-
         protected void onAnimationFinish() {
             mController.finish(mShow);
         }
@@ -239,16 +234,6 @@
         final @AnimationType int type;
     }
 
-    private class DefaultAnimationControlListener extends InternalAnimationControlListener {
-        DefaultAnimationControlListener(boolean show) {
-            super(show);
-        }
-        
-        @Override
-        protected void setStartingAnimation(boolean startingAnimation) {
-            mStartingAnimation = startingAnimation;
-        }
-    }
     /**
      * Represents a control request that we had to defer because we are waiting for the IME to
      * process our show request.
@@ -822,7 +807,8 @@
             return;
         }
 
-        final DefaultAnimationControlListener listener = new DefaultAnimationControlListener(show);
+        final InternalAnimationControlListener listener =
+                new InternalAnimationControlListener(show);
         // Show/hide animations always need to be relative to the display frame, in order that shown
         // and hidden state insets are correct.
         controlAnimationUnchecked(
@@ -878,7 +864,9 @@
                     return true;
                 }
                 mViewRoot.mView.dispatchWindowInsetsAnimationStart(animation, bounds);
+                mStartingAnimation = true;
                 listener.onReady(controller, types);
+                mStartingAnimation = false;
                 return true;
             }
         });
diff --git a/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.aidl b/core/java/android/view/SurfaceControlViewHost.aidl
similarity index 73%
copy from core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.aidl
copy to core/java/android/view/SurfaceControlViewHost.aidl
index 3ad903b..3b31ab8 100644
--- a/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.aidl
+++ b/core/java/android/view/SurfaceControlViewHost.aidl
@@ -1,11 +1,11 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
+/**
+ * Copyright (c) 2020, The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0
+ *     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,
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.app.timezonedetector;
+package android.view;
 
-parcelable PhoneTimeZoneSuggestion;
+parcelable SurfaceControlViewHost.SurfacePackage;
\ No newline at end of file
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 75d5538..b1c354f 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -1570,7 +1570,8 @@
     private void reparentSurfacePackage(SurfaceControl.Transaction t,
             SurfaceControlViewHost.SurfacePackage p) {
         // TODO: Link accessibility IDs here.
-        t.reparent(p.getSurfaceControl(), mSurfaceControl);
+        final SurfaceControl sc = p.getSurfaceControl();
+        t.reparent(sc, mSurfaceControl).show(sc);
     }
 
     /**
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index b9f08ad..b4c8795 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -20,7 +20,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
-import android.graphics.Bitmap;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
@@ -829,31 +828,6 @@
     }
 
     /**
-     * Takes the screenshot of the specified display and returns it by bitmap format.
-     *
-     * @param connectionId The id of a connection for interacting with the system.
-     * @param displayId The logic display id, use {@link Display#DEFAULT_DISPLAY} for
-     *                  default display.
-     * @return The screenshot bitmap on success, null otherwise.
-     */
-    public Bitmap takeScreenshot(int connectionId, int displayId) {
-        Bitmap screenShot = null;
-        try {
-            IAccessibilityServiceConnection connection = getConnection(connectionId);
-            if (connection != null) {
-                screenShot = connection.takeScreenshot(displayId);
-            } else {
-                if (DEBUG) {
-                    Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
-                }
-            }
-        } catch (RemoteException re) {
-            Log.w(LOG_TAG, "Error while calling remote takeScreenshot", re);
-        }
-        return screenShot;
-    }
-
-    /**
      * Clears the result state.
      */
     private void clearResultLocked() {
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 184f3302..05cf3ed 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -551,6 +551,14 @@
     public static final String ACTION_ARGUMENT_PRESS_HOLD_DURATION_MILLIS_INT =
             "android.view.accessibility.action.ARGUMENT_PRESS_HOLD_DURATION_MILLIS_INT";
 
+    /**
+     * Argument to represent the IME action Id to press the returning key on a node.
+     * For use with R.id.accessibilityActionImeEnter
+     * @hide
+     */
+    public static final String ACTION_ARGUMENT_IME_ACTION_ID_INT =
+            "android.view.accessibility.action.ARGUMENT_IME_ACTION_ID_INT";
+
     // Focus types
 
     /**
@@ -1644,8 +1652,12 @@
             return false;
         }
         AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+        Bundle arguments = null;
+        if (mExtras != null) {
+            arguments = mExtras;
+        }
         return client.performAccessibilityAction(mConnectionId, mWindowId, mSourceNodeId,
-                action, null);
+                action, arguments);
     }
 
     /**
@@ -4210,6 +4222,8 @@
                 return "ACTION_HIDE_TOOLTIP";
             case R.id.accessibilityActionPressAndHold:
                 return "ACTION_PRESS_AND_HOLD";
+            case R.id.accessibilityActionImeEnter:
+                return "ACTION_IME_ENTER";
             default:
                 return "ACTION_UNKNOWN";
         }
@@ -4839,6 +4853,13 @@
         @NonNull public static final AccessibilityAction ACTION_PRESS_AND_HOLD =
                 new AccessibilityAction(R.id.accessibilityActionPressAndHold);
 
+        /**
+         * Action to send ime action. A node should expose this action only for views that are
+         * currently with input focus and editable.
+         */
+        @NonNull public static final AccessibilityAction ACTION_IME_ENTER =
+                new AccessibilityAction(R.id.accessibilityActionImeEnter);
+
         private final int mActionId;
         private final CharSequence mLabel;
 
diff --git a/core/java/android/view/textclassifier/ConversationActions.java b/core/java/android/view/textclassifier/ConversationActions.java
index 80027b1e..6246b50 100644
--- a/core/java/android/view/textclassifier/ConversationActions.java
+++ b/core/java/android/view/textclassifier/ConversationActions.java
@@ -323,6 +323,7 @@
         private int mUserId = UserHandle.USER_NULL;
         @NonNull
         private Bundle mExtras;
+        private boolean mUseDefaultTextClassifier;
 
         private Request(
                 @NonNull List<Message> conversation,
@@ -347,6 +348,8 @@
             String callingPackageName = in.readString();
             int userId = in.readInt();
             Bundle extras = in.readBundle();
+            boolean useDefaultTextClassifier = in.readBoolean();
+
             Request request = new Request(
                     conversation,
                     typeConfig,
@@ -355,6 +358,7 @@
                     extras);
             request.setCallingPackageName(callingPackageName);
             request.setUserId(userId);
+            request.setUseDefaultTextClassifier(useDefaultTextClassifier);
             return request;
         }
 
@@ -367,6 +371,7 @@
             parcel.writeString(mCallingPackageName);
             parcel.writeInt(mUserId);
             parcel.writeBundle(mExtras);
+            parcel.writeBoolean(mUseDefaultTextClassifier);
         }
 
         @Override
@@ -455,6 +460,26 @@
         }
 
         /**
+         * Sets whether to use the default text classifier to handle this request.
+         * This will be ignored if it is not the system text classifier to handle this request.
+         *
+         * @hide
+         */
+        void setUseDefaultTextClassifier(boolean useDefaultTextClassifier) {
+            mUseDefaultTextClassifier = useDefaultTextClassifier;
+        }
+
+        /**
+         * Returns whether to use the default text classifier to handle this request. This
+         * will be ignored if it is not the system text classifier to handle this request.
+         *
+         * @hide
+         */
+        public boolean getUseDefaultTextClassifier() {
+            return mUseDefaultTextClassifier;
+        }
+
+        /**
          * Returns the extended data related to this request.
          *
          * <p><b>NOTE: </b>Do not modify this bundle.
diff --git a/core/java/android/view/textclassifier/SelectionEvent.java b/core/java/android/view/textclassifier/SelectionEvent.java
index 09cb7a0..e0f29a9 100644
--- a/core/java/android/view/textclassifier/SelectionEvent.java
+++ b/core/java/android/view/textclassifier/SelectionEvent.java
@@ -140,6 +140,7 @@
     private int mEnd;
     private int mSmartStart;
     private int mSmartEnd;
+    private boolean mUseDefaultTextClassifier;
 
     SelectionEvent(
             int start, int end,
@@ -175,6 +176,7 @@
         mSmartStart = in.readInt();
         mSmartEnd = in.readInt();
         mUserId = in.readInt();
+        mUseDefaultTextClassifier = in.readBoolean();
     }
 
     @Override
@@ -204,6 +206,7 @@
         dest.writeInt(mSmartStart);
         dest.writeInt(mSmartEnd);
         dest.writeInt(mUserId);
+        dest.writeBoolean(mUseDefaultTextClassifier);
     }
 
     @Override
@@ -428,6 +431,26 @@
     }
 
     /**
+     * Sets whether to use the default text classifier to handle this request.
+     * This will be ignored if it is not the system text classifier to handle this request.
+     *
+     * @hide
+     */
+    void setUseDefaultTextClassifier(boolean useDefaultTextClassifier) {
+        mUseDefaultTextClassifier = useDefaultTextClassifier;
+    }
+
+    /**
+     * Returns whether to use the default text classifier to handle this request. This
+     * will be ignored if it is not the system text classifier to handle this request.
+     *
+     * @hide
+     */
+    public boolean getUseDefaultTextClassifier() {
+        return mUseDefaultTextClassifier;
+    }
+
+    /**
      * Returns the type of widget that was involved in triggering this event.
      */
     @WidgetType
@@ -642,7 +665,8 @@
         return Objects.hash(mAbsoluteStart, mAbsoluteEnd, mEventType, mEntityType,
                 mWidgetVersion, mPackageName, mUserId, mWidgetType, mInvocationMethod, mResultId,
                 mEventTime, mDurationSinceSessionStart, mDurationSincePreviousEvent,
-                mEventIndex, mSessionId, mStart, mEnd, mSmartStart, mSmartEnd);
+                mEventIndex, mSessionId, mStart, mEnd, mSmartStart, mSmartEnd,
+                mUseDefaultTextClassifier);
     }
 
     @Override
@@ -673,7 +697,8 @@
                 && mStart == other.mStart
                 && mEnd == other.mEnd
                 && mSmartStart == other.mSmartStart
-                && mSmartEnd == other.mSmartEnd;
+                && mSmartEnd == other.mSmartEnd
+                && mUseDefaultTextClassifier == other.mUseDefaultTextClassifier;
     }
 
     @Override
@@ -683,12 +708,13 @@
                         + "widgetVersion=%s, packageName=%s, widgetType=%s, invocationMethod=%s, "
                         + "userId=%d, resultId=%s, eventTime=%d, durationSinceSessionStart=%d, "
                         + "durationSincePreviousEvent=%d, eventIndex=%d,"
-                        + "sessionId=%s, start=%d, end=%d, smartStart=%d, smartEnd=%d}",
+                        + "sessionId=%s, start=%d, end=%d, smartStart=%d, smartEnd=%d, "
+                        + "mUseDefaultTextClassifier=%b}",
                 mAbsoluteStart, mAbsoluteEnd, mEventType, mEntityType,
                 mWidgetVersion, mPackageName, mWidgetType, mInvocationMethod,
                 mUserId, mResultId, mEventTime, mDurationSinceSessionStart,
                 mDurationSincePreviousEvent, mEventIndex,
-                mSessionId, mStart, mEnd, mSmartStart, mSmartEnd);
+                mSessionId, mStart, mEnd, mSmartStart, mSmartEnd, mUseDefaultTextClassifier);
     }
 
     public static final @android.annotation.NonNull Creator<SelectionEvent> CREATOR = new Creator<SelectionEvent>() {
diff --git a/core/java/android/view/textclassifier/SystemTextClassifier.java b/core/java/android/view/textclassifier/SystemTextClassifier.java
index 138d25d..fe5e8d6 100644
--- a/core/java/android/view/textclassifier/SystemTextClassifier.java
+++ b/core/java/android/view/textclassifier/SystemTextClassifier.java
@@ -55,17 +55,20 @@
     // service will throw a remote exception.
     @UserIdInt
     private final int mUserId;
+    private final boolean mUseDefault;
     private TextClassificationSessionId mSessionId;
 
-    public SystemTextClassifier(Context context, TextClassificationConstants settings)
-                throws ServiceManager.ServiceNotFoundException {
+    public SystemTextClassifier(
+            Context context,
+            TextClassificationConstants settings,
+            boolean useDefault) throws ServiceManager.ServiceNotFoundException {
         mManagerService = ITextClassifierService.Stub.asInterface(
                 ServiceManager.getServiceOrThrow(Context.TEXT_CLASSIFICATION_SERVICE));
         mSettings = Objects.requireNonNull(settings);
-        mFallback = context.getSystemService(TextClassificationManager.class)
-                .getTextClassifier(TextClassifier.LOCAL);
+        mFallback = TextClassifier.NO_OP;
         mPackageName = Objects.requireNonNull(context.getOpPackageName());
         mUserId = context.getUserId();
+        mUseDefault = useDefault;
     }
 
     /**
@@ -79,6 +82,7 @@
         try {
             request.setCallingPackageName(mPackageName);
             request.setUserId(mUserId);
+            request.setUseDefaultTextClassifier(mUseDefault);
             final BlockingCallback<TextSelection> callback =
                     new BlockingCallback<>("textselection");
             mManagerService.onSuggestSelection(mSessionId, request, callback);
@@ -103,6 +107,7 @@
         try {
             request.setCallingPackageName(mPackageName);
             request.setUserId(mUserId);
+            request.setUseDefaultTextClassifier(mUseDefault);
             final BlockingCallback<TextClassification> callback =
                     new BlockingCallback<>("textclassification");
             mManagerService.onClassifyText(mSessionId, request, callback);
@@ -124,7 +129,9 @@
     public TextLinks generateLinks(@NonNull TextLinks.Request request) {
         Objects.requireNonNull(request);
         Utils.checkMainThread();
-
+        if (!Utils.checkTextLength(request.getText(), getMaxGenerateLinksTextLength())) {
+            return mFallback.generateLinks(request);
+        }
         if (!mSettings.isSmartLinkifyEnabled() && request.isLegacyFallback()) {
             return Utils.generateLegacyLinks(request);
         }
@@ -132,6 +139,7 @@
         try {
             request.setCallingPackageName(mPackageName);
             request.setUserId(mUserId);
+            request.setUseDefaultTextClassifier(mUseDefault);
             final BlockingCallback<TextLinks> callback =
                     new BlockingCallback<>("textlinks");
             mManagerService.onGenerateLinks(mSessionId, request, callback);
@@ -152,6 +160,7 @@
 
         try {
             event.setUserId(mUserId);
+            event.setUseDefaultTextClassifier(mUseDefault);
             mManagerService.onSelectionEvent(mSessionId, event);
         } catch (RemoteException e) {
             Log.e(LOG_TAG, "Error reporting selection event.", e);
@@ -169,6 +178,7 @@
                             .build()
                     : event.getEventContext();
             tcContext.setUserId(mUserId);
+            tcContext.setUseDefaultTextClassifier(mUseDefault);
             event.setEventContext(tcContext);
             mManagerService.onTextClassifierEvent(mSessionId, event);
         } catch (RemoteException e) {
@@ -184,6 +194,7 @@
         try {
             request.setCallingPackageName(mPackageName);
             request.setUserId(mUserId);
+            request.setUseDefaultTextClassifier(mUseDefault);
             final BlockingCallback<TextLanguage> callback =
                     new BlockingCallback<>("textlanguage");
             mManagerService.onDetectLanguage(mSessionId, request, callback);
@@ -205,6 +216,7 @@
         try {
             request.setCallingPackageName(mPackageName);
             request.setUserId(mUserId);
+            request.setUseDefaultTextClassifier(mUseDefault);
             final BlockingCallback<ConversationActions> callback =
                     new BlockingCallback<>("conversation-actions");
             mManagerService.onSuggestConversationActions(mSessionId, request, callback);
@@ -225,7 +237,7 @@
     @WorkerThread
     public int getMaxGenerateLinksTextLength() {
         // TODO: retrieve this from the bound service.
-        return mFallback.getMaxGenerateLinksTextLength();
+        return mSettings.getGenerateLinksMaxTextLength();
     }
 
     @Override
@@ -247,6 +259,7 @@
         printWriter.printPair("mPackageName", mPackageName);
         printWriter.printPair("mSessionId", mSessionId);
         printWriter.printPair("mUserId", mUserId);
+        printWriter.printPair("mUseDefault",  mUseDefault);
         printWriter.decreaseIndent();
         printWriter.println();
     }
diff --git a/core/java/android/view/textclassifier/TextClassification.java b/core/java/android/view/textclassifier/TextClassification.java
index 3628d2d4..00f762b 100644
--- a/core/java/android/view/textclassifier/TextClassification.java
+++ b/core/java/android/view/textclassifier/TextClassification.java
@@ -555,6 +555,7 @@
         @Nullable private String mCallingPackageName;
         @UserIdInt
         private int mUserId = UserHandle.USER_NULL;
+        private boolean mUseDefaultTextClassifier;
 
         private Request(
                 CharSequence text,
@@ -654,6 +655,26 @@
         }
 
         /**
+         * Sets whether to use the default text classifier to handle this request.
+         * This will be ignored if it is not the system text classifier to handle this request.
+         *
+         * @hide
+         */
+        void setUseDefaultTextClassifier(boolean useDefaultTextClassifier) {
+            mUseDefaultTextClassifier = useDefaultTextClassifier;
+        }
+
+        /**
+         * Returns whether to use the default text classifier to handle this request. This
+         * will be ignored if it is not the system text classifier to handle this request.
+         *
+         * @hide
+         */
+        public boolean getUseDefaultTextClassifier() {
+            return mUseDefaultTextClassifier;
+        }
+
+        /**
          * Returns the extended data.
          *
          * <p><b>NOTE: </b>Do not modify this bundle.
@@ -755,6 +776,7 @@
             dest.writeString(mCallingPackageName);
             dest.writeInt(mUserId);
             dest.writeBundle(mExtras);
+            dest.writeBoolean(mUseDefaultTextClassifier);
         }
 
         private static Request readFromParcel(Parcel in) {
@@ -768,11 +790,13 @@
             final String callingPackageName = in.readString();
             final int userId = in.readInt();
             final Bundle extras = in.readBundle();
+            final boolean useDefaultTextClassifier = in.readBoolean();
 
             final Request request = new Request(text, startIndex, endIndex,
                     defaultLocales, referenceTime, extras);
             request.setCallingPackageName(callingPackageName);
             request.setUserId(userId);
+            request.setUseDefaultTextClassifier(useDefaultTextClassifier);
             return request;
         }
 
diff --git a/core/java/android/view/textclassifier/TextClassificationConstants.java b/core/java/android/view/textclassifier/TextClassificationConstants.java
index ed69513..3d5ac58 100644
--- a/core/java/android/view/textclassifier/TextClassificationConstants.java
+++ b/core/java/android/view/textclassifier/TextClassificationConstants.java
@@ -17,6 +17,7 @@
 package android.view.textclassifier;
 
 import android.annotation.Nullable;
+import android.content.Context;
 import android.provider.DeviceConfig;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -167,6 +168,16 @@
     static final String TEXT_CLASSIFIER_SERVICE_PACKAGE_OVERRIDE =
             "textclassifier_service_package_override";
 
+    /**
+     * Whether to use the default system text classifier as the default text classifier
+     * implementation. The local text classifier is used if it is {@code false}.
+     *
+     * @see android.service.textclassifier.TextClassifierService#getDefaultTextClassifierImplementation(Context)
+     */
+    // TODO: Once the system health experiment is done, remove this together with local TC.
+    private static final String USE_DEFAULT_SYSTEM_TEXT_CLASSIFIER_AS_DEFAULT_IMPL =
+            "use_default_system_text_classifier_as_default_impl";
+
     private static final String DEFAULT_TEXT_CLASSIFIER_SERVICE_PACKAGE_OVERRIDE = null;
     private static final boolean LOCAL_TEXT_CLASSIFIER_ENABLED_DEFAULT = true;
     private static final boolean SYSTEM_TEXT_CLASSIFIER_ENABLED_DEFAULT = true;
@@ -209,7 +220,8 @@
     private static final boolean TEMPLATE_INTENT_FACTORY_ENABLED_DEFAULT = true;
     private static final boolean TRANSLATE_IN_CLASSIFICATION_ENABLED_DEFAULT = true;
     private static final boolean DETECT_LANGUAGES_FROM_TEXT_ENABLED_DEFAULT = true;
-    private static final float[] LANG_ID_CONTEXT_SETTINGS_DEFAULT = new float[] {20f, 1.0f, 0.4f};
+    private static final float[] LANG_ID_CONTEXT_SETTINGS_DEFAULT = new float[]{20f, 1.0f, 0.4f};
+    private static final boolean USE_DEFAULT_SYSTEM_TEXT_CLASSIFIER_AS_DEFAULT_IMPL_DEFAULT = true;
 
     @Nullable
     public String getTextClassifierServicePackageOverride() {
@@ -331,6 +343,13 @@
                 LANG_ID_CONTEXT_SETTINGS, LANG_ID_CONTEXT_SETTINGS_DEFAULT);
     }
 
+    public boolean getUseDefaultTextClassifierAsDefaultImplementation() {
+        return DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
+                USE_DEFAULT_SYSTEM_TEXT_CLASSIFIER_AS_DEFAULT_IMPL,
+                USE_DEFAULT_SYSTEM_TEXT_CLASSIFIER_AS_DEFAULT_IMPL_DEFAULT);
+    }
+
     void dump(IndentingPrintWriter pw) {
         pw.println("TextClassificationConstants:");
         pw.increaseIndent();
@@ -378,6 +397,8 @@
                 .println();
         pw.printPair("textclassifier_service_package_override",
                 getTextClassifierServicePackageOverride()).println();
+        pw.printPair("use_default_system_text_classifier_as_default_impl",
+                getUseDefaultTextClassifierAsDefaultImplementation()).println();
         pw.decreaseIndent();
     }
 
diff --git a/core/java/android/view/textclassifier/TextClassificationContext.java b/core/java/android/view/textclassifier/TextClassificationContext.java
index 930765b..d58d175 100644
--- a/core/java/android/view/textclassifier/TextClassificationContext.java
+++ b/core/java/android/view/textclassifier/TextClassificationContext.java
@@ -38,6 +38,7 @@
     @Nullable private final String mWidgetVersion;
     @UserIdInt
     private int mUserId = UserHandle.USER_NULL;
+    private boolean mUseDefaultTextClassifier;
 
     private TextClassificationContext(
             String packageName,
@@ -76,6 +77,26 @@
     }
 
     /**
+     * Sets whether to use the default text classifier to handle this request.
+     * This will be ignored if it is not the system text classifier to handle this request.
+     *
+     * @hide
+     */
+    void setUseDefaultTextClassifier(boolean useDefaultTextClassifier) {
+        mUseDefaultTextClassifier = useDefaultTextClassifier;
+    }
+
+    /**
+     * Returns whether to use the default text classifier to handle this request. This
+     * will be ignored if it is not the system text classifier to handle this request.
+     *
+     * @hide
+     */
+    public boolean getUseDefaultTextClassifier() {
+        return mUseDefaultTextClassifier;
+    }
+
+    /**
      * Returns the widget type for this classification context.
      */
     @NonNull
@@ -156,6 +177,7 @@
         parcel.writeString(mWidgetType);
         parcel.writeString(mWidgetVersion);
         parcel.writeInt(mUserId);
+        parcel.writeBoolean(mUseDefaultTextClassifier);
     }
 
     private TextClassificationContext(Parcel in) {
@@ -163,6 +185,7 @@
         mWidgetType = in.readString();
         mWidgetVersion = in.readString();
         mUserId = in.readInt();
+        mUseDefaultTextClassifier = in.readBoolean();
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<TextClassificationContext> CREATOR =
diff --git a/core/java/android/view/textclassifier/TextClassificationManager.java b/core/java/android/view/textclassifier/TextClassificationManager.java
index bb96d55..dfbec9b 100644
--- a/core/java/android/view/textclassifier/TextClassificationManager.java
+++ b/core/java/android/view/textclassifier/TextClassificationManager.java
@@ -25,7 +25,6 @@
 import android.os.ServiceManager;
 import android.provider.DeviceConfig;
 import android.provider.DeviceConfig.Properties;
-import android.service.textclassifier.TextClassifierService;
 import android.view.textclassifier.TextClassifier.TextClassifierType;
 
 import com.android.internal.annotations.GuardedBy;
@@ -62,9 +61,6 @@
     @Nullable
     private TextClassifier mLocalTextClassifier;
     @GuardedBy("mLock")
-    @Nullable
-    private TextClassifier mSystemTextClassifier;
-    @GuardedBy("mLock")
     private TextClassificationSessionFactory mSessionFactory;
     @GuardedBy("mLock")
     private TextClassificationConstants mSettings;
@@ -91,8 +87,8 @@
         synchronized (mLock) {
             if (mCustomTextClassifier != null) {
                 return mCustomTextClassifier;
-            } else if (isSystemTextClassifierEnabled()) {
-                return getSystemTextClassifier();
+            } else if (getSettings().isSystemTextClassifierEnabled()) {
+                return getSystemTextClassifier(SystemTextClassifier.SYSTEM);
             } else {
                 return getLocalTextClassifier();
             }
@@ -116,6 +112,7 @@
      *
      * @see TextClassifier#LOCAL
      * @see TextClassifier#SYSTEM
+     * @see TextClassifier#DEFAULT_SERVICE
      * @hide
      */
     @UnsupportedAppUsage
@@ -124,7 +121,7 @@
             case TextClassifier.LOCAL:
                 return getLocalTextClassifier();
             default:
-                return getSystemTextClassifier();
+                return getSystemTextClassifier(type);
         }
     }
 
@@ -204,21 +201,22 @@
         }
     }
 
-    private TextClassifier getSystemTextClassifier() {
+    /** @hide */
+    private TextClassifier getSystemTextClassifier(@TextClassifierType int type) {
         synchronized (mLock) {
-            if (mSystemTextClassifier == null && isSystemTextClassifierEnabled()) {
+            if (getSettings().isSystemTextClassifierEnabled()) {
                 try {
-                    mSystemTextClassifier = new SystemTextClassifier(mContext, getSettings());
-                    Log.d(LOG_TAG, "Initialized SystemTextClassifier");
+                    Log.d(LOG_TAG, "Initializing SystemTextClassifier, type = " + type);
+                    return new SystemTextClassifier(
+                            mContext,
+                            getSettings(),
+                            /* useDefault= */ type == TextClassifier.DEFAULT_SERVICE);
                 } catch (ServiceManager.ServiceNotFoundException e) {
                     Log.e(LOG_TAG, "Could not initialize SystemTextClassifier", e);
                 }
             }
+            return TextClassifier.NO_OP;
         }
-        if (mSystemTextClassifier != null) {
-            return mSystemTextClassifier;
-        }
-        return TextClassifier.NO_OP;
     }
 
     /**
@@ -240,11 +238,6 @@
         }
     }
 
-    private boolean isSystemTextClassifierEnabled() {
-        return getSettings().isSystemTextClassifierEnabled()
-                && TextClassifierService.getServiceComponentName(mContext) != null;
-    }
-
     /** @hide */
     @VisibleForTesting
     public void invalidateForTesting() {
@@ -261,7 +254,6 @@
     private void invalidateTextClassifiers() {
         synchronized (mLock) {
             mLocalTextClassifier = null;
-            mSystemTextClassifier = null;
         }
     }
 
@@ -274,7 +266,8 @@
     /** @hide **/
     public void dump(IndentingPrintWriter pw) {
         getLocalTextClassifier().dump(pw);
-        getSystemTextClassifier().dump(pw);
+        getSystemTextClassifier(TextClassifier.DEFAULT_SERVICE).dump(pw);
+        getSystemTextClassifier(TextClassifier.SYSTEM).dump(pw);
         getSettings().dump(pw);
     }
 
diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java
index 9b33693..2cc226d 100644
--- a/core/java/android/view/textclassifier/TextClassifier.java
+++ b/core/java/android/view/textclassifier/TextClassifier.java
@@ -66,12 +66,14 @@
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef(value = {LOCAL, SYSTEM})
+    @IntDef(value = {LOCAL, SYSTEM, DEFAULT_SERVICE})
     @interface TextClassifierType {}  // TODO: Expose as system APIs.
     /** Specifies a TextClassifier that runs locally in the app's process. @hide */
     int LOCAL = 0;
     /** Specifies a TextClassifier that runs in the system process and serves all apps. @hide */
     int SYSTEM = 1;
+    /** Specifies the default TextClassifier that runs in the system process. @hide */
+    int DEFAULT_SERVICE = 2;
 
     /** The TextClassifier failed to run. */
     String TYPE_UNKNOWN = "";
@@ -667,8 +669,10 @@
             Preconditions.checkArgument(endIndex > startIndex);
         }
 
-        static void checkTextLength(CharSequence text, int maxLength) {
-            Preconditions.checkArgumentInRange(text.length(), 0, maxLength, "text.length()");
+        /** Returns if the length of the text is within the range. */
+        static boolean checkTextLength(CharSequence text, int maxLength) {
+            int textLength = text.length();
+            return textLength >= 0 && textLength <= maxLength;
         }
 
         /**
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index 61bd7c7..d7149ee 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -286,8 +286,10 @@
     @WorkerThread
     public TextLinks generateLinks(@NonNull TextLinks.Request request) {
         Objects.requireNonNull(request);
-        Utils.checkTextLength(request.getText(), getMaxGenerateLinksTextLength());
         Utils.checkMainThread();
+        if (!Utils.checkTextLength(request.getText(), getMaxGenerateLinksTextLength())) {
+            return mFallback.generateLinks(request);
+        }
 
         if (!mSettings.isSmartLinkifyEnabled() && request.isLegacyFallback()) {
             return Utils.generateLegacyLinks(request);
diff --git a/core/java/android/view/textclassifier/TextLanguage.java b/core/java/android/view/textclassifier/TextLanguage.java
index cc9109e..58024dc 100644
--- a/core/java/android/view/textclassifier/TextLanguage.java
+++ b/core/java/android/view/textclassifier/TextLanguage.java
@@ -230,6 +230,7 @@
         @Nullable private String mCallingPackageName;
         @UserIdInt
         private int mUserId = UserHandle.USER_NULL;
+        private boolean mUseDefaultTextClassifier;
 
         private Request(CharSequence text, Bundle bundle) {
             mText = text;
@@ -283,6 +284,26 @@
         }
 
         /**
+         * Sets whether to use the default text classifier to handle this request.
+         * This will be ignored if it is not the system text classifier to handle this request.
+         *
+         * @hide
+         */
+        void setUseDefaultTextClassifier(boolean useDefaultTextClassifier) {
+            mUseDefaultTextClassifier = useDefaultTextClassifier;
+        }
+
+        /**
+         * Returns whether to use the default text classifier to handle this request. This
+         * will be ignored if it is not the system text classifier to handle this request.
+         *
+         * @hide
+         */
+        public boolean getUseDefaultTextClassifier() {
+            return mUseDefaultTextClassifier;
+        }
+
+        /**
          * Returns a bundle containing non-structured extra information about this request.
          *
          * <p><b>NOTE: </b>Do not modify this bundle.
@@ -303,6 +324,7 @@
             dest.writeString(mCallingPackageName);
             dest.writeInt(mUserId);
             dest.writeBundle(mExtra);
+            dest.writeBoolean(mUseDefaultTextClassifier);
         }
 
         private static Request readFromParcel(Parcel in) {
@@ -310,10 +332,12 @@
             final String callingPackageName = in.readString();
             final int userId = in.readInt();
             final Bundle extra = in.readBundle();
+            final boolean useDefaultTextClassifier = in.readBoolean();
 
             final Request request = new Request(text, extra);
             request.setCallingPackageName(callingPackageName);
             request.setUserId(userId);
+            request.setUseDefaultTextClassifier(useDefaultTextClassifier);
             return request;
         }
 
diff --git a/core/java/android/view/textclassifier/TextLinks.java b/core/java/android/view/textclassifier/TextLinks.java
index bda12b0..7430cb3 100644
--- a/core/java/android/view/textclassifier/TextLinks.java
+++ b/core/java/android/view/textclassifier/TextLinks.java
@@ -345,6 +345,7 @@
         @Nullable private final ZonedDateTime mReferenceTime;
         @UserIdInt
         private int mUserId = UserHandle.USER_NULL;
+        private boolean mUseDefaultTextClassifier;
 
         private Request(
                 CharSequence text,
@@ -447,6 +448,26 @@
         }
 
         /**
+         * Sets whether to use the default text classifier to handle this request.
+         * This will be ignored if it is not the system text classifier to handle this request.
+         *
+         * @hide
+         */
+        void setUseDefaultTextClassifier(boolean useDefaultTextClassifier) {
+            mUseDefaultTextClassifier = useDefaultTextClassifier;
+        }
+
+        /**
+         * Returns whether to use the default text classifier to handle this request. This
+         * will be ignored if it is not the system text classifier to handle this request.
+         *
+         * @hide
+         */
+        public boolean getUseDefaultTextClassifier() {
+            return mUseDefaultTextClassifier;
+        }
+
+        /**
          * Returns the extended data.
          *
          * <p><b>NOTE: </b>Do not modify this bundle.
@@ -568,6 +589,7 @@
             dest.writeInt(mUserId);
             dest.writeBundle(mExtras);
             dest.writeString(mReferenceTime == null ? null : mReferenceTime.toString());
+            dest.writeBoolean(mUseDefaultTextClassifier);
         }
 
         private static Request readFromParcel(Parcel in) {
@@ -580,11 +602,13 @@
             final String referenceTimeString = in.readString();
             final ZonedDateTime referenceTime = referenceTimeString == null
                     ? null : ZonedDateTime.parse(referenceTimeString);
+            final boolean useDefaultTextClassifier = in.readBoolean();
 
             final Request request = new Request(text, defaultLocales, entityConfig,
                     /* legacyFallback= */ true, referenceTime, extras);
             request.setCallingPackageName(callingPackageName);
             request.setUserId(userId);
+            request.setUseDefaultTextClassifier(useDefaultTextClassifier);
             return request;
         }
 
diff --git a/core/java/android/view/textclassifier/TextSelection.java b/core/java/android/view/textclassifier/TextSelection.java
index 4a36cbf..575a072 100644
--- a/core/java/android/view/textclassifier/TextSelection.java
+++ b/core/java/android/view/textclassifier/TextSelection.java
@@ -216,6 +216,7 @@
         @Nullable private String mCallingPackageName;
         @UserIdInt
         private int mUserId = UserHandle.USER_NULL;
+        private boolean mUseDefaultTextClassifier;
 
         private Request(
                 CharSequence text,
@@ -316,6 +317,26 @@
         }
 
         /**
+         * Sets whether to use the default text classifier to handle this request.
+         * This will be ignored if it is not the system text classifier to handle this request.
+         *
+         * @hide
+         */
+        void setUseDefaultTextClassifier(boolean useDefaultTextClassifier) {
+            mUseDefaultTextClassifier = useDefaultTextClassifier;
+        }
+
+        /**
+         * Returns whether to use the default text classifier to handle this request. This
+         * will be ignored if it is not the system text classifier to handle this request.
+         *
+         * @hide
+         */
+        public boolean getUseDefaultTextClassifier() {
+            return mUseDefaultTextClassifier;
+        }
+
+        /**
          * Returns the extended data.
          *
          * <p><b>NOTE: </b>Do not modify this bundle.
@@ -420,6 +441,7 @@
             dest.writeString(mCallingPackageName);
             dest.writeInt(mUserId);
             dest.writeBundle(mExtras);
+            dest.writeBoolean(mUseDefaultTextClassifier);
         }
 
         private static Request readFromParcel(Parcel in) {
@@ -430,11 +452,13 @@
             final String callingPackageName = in.readString();
             final int userId = in.readInt();
             final Bundle extras = in.readBundle();
+            final boolean systemTextClassifierType = in.readBoolean();
 
             final Request request = new Request(text, startIndex, endIndex, defaultLocales,
                     /* darkLaunchAllowed= */ false, extras);
             request.setCallingPackageName(callingPackageName);
             request.setUserId(userId);
+            request.setUseDefaultTextClassifier(systemTextClassifierType);
             return request;
         }
 
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 4752ead..9f03d95 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -756,9 +756,6 @@
      */
     private ListItemAccessibilityDelegate mAccessibilityDelegate;
 
-    private int mLastAccessibilityScrollEventFromIndex;
-    private int mLastAccessibilityScrollEventToIndex;
-
     /**
      * Track the item count from the last time we handled a data change.
      */
@@ -1520,25 +1517,10 @@
         onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these.
     }
 
-    /** @hide */
-    @Override
-    public void sendAccessibilityEventUnchecked(AccessibilityEvent event) {
-        // Since this class calls onScrollChanged even if the mFirstPosition and the
-        // child count have not changed we will avoid sending duplicate accessibility
-        // events.
-        if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
-            final int firstVisiblePosition = getFirstVisiblePosition();
-            final int lastVisiblePosition = getLastVisiblePosition();
-            if (mLastAccessibilityScrollEventFromIndex == firstVisiblePosition
-                    && mLastAccessibilityScrollEventToIndex == lastVisiblePosition) {
-                return;
-            } else {
-                mLastAccessibilityScrollEventFromIndex = firstVisiblePosition;
-                mLastAccessibilityScrollEventToIndex = lastVisiblePosition;
-            }
-        }
-        super.sendAccessibilityEventUnchecked(event);
-    }
+    /**
+     * A TYPE_VIEW_SCROLLED event should be sent whenever a scroll happens, even if the
+     * mFirstPosition and the child count have not changed.
+     */
 
     @Override
     public CharSequence getAccessibilityClassName() {
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 7fd4150..4e4a983 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -454,15 +454,17 @@
             aspectRatio = 5.5f;
         }
 
-        final Paint.FontMetrics fontMetrics = mTextView.getPaint().getFontMetrics();
-        final float sourceHeight = fontMetrics.descent - fontMetrics.ascent;
+        final Layout layout = mTextView.getLayout();
+        final int line = layout.getLineForOffset(mTextView.getSelectionStart());
+        final int sourceHeight =
+            layout.getLineBottomWithoutSpacing(line) - layout.getLineTop(line);
         // Slightly increase the height to avoid tooLargeTextForMagnifier() returns true.
         int height = (int)(sourceHeight * zoom) + 2;
         int width = (int)(aspectRatio * height);
 
         params.setFishEyeStyle()
                 .setSize(width, height)
-                .setSourceSize(width, Math.round(sourceHeight))
+                .setSourceSize(width, sourceHeight)
                 .setElevation(0)
                 .setInitialZoom(zoom)
                 .setClippingEnabled(false);
@@ -5041,7 +5043,7 @@
 
             // Vertically snap to middle of current line.
             showPosInView.y = ((mTextView.getLayout().getLineTop(lineNumber)
-                    + mTextView.getLayout().getLineBottom(lineNumber)) / 2.0f
+                    + mTextView.getLayout().getLineBottomWithoutSpacing(lineNumber)) / 2.0f
                     + mTextView.getTotalPaddingTop() - mTextView.getScrollY()) * mTextViewScaleY;
             return true;
         }
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index 79ec680..3c3daa3 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -3255,6 +3255,9 @@
      */
     @UnsupportedAppUsage
     private void scrollListItemsBy(int amount) {
+        int oldX = mScrollX;
+        int oldY = mScrollY;
+
         offsetChildrenTopAndBottom(amount);
 
         final int listBottom = getHeight() - mListPadding.bottom;
@@ -3327,6 +3330,7 @@
         recycleBin.fullyDetachScrapViews();
         removeUnusedFixedViews(mHeaderViewInfos);
         removeUnusedFixedViews(mFooterViewInfos);
+        onScrollChanged(mScrollX, mScrollY, oldX, oldY);
     }
 
     private View addViewAbove(View theView, int position) {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 8ce6ee5..cbfa05c 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -422,6 +422,9 @@
      */
     static final int PROCESS_TEXT_REQUEST_CODE = 100;
 
+    // Accessibility action to send IME custom action for CTS testing.
+    public static final int ACCESSIBILITY_ACTION_IME_ENTER = R.id.accessibilityActionImeEnter;
+
     /**
      *  Return code of {@link #doKeyDown}.
      */
@@ -11741,6 +11744,23 @@
                 info.setContentInvalid(true);
                 info.setError(mEditor.mError);
             }
+            // TextView will expose this action if it is editable and has focus.
+            if (isTextEditable() && isFocused()) {
+                CharSequence imeActionLabel = mContext.getResources().getString(
+                        com.android.internal.R.string.keyboardview_keycode_enter);
+                if (getImeActionId() != 0 && getImeActionLabel() != null) {
+                    imeActionLabel = getImeActionLabel();
+                    final int imeActionId = getImeActionId();
+                    // put ime action id into the extra data with ACTION_ARGUMENT_IME_ACTION_ID_INT.
+                    final Bundle argument = info.getExtras();
+                    argument.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_IME_ACTION_ID_INT,
+                            imeActionId);
+                }
+                AccessibilityNodeInfo.AccessibilityAction action =
+                        new AccessibilityNodeInfo.AccessibilityAction(
+                                ACCESSIBILITY_ACTION_IME_ENTER, imeActionLabel);
+                info.addAction(action);
+            }
         }
 
         if (!TextUtils.isEmpty(mText)) {
@@ -12052,6 +12072,17 @@
                     }
                 }
             } return true;
+            case ACCESSIBILITY_ACTION_IME_ENTER: {
+                if (isFocused() && isTextEditable()) {
+                    final int imeActionId = (arguments != null) ? arguments.getInt(
+                            AccessibilityNodeInfo.ACTION_ARGUMENT_IME_ACTION_ID_INT,
+                            EditorInfo.IME_ACTION_UNSPECIFIED)
+                            : EditorInfo.IME_ACTION_UNSPECIFIED;
+                    if (imeActionId == getImeActionId()) {
+                        onEditorAction(imeActionId);
+                    }
+                }
+            } return true;
             default: {
                 return super.performAccessibilityActionInternal(action, arguments);
             }
diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java
index 78d4e61..a2c70b9 100644
--- a/core/java/android/widget/Toast.java
+++ b/core/java/android/widget/Toast.java
@@ -148,6 +148,9 @@
     @Nullable
     private CharSequence mText;
 
+    // TODO(b/144152069): Remove this after assessing impact on dogfood.
+    private boolean mIsCustomToast;
+
     /**
      * Construct an empty Toast object.  You must call {@link #setView} before you
      * can call {@link #show}.
@@ -214,7 +217,8 @@
                     service.enqueueTextToast(pkg, mToken, mText, mDuration, displayId, callback);
                 }
             } else {
-                service.enqueueToast(pkg, mToken, tn, mDuration, displayId);
+                service.enqueueTextOrCustomToast(pkg, mToken, tn, mDuration, displayId,
+                        mIsCustomToast);
             }
         } catch (RemoteException e) {
             // Empty
@@ -252,12 +256,17 @@
      */
     @Deprecated
     public void setView(View view) {
+        mIsCustomToast = true;
         mNextView = view;
     }
 
     /**
      * Return the view.
      *
+     * <p><strong>Warning:</strong> Starting from Android {@link Build.VERSION_CODES#R}, for apps
+     * targeting API level {@link Build.VERSION_CODES#R} or higher that haven't called {@link
+     * #setView(View)} with a non-{@code null} view, this method will return {@code null}.
+     *
      * @see #setView
      * @deprecated Custom toast views are deprecated. Apps can create a standard text toast with the
      *      {@link #makeText(Context, CharSequence, int)} method, or use a
@@ -266,6 +275,7 @@
      *      targeting API level {@link Build.VERSION_CODES#R} or higher that are in the background
      *      will not have custom toast views displayed.
      */
+    @Deprecated
     public View getView() {
         return mNextView;
     }
@@ -292,6 +302,10 @@
     /**
      * Set the margins of the view.
      *
+     * <p><strong>Warning:</strong> Starting from Android {@link Build.VERSION_CODES#R}, for apps
+     * targeting API level {@link Build.VERSION_CODES#R} or higher, this method is a no-op when
+     * called on text toasts.
+     *
      * @param horizontalMargin The horizontal margin, in percentage of the
      *        container width, between the container's edges and the
      *        notification
@@ -300,30 +314,59 @@
      *        notification
      */
     public void setMargin(float horizontalMargin, float verticalMargin) {
+        if (isSystemRenderedTextToast()) {
+            Log.e(TAG, "setMargin() shouldn't be called on text toasts, the values won't be used");
+        }
         mTN.mHorizontalMargin = horizontalMargin;
         mTN.mVerticalMargin = verticalMargin;
     }
 
     /**
      * Return the horizontal margin.
+     *
+     * <p><strong>Warning:</strong> Starting from Android {@link Build.VERSION_CODES#R}, for apps
+     * targeting API level {@link Build.VERSION_CODES#R} or higher, this method shouldn't be called
+     * on text toasts as its return value may not reflect actual value since text toasts are not
+     * rendered by the app anymore.
      */
     public float getHorizontalMargin() {
+        if (isSystemRenderedTextToast()) {
+            Log.e(TAG, "getHorizontalMargin() shouldn't be called on text toasts, the result may "
+                    + "not reflect actual values.");
+        }
         return mTN.mHorizontalMargin;
     }
 
     /**
      * Return the vertical margin.
+     *
+     * <p><strong>Warning:</strong> Starting from Android {@link Build.VERSION_CODES#R}, for apps
+     * targeting API level {@link Build.VERSION_CODES#R} or higher, this method shouldn't be called
+     * on text toasts as its return value may not reflect actual value since text toasts are not
+     * rendered by the app anymore.
      */
     public float getVerticalMargin() {
+        if (isSystemRenderedTextToast()) {
+            Log.e(TAG, "getVerticalMargin() shouldn't be called on text toasts, the result may not"
+                    + " reflect actual values.");
+        }
         return mTN.mVerticalMargin;
     }
 
     /**
      * Set the location at which the notification should appear on the screen.
+     *
+     * <p><strong>Warning:</strong> Starting from Android {@link Build.VERSION_CODES#R}, for apps
+     * targeting API level {@link Build.VERSION_CODES#R} or higher, this method is a no-op when
+     * called on text toasts.
+     *
      * @see android.view.Gravity
      * @see #getGravity
      */
     public void setGravity(int gravity, int xOffset, int yOffset) {
+        if (isSystemRenderedTextToast()) {
+            Log.e(TAG, "setGravity() shouldn't be called on text toasts, the values won't be used");
+        }
         mTN.mGravity = gravity;
         mTN.mX = xOffset;
         mTN.mY = yOffset;
@@ -331,27 +374,59 @@
 
      /**
      * Get the location at which the notification should appear on the screen.
+     *
+     * <p><strong>Warning:</strong> Starting from Android {@link Build.VERSION_CODES#R}, for apps
+     * targeting API level {@link Build.VERSION_CODES#R} or higher, this method shouldn't be called
+     * on text toasts as its return value may not reflect actual value since text toasts are not
+     * rendered by the app anymore.
+     *
      * @see android.view.Gravity
      * @see #getGravity
      */
     public int getGravity() {
+        if (isSystemRenderedTextToast()) {
+            Log.e(TAG, "getGravity() shouldn't be called on text toasts, the result may not reflect"
+                    + " actual values.");
+        }
         return mTN.mGravity;
     }
 
     /**
      * Return the X offset in pixels to apply to the gravity's location.
+     *
+     * <p><strong>Warning:</strong> Starting from Android {@link Build.VERSION_CODES#R}, for apps
+     * targeting API level {@link Build.VERSION_CODES#R} or higher, this method shouldn't be called
+     * on text toasts as its return value may not reflect actual value since text toasts are not
+     * rendered by the app anymore.
      */
     public int getXOffset() {
+        if (isSystemRenderedTextToast()) {
+            Log.e(TAG, "getXOffset() shouldn't be called on text toasts, the result may not reflect"
+                    + " actual values.");
+        }
         return mTN.mX;
     }
 
     /**
      * Return the Y offset in pixels to apply to the gravity's location.
+     *
+     * <p><strong>Warning:</strong> Starting from Android {@link Build.VERSION_CODES#R}, for apps
+     * targeting API level {@link Build.VERSION_CODES#R} or higher, this method shouldn't be called
+     * on text toasts as its return value may not reflect actual value since text toasts are not
+     * rendered by the app anymore.
      */
     public int getYOffset() {
+        if (isSystemRenderedTextToast()) {
+            Log.e(TAG, "getYOffset() shouldn't be called on text toasts, the result may not reflect"
+                    + " actual values.");
+        }
         return mTN.mY;
     }
 
+    private boolean isSystemRenderedTextToast() {
+        return Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM) && mNextView == null;
+    }
+
     /**
      * Adds a callback to be notified when the toast is shown or hidden.
      *
@@ -385,7 +460,7 @@
     }
 
     /**
-     * Make a standard toast that just contains a text view.
+     * Make a standard toast that just contains text.
      *
      * @param context  The context to use.  Usually your {@link android.app.Application}
      *                 or {@link android.app.Activity} object.
@@ -428,7 +503,7 @@
     }
 
     /**
-     * Make a standard toast that just contains a text view with the text from a resource.
+     * Make a standard toast that just contains text from a resource.
      *
      * @param context  The context to use.  Usually your {@link android.app.Application}
      *                 or {@link android.app.Activity} object.
diff --git a/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
index 6a6a60d..82eb55a 100644
--- a/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
+++ b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
@@ -267,8 +267,7 @@
         targets.addAll(getAccessibilityServiceTargets(context));
         targets.addAll(getWhiteListingServiceTargets(context));
 
-        final AccessibilityManager ams = (AccessibilityManager) context.getSystemService(
-                Context.ACCESSIBILITY_SERVICE);
+        final AccessibilityManager ams = context.getSystemService(AccessibilityManager.class);
         final List<String> requiredTargets = ams.getAccessibilityShortcutTargets(shortcutType);
         targets.removeIf(target -> !requiredTargets.contains(target.getId()));
 
@@ -277,8 +276,7 @@
 
     private static List<AccessibilityButtonTarget> getAccessibilityServiceTargets(
             @NonNull Context context) {
-        final AccessibilityManager ams = (AccessibilityManager) context.getSystemService(
-                Context.ACCESSIBILITY_SERVICE);
+        final AccessibilityManager ams = context.getSystemService(AccessibilityManager.class);
         final List<AccessibilityServiceInfo> installedServices =
                 ams.getInstalledAccessibilityServiceList();
         if (installedServices == null) {
@@ -354,10 +352,11 @@
     }
 
     private static class ViewHolder {
+        View mItemView;
         ImageView mIconView;
         TextView mLabelView;
         FrameLayout mItemContainer;
-        ImageView mViewItem;
+        ImageView mActionViewItem;
         Switch mSwitchItem;
     }
 
@@ -407,12 +406,13 @@
                         R.layout.accessibility_button_chooser_item, parent, /* attachToRoot= */
                         false);
                 holder = new ViewHolder();
+                holder.mItemView = convertView;
                 holder.mIconView = convertView.findViewById(R.id.accessibility_button_target_icon);
                 holder.mLabelView = convertView.findViewById(
                         R.id.accessibility_button_target_label);
                 holder.mItemContainer = convertView.findViewById(
                         R.id.accessibility_button_target_item_container);
-                holder.mViewItem = convertView.findViewById(
+                holder.mActionViewItem = convertView.findViewById(
                         R.id.accessibility_button_target_view_item);
                 holder.mSwitchItem = convertView.findViewById(
                         R.id.accessibility_button_target_switch_item);
@@ -465,11 +465,12 @@
             holder.mIconView.setAlpha(enabledState
                     ? ENABLED_ALPHA : DISABLED_ALPHA);
             holder.mLabelView.setEnabled(enabledState);
-            holder.mViewItem.setEnabled(enabledState);
-            holder.mViewItem.setImageDrawable(context.getDrawable(R.drawable.ic_delete_item));
-            holder.mViewItem.setVisibility(View.VISIBLE);
+            holder.mActionViewItem.setEnabled(enabledState);
+            holder.mActionViewItem.setImageDrawable(context.getDrawable(R.drawable.ic_delete_item));
+            holder.mActionViewItem.setVisibility(View.VISIBLE);
             holder.mSwitchItem.setVisibility(View.GONE);
             holder.mItemContainer.setVisibility(isLaunchMenuMode ? View.GONE : View.VISIBLE);
+            holder.mItemView.setEnabled(enabledState);
         }
 
         private void updateInvisibleActionItemVisibility(@NonNull Context context,
@@ -477,12 +478,13 @@
             holder.mIconView.setColorFilter(null);
             holder.mIconView.setAlpha(ENABLED_ALPHA);
             holder.mLabelView.setEnabled(true);
-            holder.mViewItem.setEnabled(true);
-            holder.mViewItem.setImageDrawable(context.getDrawable(R.drawable.ic_delete_item));
-            holder.mViewItem.setVisibility(View.VISIBLE);
+            holder.mActionViewItem.setEnabled(true);
+            holder.mActionViewItem.setImageDrawable(context.getDrawable(R.drawable.ic_delete_item));
+            holder.mActionViewItem.setVisibility(View.VISIBLE);
             holder.mSwitchItem.setVisibility(View.GONE);
             holder.mItemContainer.setVisibility((mShortcutMenuMode == ShortcutMenuMode.EDIT)
                     ? View.VISIBLE : View.GONE);
+            holder.mItemView.setEnabled(true);
         }
 
         private void updateIntuitiveActionItemVisibility(@NonNull Context context,
@@ -495,12 +497,13 @@
             holder.mIconView.setColorFilter(null);
             holder.mIconView.setAlpha(ENABLED_ALPHA);
             holder.mLabelView.setEnabled(true);
-            holder.mViewItem.setEnabled(true);
-            holder.mViewItem.setImageDrawable(context.getDrawable(R.drawable.ic_delete_item));
-            holder.mViewItem.setVisibility(isEditMenuMode ? View.VISIBLE : View.GONE);
+            holder.mActionViewItem.setEnabled(true);
+            holder.mActionViewItem.setImageDrawable(context.getDrawable(R.drawable.ic_delete_item));
+            holder.mActionViewItem.setVisibility(isEditMenuMode ? View.VISIBLE : View.GONE);
             holder.mSwitchItem.setVisibility(isEditMenuMode ? View.GONE : View.VISIBLE);
             holder.mSwitchItem.setChecked(!isEditMenuMode && isServiceEnabled);
             holder.mItemContainer.setVisibility(View.VISIBLE);
+            holder.mItemView.setEnabled(true);
         }
 
         private void updateBounceActionItemVisibility(@NonNull Context context,
@@ -508,12 +511,13 @@
             holder.mIconView.setColorFilter(null);
             holder.mIconView.setAlpha(ENABLED_ALPHA);
             holder.mLabelView.setEnabled(true);
-            holder.mViewItem.setEnabled(true);
-            holder.mViewItem.setImageDrawable(context.getDrawable(R.drawable.ic_delete_item));
-            holder.mViewItem.setVisibility((mShortcutMenuMode == ShortcutMenuMode.EDIT)
+            holder.mActionViewItem.setEnabled(true);
+            holder.mActionViewItem.setImageDrawable(context.getDrawable(R.drawable.ic_delete_item));
+            holder.mActionViewItem.setVisibility((mShortcutMenuMode == ShortcutMenuMode.EDIT)
                     ? View.VISIBLE : View.GONE);
             holder.mSwitchItem.setVisibility(View.GONE);
             holder.mItemContainer.setVisibility(View.VISIBLE);
+            holder.mItemView.setEnabled(true);
         }
     }
 
@@ -559,8 +563,7 @@
 
     private static boolean isAccessibilityServiceEnabled(@NonNull Context context,
             AccessibilityButtonTarget target) {
-        final AccessibilityManager ams = (AccessibilityManager) context.getSystemService(
-                Context.ACCESSIBILITY_SERVICE);
+        final AccessibilityManager ams = context.getSystemService(AccessibilityManager.class);
         final List<AccessibilityServiceInfo> enabledServices =
                 ams.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
 
@@ -598,8 +601,7 @@
 
     private void onLegacyTargetSelected(AccessibilityButtonTarget target) {
         if (mShortcutType == ACCESSIBILITY_BUTTON) {
-            final AccessibilityManager ams = (AccessibilityManager) getSystemService(
-                    Context.ACCESSIBILITY_SERVICE);
+            final AccessibilityManager ams = getSystemService(AccessibilityManager.class);
             ams.notifyAccessibilityButtonClicked(getDisplayId(), target.getId());
         } else if (mShortcutType == ACCESSIBILITY_SHORTCUT_KEY) {
             switchServiceState(target);
@@ -607,9 +609,12 @@
     }
 
     private void onInvisibleTargetSelected(AccessibilityButtonTarget target) {
-        final AccessibilityManager ams = (AccessibilityManager) getSystemService(
-                Context.ACCESSIBILITY_SERVICE);
-        ams.notifyAccessibilityButtonClicked(getDisplayId(), target.getId());
+        final AccessibilityManager ams = getSystemService(AccessibilityManager.class);
+        if (mShortcutType == ACCESSIBILITY_BUTTON) {
+            ams.notifyAccessibilityButtonClicked(getDisplayId(), target.getId());
+        } else if (mShortcutType == ACCESSIBILITY_SHORTCUT_KEY) {
+            ams.performAccessibilityShortcut(target.getId());
+        }
     }
 
     private void onIntuitiveTargetSelected(AccessibilityButtonTarget target) {
diff --git a/core/java/com/android/internal/app/BlockedAppActivity.java b/core/java/com/android/internal/app/BlockedAppActivity.java
new file mode 100644
index 0000000..fbdbbfb
--- /dev/null
+++ b/core/java/com/android/internal/app/BlockedAppActivity.java
@@ -0,0 +1,86 @@
+/*
+ * 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.internal.app;
+
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.internal.R;
+
+/**
+ * A dialog shown to the user when they try to launch an app that is not allowed in lock task
+ * mode. The intent to start this activity must be created with the static factory method provided
+ * below.
+ */
+public class BlockedAppActivity extends AlertActivity {
+
+    private static final String TAG = "BlockedAppActivity";
+    private static final String PACKAGE_NAME = "com.android.internal.app";
+    private static final String EXTRA_BLOCKED_PACKAGE = PACKAGE_NAME + ".extra.BLOCKED_PACKAGE";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Intent intent = getIntent();
+        int userId = intent.getIntExtra(Intent.EXTRA_USER_ID, /* defaultValue= */ -1);
+        if (userId < 0) {
+            Slog.wtf(TAG, "Invalid user: " + userId);
+            finish();
+            return;
+        }
+
+        String packageName = intent.getStringExtra(EXTRA_BLOCKED_PACKAGE);
+        if (TextUtils.isEmpty(packageName)) {
+            Slog.wtf(TAG, "Invalid package: " + packageName);
+            finish();
+            return;
+        }
+
+        CharSequence appLabel = getAppLabel(userId, packageName);
+
+        mAlertParams.mTitle = getString(R.string.app_blocked_title);
+        mAlertParams.mMessage = getString(R.string.app_blocked_message, appLabel);
+        mAlertParams.mPositiveButtonText = getString(android.R.string.ok);
+        setupAlert();
+    }
+
+    private CharSequence getAppLabel(int userId, String packageName) {
+        PackageManager pm = getPackageManager();
+        try {
+            ApplicationInfo aInfo =
+                    pm.getApplicationInfoAsUser(packageName, /* flags= */ 0, userId);
+            return aInfo.loadLabel(pm);
+        } catch (PackageManager.NameNotFoundException ne) {
+            Slog.e(TAG, "Package " + packageName + " not found", ne);
+        }
+        return packageName;
+    }
+
+
+    /** Creates an intent that launches {@link BlockedAppActivity}. */
+    public static Intent createIntent(int userId, String packageName) {
+        return new Intent()
+                .setClassName("android", BlockedAppActivity.class.getName())
+                .putExtra(Intent.EXTRA_USER_ID, userId)
+                .putExtra(EXTRA_BLOCKED_PACKAGE, packageName);
+    }
+}
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index a43e4fe..65cad83 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -2424,6 +2424,10 @@
                     offset += findViewById(R.id.content_preview_container).getHeight();
                 }
 
+                if (hasWorkProfile() && ENABLE_TABBED_VIEW) {
+                    offset += findViewById(R.id.tabs).getHeight();
+                }
+
                 int directShareHeight = 0;
                 rowsToShow = Math.min(4, rowsToShow);
                 for (int i = 0, childCount = recyclerView.getChildCount();
diff --git a/core/java/com/android/internal/net/VpnConfig.java b/core/java/com/android/internal/net/VpnConfig.java
index f5a19fe..6d2d735 100644
--- a/core/java/com/android/internal/net/VpnConfig.java
+++ b/core/java/com/android/internal/net/VpnConfig.java
@@ -52,6 +52,7 @@
 
     public static final String DIALOGS_PACKAGE = "com.android.vpndialogs";
 
+    // TODO: Rename this to something that encompasses Settings-based Platform VPNs as well.
     public static final String LEGACY_VPN = "[Legacy VPN]";
 
     public static Intent getIntentForConfirmation() {
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index c6ed624..518911e 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -323,7 +323,7 @@
         result.append(System.getProperty("java.vm.version")); // such as 1.1.0
         result.append(" (Linux; U; Android ");
 
-        String version = Build.VERSION.RELEASE; // "1.0" or "3.4b5"
+        String version = Build.VERSION.RELEASE_OR_CODENAME; // "1.0" or "3.4b5"
         result.append(version.length() > 0 ? version : "1.0");
 
         // add the model for the release build
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index cec68df..35eb0fc 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -35,8 +35,13 @@
         "android_animation_PropertyValuesHolder.cpp",
         "android_os_SystemClock.cpp",
         "android_os_SystemProperties.cpp",
+        "android_os_Trace.cpp",
+        "android_text_AndroidCharacter.cpp",
         "android_util_EventLog.cpp",
         "android_util_Log.cpp",
+        "android_util_StringBlock.cpp",
+        "android_util_XmlBlock.cpp",
+        "android_view_RenderNodeAnimator.cpp",
         "com_android_internal_util_VirtualRefBasePtr.cpp",
         "com_android_internal_view_animation_NativeInterpolatorFactoryHelper.cpp",
     ],
@@ -114,14 +119,12 @@
                 "android_view_KeyEvent.cpp",
                 "android_view_MotionEvent.cpp",
                 "android_view_PointerIcon.cpp",
-                "android_view_RenderNodeAnimator.cpp",
                 "android_view_Surface.cpp",
                 "android_view_SurfaceControl.cpp",
                 "android_graphics_BLASTBufferQueue.cpp",
                 "android_view_SurfaceSession.cpp",
                 "android_view_TextureView.cpp",
                 "android_view_VelocityTracker.cpp",
-                "android_text_AndroidCharacter.cpp",
                 "android_text_Hyphenator.cpp",
                 "android_os_Debug.cpp",
                 "android_os_GraphicsEnvironment.cpp",
@@ -138,7 +141,6 @@
                 "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",
                 "android_os_VintfRuntimeInfo.cpp",
@@ -150,8 +152,6 @@
                 "android_util_Binder.cpp",
                 "android_util_MemoryIntArray.cpp",
                 "android_util_Process.cpp",
-                "android_util_StringBlock.cpp",
-                "android_util_XmlBlock.cpp",
                 "android_util_jar_StrictJarFile.cpp",
                 "android_media_AudioDeviceAddress.cpp",
                 "android_media_AudioEffectDescriptor.cpp",
@@ -208,6 +208,7 @@
 
             static_libs: [
                 "libasync_safe",
+                "libbinderthreadstateutils",
                 "libdmabufinfo",
                 "libgif",
                 "libseccomp_policy",
@@ -311,11 +312,8 @@
             srcs: [
                 "android_content_res_ApkAssets.cpp",
                 "android_os_MessageQueue.cpp",
-                "android_os_Trace.cpp",
                 "android_util_AssetManager.cpp",
                 "android_util_FileObserver.cpp",
-                "android_util_StringBlock.cpp",
-                "android_util_XmlBlock.cpp",
             ],
         },
     },
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 481be24..657336e 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -634,8 +634,6 @@
     char cachePruneBuf[sizeof("-Xzygote-max-boot-retry=")-1 + PROPERTY_VALUE_MAX];
     char dex2oatXmsImageFlagsBuf[sizeof("-Xms")-1 + PROPERTY_VALUE_MAX];
     char dex2oatXmxImageFlagsBuf[sizeof("-Xmx")-1 + PROPERTY_VALUE_MAX];
-    char dex2oatXmsFlagsBuf[sizeof("-Xms")-1 + PROPERTY_VALUE_MAX];
-    char dex2oatXmxFlagsBuf[sizeof("-Xmx")-1 + PROPERTY_VALUE_MAX];
     char dex2oatCompilerFilterBuf[sizeof("--compiler-filter=")-1 + PROPERTY_VALUE_MAX];
     char dex2oatImageCompilerFilterBuf[sizeof("--compiler-filter=")-1 + PROPERTY_VALUE_MAX];
     char dex2oatThreadsBuf[sizeof("-j")-1 + PROPERTY_VALUE_MAX];
@@ -885,88 +883,45 @@
     bool skip_compilation = ((strcmp(voldDecryptBuf, "trigger_restart_min_framework") == 0) ||
                              (strcmp(voldDecryptBuf, "1") == 0));
 
-    // Extra options for boot.art/boot.oat image generation.
-    parseCompilerRuntimeOption("dalvik.vm.image-dex2oat-Xms", dex2oatXmsImageFlagsBuf,
-                               "-Xms", "-Ximage-compiler-option");
-    parseCompilerRuntimeOption("dalvik.vm.image-dex2oat-Xmx", dex2oatXmxImageFlagsBuf,
-                               "-Xmx", "-Ximage-compiler-option");
-    if (skip_compilation) {
-        addOption("-Ximage-compiler-option");
-        addOption("--compiler-filter=assume-verified");
-    } else {
-        parseCompilerOption("dalvik.vm.image-dex2oat-filter", dex2oatImageCompilerFilterBuf,
-                            "--compiler-filter=", "-Ximage-compiler-option");
-    }
-
-    // If there is a boot profile, it takes precedence over the image and preloaded classes.
-    if (hasFile("/system/etc/boot-image.prof")) {
-        addOption("-Ximage-compiler-option");
-        addOption("--profile-file=/system/etc/boot-image.prof");
-        addOption("-Ximage-compiler-option");
-        addOption("--compiler-filter=speed-profile");
-    } else {
-        ALOGE("Missing boot-image.prof file, /system/etc/boot-image.prof not found: %s\n",
-              strerror(errno));
-        return -1;
-    }
-
-
-    // If there is a dirty-image-objects file, push it.
-    if (hasFile("/system/etc/dirty-image-objects")) {
-        addOption("-Ximage-compiler-option");
-        addOption("--dirty-image-objects=/system/etc/dirty-image-objects");
-    }
-
-    property_get("dalvik.vm.image-dex2oat-flags", dex2oatImageFlagsBuf, "");
-    parseExtraOpts(dex2oatImageFlagsBuf, "-Ximage-compiler-option");
-
-    // Extra options for DexClassLoader.
-    parseCompilerRuntimeOption("dalvik.vm.dex2oat-Xms", dex2oatXmsFlagsBuf,
-                               "-Xms", "-Xcompiler-option");
-    parseCompilerRuntimeOption("dalvik.vm.dex2oat-Xmx", dex2oatXmxFlagsBuf,
-                               "-Xmx", "-Xcompiler-option");
+    // Extra options for JIT.
     if (skip_compilation) {
         addOption("-Xcompiler-option");
         addOption("--compiler-filter=assume-verified");
-
-        // We skip compilation when a minimal runtime is brought up for decryption. In that case
-        // /data is temporarily backed by a tmpfs, which is usually small.
-        // If the system image contains prebuilts, they will be relocated into the tmpfs. In this
-        // specific situation it is acceptable to *not* relocate and run out of the prebuilts
-        // directly instead.
-        addOption("--runtime-arg");
-        addOption("-Xnorelocate");
     } else {
         parseCompilerOption("dalvik.vm.dex2oat-filter", dex2oatCompilerFilterBuf,
                             "--compiler-filter=", "-Xcompiler-option");
     }
     parseCompilerOption("dalvik.vm.dex2oat-threads", dex2oatThreadsBuf, "-j", "-Xcompiler-option");
-    parseCompilerOption("dalvik.vm.image-dex2oat-threads", dex2oatThreadsImageBuf, "-j",
-                        "-Ximage-compiler-option");
     parseCompilerOption("dalvik.vm.dex2oat-cpu-set", dex2oatCpuSetBuf, "--cpu-set=",
                         "-Xcompiler-option");
-    parseCompilerOption("dalvik.vm.image-dex2oat-cpu-set", dex2oatCpuSetImageBuf, "--cpu-set=",
-                        "-Ximage-compiler-option");
-
-    // The runtime will compile a boot image, when necessary, not using installd. Thus, we need to
-    // pass the instruction-set-features/variant as an image-compiler-option.
-    // Note: it is OK to reuse the buffer, as the values are exactly the same between
-    //       * compiler-option, used for runtime compilation (DexClassLoader)
-    //       * image-compiler-option, used for boot-image compilation on device
 
     // Copy the variant.
     sprintf(dex2oat_isa_variant_key, "dalvik.vm.isa.%s.variant", ABI_STRING);
     parseCompilerOption(dex2oat_isa_variant_key, dex2oat_isa_variant,
-                        "--instruction-set-variant=", "-Ximage-compiler-option");
-    parseCompilerOption(dex2oat_isa_variant_key, dex2oat_isa_variant,
                         "--instruction-set-variant=", "-Xcompiler-option");
     // Copy the features.
     sprintf(dex2oat_isa_features_key, "dalvik.vm.isa.%s.features", ABI_STRING);
     parseCompilerOption(dex2oat_isa_features_key, dex2oat_isa_features,
-                        "--instruction-set-features=", "-Ximage-compiler-option");
-    parseCompilerOption(dex2oat_isa_features_key, dex2oat_isa_features,
                         "--instruction-set-features=", "-Xcompiler-option");
 
+    /*
+     * When running with debug.generate-debug-info, add --generate-debug-info to
+     * the compiler options so that both JITted code and the boot image extension,
+     * if it is compiled on device, will include native debugging information.
+     */
+    property_get("debug.generate-debug-info", propBuf, "");
+    bool generate_debug_info = (strcmp(propBuf, "true") == 0);
+    if (generate_debug_info) {
+        addOption("-Xcompiler-option");
+        addOption("--generate-debug-info");
+    }
+
+    // The mini-debug-info makes it possible to backtrace through compiled code.
+    bool generate_mini_debug_info = property_get_bool("dalvik.vm.minidebuginfo", 0);
+    if (generate_mini_debug_info) {
+        addOption("-Xcompiler-option");
+        addOption("--generate-mini-debug-info");
+    }
 
     property_get("dalvik.vm.dex2oat-flags", dex2oatFlagsBuf, "");
     parseExtraOpts(dex2oatFlagsBuf, "-Xcompiler-option");
@@ -975,6 +930,53 @@
     property_get("dalvik.vm.extra-opts", extraOptsBuf, "");
     parseExtraOpts(extraOptsBuf, NULL);
 
+    // Extra options for boot image extension generation.
+    if (skip_compilation) {
+        addOption("-Xnoimage-dex2oat");
+    } else {
+        parseCompilerRuntimeOption("dalvik.vm.image-dex2oat-Xms", dex2oatXmsImageFlagsBuf,
+                                   "-Xms", "-Ximage-compiler-option");
+        parseCompilerRuntimeOption("dalvik.vm.image-dex2oat-Xmx", dex2oatXmxImageFlagsBuf,
+                                   "-Xmx", "-Ximage-compiler-option");
+
+        parseCompilerOption("dalvik.vm.image-dex2oat-filter", dex2oatImageCompilerFilterBuf,
+                            "--compiler-filter=", "-Ximage-compiler-option");
+
+        // If there is a dirty-image-objects file, push it.
+        if (hasFile("/system/etc/dirty-image-objects")) {
+            addOption("-Ximage-compiler-option");
+            addOption("--dirty-image-objects=/system/etc/dirty-image-objects");
+        }
+
+        parseCompilerOption("dalvik.vm.image-dex2oat-threads", dex2oatThreadsImageBuf, "-j",
+                            "-Ximage-compiler-option");
+        parseCompilerOption("dalvik.vm.image-dex2oat-cpu-set", dex2oatCpuSetImageBuf, "--cpu-set=",
+                            "-Ximage-compiler-option");
+
+        // The runtime may compile a boot image extension, when necessary, not using installd.
+        // Thus, we need to pass the instruction-set-features/variant as an image-compiler-option.
+        // Note: it is OK to reuse the buffer, as the values are exactly the same between
+        //       * compiler-option, used for runtime compilation (DexClassLoader)
+        //       * image-compiler-option, used for boot-image compilation on device
+        parseCompilerOption(dex2oat_isa_variant_key, dex2oat_isa_variant,
+                            "--instruction-set-variant=", "-Ximage-compiler-option");
+        parseCompilerOption(dex2oat_isa_features_key, dex2oat_isa_features,
+                            "--instruction-set-features=", "-Ximage-compiler-option");
+
+        if (generate_debug_info) {
+            addOption("-Ximage-compiler-option");
+            addOption("--generate-debug-info");
+        }
+
+        if (generate_mini_debug_info) {
+            addOption("-Ximage-compiler-option");
+            addOption("--generate-mini-debug-info");
+        }
+
+        property_get("dalvik.vm.image-dex2oat-flags", dex2oatImageFlagsBuf, "");
+        parseExtraOpts(dex2oatImageFlagsBuf, "-Ximage-compiler-option");
+    }
+
     /* Set the properties for locale */
     {
         strcpy(localeOption, "-Duser.locale=");
@@ -1032,25 +1034,6 @@
     parseRuntimeOption("dalvik.vm.zygote.max-boot-retry", cachePruneBuf,
                        "-Xzygote-max-boot-retry=");
 
-    /*
-     * When running with debug.generate-debug-info, add --generate-debug-info to
-     * the compiler options so that the boot image, if it is compiled on device,
-     * will include native debugging information.
-     */
-    property_get("debug.generate-debug-info", propBuf, "");
-    if (strcmp(propBuf, "true") == 0) {
-        addOption("-Xcompiler-option");
-        addOption("--generate-debug-info");
-        addOption("-Ximage-compiler-option");
-        addOption("--generate-debug-info");
-    }
-
-    // The mini-debug-info makes it possible to backtrace through JIT code.
-    if (property_get_bool("dalvik.vm.minidebuginfo", 0)) {
-        addOption("-Xcompiler-option");
-        addOption("--generate-mini-debug-info");
-    }
-
     // If set, the property below can be used to enable core platform API violation reporting.
     property_get("persist.debug.dalvik.vm.core_platform_api_policy", propBuf, "");
     if (propBuf[0] != '\0') {
diff --git a/core/jni/LayoutlibLoader.cpp b/core/jni/LayoutlibLoader.cpp
index 6c0680f..571a3387 100644
--- a/core/jni/LayoutlibLoader.cpp
+++ b/core/jni/LayoutlibLoader.cpp
@@ -75,10 +75,12 @@
 extern int register_android_os_SystemClock(JNIEnv* env);
 extern int register_android_os_SystemProperties(JNIEnv* env);
 extern int register_android_os_Trace(JNIEnv* env);
+extern int register_android_text_AndroidCharacter(JNIEnv* env);
 extern int register_android_util_EventLog(JNIEnv* env);
 extern int register_android_util_Log(JNIEnv* env);
 extern int register_android_util_PathParser(JNIEnv* env);
 extern int register_android_view_RenderNode(JNIEnv* env);
+extern int register_android_view_RenderNodeAnimator(JNIEnv* env);
 extern int register_android_view_DisplayListCanvas(JNIEnv* env);
 extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv *env);
 extern int register_com_android_internal_view_animation_NativeInterpolatorFactoryHelper(JNIEnv *env);
@@ -90,58 +92,66 @@
 
 // Map of all possible class names to register to their corresponding JNI registration function pointer
 // The actual list of registered classes will be determined at runtime via the 'native_classes' System property
-static const std::unordered_map<std::string, RegJNIRec>  gRegJNIMap = {
-    {"android.animation.PropertyValuesHolder", REG_JNI(register_android_animation_PropertyValuesHolder)},
+static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = {
+        {"android.animation.PropertyValuesHolder",
+         REG_JNI(register_android_animation_PropertyValuesHolder)},
 #ifdef __linux__
-    {"android.content.AssetManager", REG_JNI(register_android_content_AssetManager)},
-    {"android.content.StringBlock", REG_JNI(register_android_content_StringBlock)},
-    {"android.content.XmlBlock", REG_JNI(register_android_content_XmlBlock)},
-    {"android.content.res.ApkAssets", REG_JNI(register_android_content_res_ApkAssets)},
+        {"android.content.res.ApkAssets", REG_JNI(register_android_content_res_ApkAssets)},
+        {"android.content.res.AssetManager", REG_JNI(register_android_content_AssetManager)},
 #endif
-    {"android.graphics.Bitmap", REG_JNI(register_android_graphics_Bitmap)},
-    {"android.graphics.BitmapFactory", REG_JNI(register_android_graphics_BitmapFactory)},
-    {"android.graphics.ByteBufferStreamAdaptor", REG_JNI(register_android_graphics_ByteBufferStreamAdaptor)},
-    {"android.graphics.Canvas", REG_JNI(register_android_graphics_Canvas)},
-    {"android.graphics.RenderNode", REG_JNI(register_android_view_RenderNode)},
-    {"android.graphics.ColorFilter", REG_JNI(register_android_graphics_ColorFilter)},
-    {"android.graphics.ColorSpace", REG_JNI(register_android_graphics_ColorSpace)},
-    {"android.graphics.CreateJavaOutputStreamAdaptor", REG_JNI(register_android_graphics_CreateJavaOutputStreamAdaptor)},
-    {"android.graphics.DrawFilter", REG_JNI(register_android_graphics_DrawFilter)},
-    {"android.graphics.FontFamily", REG_JNI(register_android_graphics_FontFamily)},
-    {"android.graphics.Graphics", REG_JNI(register_android_graphics_Graphics)},
-    {"android.graphics.ImageDecoder", REG_JNI(register_android_graphics_ImageDecoder)},
-    {"android.graphics.MaskFilter", REG_JNI(register_android_graphics_MaskFilter)},
-    {"android.graphics.Matrix", REG_JNI(register_android_graphics_Matrix)},
-    {"android.graphics.NinePatch", REG_JNI(register_android_graphics_NinePatch)},
-    {"android.graphics.Paint", REG_JNI(register_android_graphics_Paint)},
-    {"android.graphics.Path", REG_JNI(register_android_graphics_Path)},
-    {"android.graphics.PathEffect", REG_JNI(register_android_graphics_PathEffect)},
-    {"android.graphics.PathMeasure", REG_JNI(register_android_graphics_PathMeasure)},
-    {"android.graphics.Picture", REG_JNI(register_android_graphics_Picture)},
-    {"android.graphics.RecordingCanvas", REG_JNI(register_android_view_DisplayListCanvas)},
-    {"android.graphics.Region", REG_JNI(register_android_graphics_Region)},
-    {"android.graphics.Shader", REG_JNI(register_android_graphics_Shader)},
-    {"android.graphics.Typeface", REG_JNI(register_android_graphics_Typeface)},
-    {"android.graphics.drawable.AnimatedVectorDrawable", REG_JNI(register_android_graphics_drawable_AnimatedVectorDrawable)},
-    {"android.graphics.drawable.VectorDrawable", REG_JNI(register_android_graphics_drawable_VectorDrawable)},
-    {"android.graphics.fonts.Font", REG_JNI(register_android_graphics_fonts_Font)},
-    {"android.graphics.fonts.FontFamily", REG_JNI(register_android_graphics_fonts_FontFamily)},
-    {"android.graphics.text.LineBreaker", REG_JNI(register_android_graphics_text_LineBreaker)},
-    {"android.graphics.text.MeasuredText", REG_JNI(register_android_graphics_text_MeasuredText)},
+        {"android.content.res.StringBlock", REG_JNI(register_android_content_StringBlock)},
+        {"android.content.res.XmlBlock", REG_JNI(register_android_content_XmlBlock)},
+        {"android.graphics.Bitmap", REG_JNI(register_android_graphics_Bitmap)},
+        {"android.graphics.BitmapFactory", REG_JNI(register_android_graphics_BitmapFactory)},
+        {"android.graphics.ByteBufferStreamAdaptor",
+         REG_JNI(register_android_graphics_ByteBufferStreamAdaptor)},
+        {"android.graphics.Canvas", REG_JNI(register_android_graphics_Canvas)},
+        {"android.graphics.RenderNode", REG_JNI(register_android_view_RenderNode)},
+        {"android.graphics.ColorFilter", REG_JNI(register_android_graphics_ColorFilter)},
+        {"android.graphics.ColorSpace", REG_JNI(register_android_graphics_ColorSpace)},
+        {"android.graphics.CreateJavaOutputStreamAdaptor",
+         REG_JNI(register_android_graphics_CreateJavaOutputStreamAdaptor)},
+        {"android.graphics.DrawFilter", REG_JNI(register_android_graphics_DrawFilter)},
+        {"android.graphics.FontFamily", REG_JNI(register_android_graphics_FontFamily)},
+        {"android.graphics.Graphics", REG_JNI(register_android_graphics_Graphics)},
+        {"android.graphics.ImageDecoder", REG_JNI(register_android_graphics_ImageDecoder)},
+        {"android.graphics.MaskFilter", REG_JNI(register_android_graphics_MaskFilter)},
+        {"android.graphics.Matrix", REG_JNI(register_android_graphics_Matrix)},
+        {"android.graphics.NinePatch", REG_JNI(register_android_graphics_NinePatch)},
+        {"android.graphics.Paint", REG_JNI(register_android_graphics_Paint)},
+        {"android.graphics.Path", REG_JNI(register_android_graphics_Path)},
+        {"android.graphics.PathEffect", REG_JNI(register_android_graphics_PathEffect)},
+        {"android.graphics.PathMeasure", REG_JNI(register_android_graphics_PathMeasure)},
+        {"android.graphics.Picture", REG_JNI(register_android_graphics_Picture)},
+        {"android.graphics.RecordingCanvas", REG_JNI(register_android_view_DisplayListCanvas)},
+        {"android.graphics.Region", REG_JNI(register_android_graphics_Region)},
+        {"android.graphics.Shader", REG_JNI(register_android_graphics_Shader)},
+        {"android.graphics.Typeface", REG_JNI(register_android_graphics_Typeface)},
+        {"android.graphics.drawable.AnimatedVectorDrawable",
+         REG_JNI(register_android_graphics_drawable_AnimatedVectorDrawable)},
+        {"android.graphics.drawable.VectorDrawable",
+         REG_JNI(register_android_graphics_drawable_VectorDrawable)},
+        {"android.graphics.fonts.Font", REG_JNI(register_android_graphics_fonts_Font)},
+        {"android.graphics.fonts.FontFamily", REG_JNI(register_android_graphics_fonts_FontFamily)},
+        {"android.graphics.text.LineBreaker", REG_JNI(register_android_graphics_text_LineBreaker)},
+        {"android.graphics.text.MeasuredText",
+         REG_JNI(register_android_graphics_text_MeasuredText)},
 #ifdef __linux__
-    {"android.os.FileObserver", REG_JNI(register_android_os_FileObserver)},
-    {"android.os.MessageQueue", REG_JNI(register_android_os_MessageQueue)},
+        {"android.os.FileObserver", REG_JNI(register_android_os_FileObserver)},
+        {"android.os.MessageQueue", REG_JNI(register_android_os_MessageQueue)},
 #endif
-    {"android.os.SystemClock", REG_JNI(register_android_os_SystemClock)},
-    {"android.os.SystemProperties", REG_JNI(register_android_os_SystemProperties)},
-#ifdef __linux__
-    {"android.os.Trace", REG_JNI(register_android_os_Trace)},
-#endif
-    {"android.util.EventLog", REG_JNI(register_android_util_EventLog)},
-    {"android.util.Log", REG_JNI(register_android_util_Log)},
-    {"android.util.PathParser", REG_JNI(register_android_util_PathParser)},
-    {"com.android.internal.util.VirtualRefBasePtr", REG_JNI(register_com_android_internal_util_VirtualRefBasePtr)},
-    {"com.android.internal.view.animation.NativeInterpolatorFactoryHelper", REG_JNI(register_com_android_internal_view_animation_NativeInterpolatorFactoryHelper)},
+        {"android.os.SystemClock", REG_JNI(register_android_os_SystemClock)},
+        {"android.os.SystemProperties", REG_JNI(register_android_os_SystemProperties)},
+        {"android.os.Trace", REG_JNI(register_android_os_Trace)},
+        {"android.text.AndroidCharacter", REG_JNI(register_android_text_AndroidCharacter)},
+        {"android.util.EventLog", REG_JNI(register_android_util_EventLog)},
+        {"android.util.Log", REG_JNI(register_android_util_Log)},
+        {"android.util.PathParser", REG_JNI(register_android_util_PathParser)},
+        {"android.view.RenderNodeAnimator", REG_JNI(register_android_view_RenderNodeAnimator)},
+        {"com.android.internal.util.VirtualRefBasePtr",
+         REG_JNI(register_com_android_internal_util_VirtualRefBasePtr)},
+        {"com.android.internal.view.animation.NativeInterpolatorFactoryHelper",
+         REG_JNI(register_com_android_internal_view_animation_NativeInterpolatorFactoryHelper)},
 };
 // Vector to store the names of classes that need delegates of their native methods
 static vector<string> classesToDelegate;
@@ -159,7 +169,6 @@
 
 int AndroidRuntime::registerNativeMethods(JNIEnv* env,
         const char* className, const JNINativeMethod* gMethods, int numMethods) {
-
     string classNameString = string(className);
     if (find(classesToDelegate.begin(), classesToDelegate.end(), classNameString)
             != classesToDelegate.end()) {
diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp
index aa209cb..38fb8bd 100644
--- a/core/jni/android/graphics/Graphics.cpp
+++ b/core/jni/android/graphics/Graphics.cpp
@@ -569,8 +569,8 @@
     // mRecycledBitmap specifies the width and height of the bitmap that we
     // want to reuse.  Neither can be changed.  We will try to find a way
     // to reuse the memory.
-    const int maxWidth = SkTMax(bitmap->width(), mRecycledBitmap->info().width());
-    const int maxHeight = SkTMax(bitmap->height(), mRecycledBitmap->info().height());
+    const int maxWidth = std::max(bitmap->width(), mRecycledBitmap->info().width());
+    const int maxHeight = std::max(bitmap->height(), mRecycledBitmap->info().height());
     const SkImageInfo maxInfo = bitmap->info().makeWH(maxWidth, maxHeight);
     const size_t rowBytes = maxInfo.minRowBytes();
     const size_t bytesNeeded = maxInfo.computeByteSize(rowBytes);
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index c269d1c..14d7487 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -37,6 +37,7 @@
 #include <binder/Parcel.h>
 #include <binder/ProcessState.h>
 #include <binder/Stability.h>
+#include <binderthreadstate/CallerUtils.h>
 #include <cutils/atomic.h>
 #include <log/log.h>
 #include <utils/KeyedVector.h>
@@ -950,7 +951,7 @@
 
 static jboolean android_os_Binder_isHandlingTransaction()
 {
-    return IPCThreadState::self()->isServingCall();
+    return getCurrentServingCall() == BinderCallType::BINDER;
 }
 
 static jlong android_os_Binder_clearCallingIdentity()
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index 69ca17c..5a8225c 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -147,10 +147,12 @@
 }
 
 static jlong android_view_ThreadedRenderer_createProxy(JNIEnv* env, jobject clazz,
-        jboolean translucent, jlong rootRenderNodePtr) {
+        jboolean translucent, jboolean isWideGamut, jlong rootRenderNodePtr) {
     RootRenderNode* rootRenderNode = reinterpret_cast<RootRenderNode*>(rootRenderNodePtr);
     ContextFactoryImpl factory(rootRenderNode);
-    return (jlong) new RenderProxy(translucent, rootRenderNode, &factory);
+    RenderProxy* proxy = new RenderProxy(translucent, rootRenderNode, &factory);
+    proxy->setWideGamut(isWideGamut);
+    return (jlong) proxy;
 }
 
 static void android_view_ThreadedRenderer_deleteProxy(JNIEnv* env, jobject clazz,
@@ -627,7 +629,7 @@
     { "nSetProcessStatsBuffer", "(I)V", (void*) android_view_ThreadedRenderer_setProcessStatsBuffer },
     { "nGetRenderThreadTid", "(J)I", (void*) android_view_ThreadedRenderer_getRenderThreadTid },
     { "nCreateRootRenderNode", "()J", (void*) android_view_ThreadedRenderer_createRootRenderNode },
-    { "nCreateProxy", "(ZJ)J", (void*) android_view_ThreadedRenderer_createProxy },
+    { "nCreateProxy", "(ZZJ)J", (void*) android_view_ThreadedRenderer_createProxy },
     { "nDeleteProxy", "(J)V", (void*) android_view_ThreadedRenderer_deleteProxy },
     { "nLoadSystemProperties", "(J)Z", (void*) android_view_ThreadedRenderer_loadSystemProperties },
     { "nSetName", "(JLjava/lang/String;)V", (void*) android_view_ThreadedRenderer_setName },
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index a6e08d2..fc0a2ef 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -2562,7 +2562,7 @@
     // CATEGORY: SETTINGS
     // OS: R
     OPEN_SUPPORTED_LINKS = 1824;
-    
+
     // OPEN: Settings > Display > Dark theme > Set start time dialog
     DIALOG_DARK_THEME_SET_START_TIME = 1825;
 
@@ -2573,4 +2573,9 @@
     // CATEGORY: SETTINGS
     // OS: R
     VIBRATE_FOR_CALLS = 1827;
+
+    // OPEN: Settings > Connected devices > Connection preferences > NFC
+    // CATEGORY: SETTINGS
+    // OS: R
+    CONNECTION_DEVICE_ADVANCED_NFC = 1828;
 }
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index 8adcc9e..bf4cdee 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -52,6 +52,7 @@
 import "frameworks/base/core/proto/android/service/print.proto";
 import "frameworks/base/core/proto/android/service/procstats.proto";
 import "frameworks/base/core/proto/android/service/restricted_image.proto";
+import "frameworks/base/core/proto/android/service/sensor_service.proto";
 import "frameworks/base/core/proto/android/service/usb.proto";
 import "frameworks/base/core/proto/android/util/event_log_tags.proto";
 import "frameworks/base/core/proto/android/util/log.proto";
@@ -492,6 +493,11 @@
         (section).args = "contexthub --proto"
     ];
 
+    optional android.service.SensorServiceProto sensor_service = 3053 [
+        (section).type = SECTION_DUMPSYS,
+        (section).args = "sensorservice --proto"
+    ];
+
     // Reserved for OEMs.
     extensions 50000 to 100000;
 }
diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto
index 0a2fd70..2d2ead4 100644
--- a/core/proto/android/server/activitymanagerservice.proto
+++ b/core/proto/android/server/activitymanagerservice.proto
@@ -27,7 +27,6 @@
 import "frameworks/base/core/proto/android/content/configuration.proto";
 import "frameworks/base/core/proto/android/content/intent.proto";
 import "frameworks/base/core/proto/android/content/package_item_info.proto";
-import "frameworks/base/core/proto/android/graphics/rect.proto";
 import "frameworks/base/core/proto/android/internal/processstats.proto";
 import "frameworks/base/core/proto/android/os/bundle.proto";
 import "frameworks/base/core/proto/android/os/looper.proto";
diff --git a/core/proto/android/server/notificationhistory.proto b/core/proto/android/server/notificationhistory.proto
index 1e6ee3f..6749719 100644
--- a/core/proto/android/server/notificationhistory.proto
+++ b/core/proto/android/server/notificationhistory.proto
@@ -17,8 +17,6 @@
 syntax = "proto2";
 package com.android.server.notification;
 
-import "frameworks/base/core/proto/android/server/enums.proto";
-
 option java_multiple_files = true;
 
 // On disk data store for historical notifications
diff --git a/core/proto/android/service/sensor_service.proto b/core/proto/android/service/sensor_service.proto
new file mode 100644
index 0000000..8598f86
--- /dev/null
+++ b/core/proto/android/service/sensor_service.proto
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+package android.service;
+
+import "frameworks/base/core/proto/android/privacy.proto";
+
+option java_multiple_files = true;
+
+/*
+ * Notes:
+ * 1. When using ProtoOutputStream to write this proto message, must call
+ *    token = ProtoOutputStream#start(fieldId) before and ProtoOutputStream#end(token) after
+ *    writing a nested message.
+ * 2. Never reuse a proto field number. When removing a field, mark it as reserved.
+ */
+
+// Proto dump of android::SensorService. dumpsys sensorservice --proto
+message SensorServiceProto {
+    option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    enum OperatingModeEnum {
+        OP_MODE_UNKNOWN = 0;
+        OP_MODE_NORMAL = 1;
+        OP_MODE_RESTRICTED = 2;
+        OP_MODE_DATA_INJECTION = 3;
+    }
+
+    optional int64 current_time_ms = 1;
+    optional SensorDeviceProto sensor_device = 2;
+    optional SensorListProto sensors = 3;
+    optional SensorFusionProto fusion_state = 4;
+    optional SensorEventsProto sensor_events = 5;
+    repeated ActiveSensorProto active_sensors = 6;
+    optional int32 socket_buffer_size = 7;
+    optional int32 socket_buffer_size_in_events = 8;
+    optional bool wake_lock_acquired = 9;
+    optional OperatingModeEnum operating_mode = 10;
+    // Non-empty only if operating_mode is RESTRICTED or DATA_INJECTION.
+    optional string whitelisted_package = 11;
+    optional bool sensor_privacy = 12;
+    repeated SensorEventConnectionProto active_connections = 13;
+    repeated SensorDirectConnectionProto direct_connections = 14;
+    repeated SensorRegistrationInfoProto previous_registrations = 15;
+}
+
+// Proto dump of android::SensorDevice
+message SensorDeviceProto {
+    option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    optional bool initialized = 1;
+    optional int32 total_sensors = 2;
+    optional int32 active_sensors = 3;
+
+    message SensorProto {
+        option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+        optional int32 handle = 1;
+        optional int32 active_count = 2;
+        repeated float sampling_period_ms = 3;
+        optional float sampling_period_selected = 4;
+        repeated float batching_period_ms = 5;
+        optional float batching_period_selected = 6;
+    }
+    repeated SensorProto sensors = 4;
+}
+
+// Proto dump of android::SensorServiceUtil::SensorList
+message SensorListProto {
+    option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    enum ReportingModeEnum {
+        RM_UNKNOWN = 0;
+        RM_CONTINUOUS = 1;
+        RM_ON_CHANGE = 2;
+        RM_ONE_SHOT = 3;
+        RM_SPECIAL_TRIGGER = 4;
+    }
+
+    message SensorProto {
+        option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+        optional int32 handle = 1;
+        optional string name = 2;
+        optional string vendor = 3;
+        optional int32 version = 4;
+        optional string string_type = 5;
+        optional int32 type = 6;
+        optional string required_permission = 7;
+        optional int32 flags = 8;
+        optional ReportingModeEnum reporting_mode = 9;
+        optional int32 max_delay_us = 10;
+        optional int32 min_delay_us = 11;
+        optional int32 fifo_max_event_count = 12;
+        optional int32 fifo_reserved_event_count = 13;
+        optional bool is_wakeup = 14;
+        optional bool data_injection_supported = 15;
+        optional bool is_dynamic = 16;
+        optional bool has_additional_info = 17;
+        optional int32 highest_rate_level = 18;
+        optional bool ashmem = 19;
+        optional bool gralloc = 20;
+        optional float min_value = 21;
+        optional float max_value = 22;
+        optional float resolution = 23;
+        optional float power_usage = 24;
+    }
+    repeated SensorProto sensors = 1;
+}
+
+
+// Proto dump of android::SensorFusion
+message SensorFusionProto {
+    option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    message FusionProto {
+        option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+        optional bool enabled = 1;
+        optional int32 num_clients = 2;
+        optional float estimated_gyro_rate = 3;
+        optional float attitude_x = 4;
+        optional float attitude_y = 5;
+        optional float attitude_z = 6;
+        optional float attitude_w = 7;
+        optional float attitude_length = 8;
+        optional float bias_x = 9;
+        optional float bias_y = 10;
+        optional float bias_z = 11;
+    }
+    optional FusionProto fusion_9axis = 1;
+    optional FusionProto fusion_nomag = 2;
+    optional FusionProto fusion_nogyro = 3;
+}
+
+// Proto dump of android::SensorServiceUtil::RecentEventLogger
+message SensorEventsProto {
+    option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    message Event {
+        option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+        optional float timestamp_sec = 1;
+        optional int64 wall_timestamp_ms = 2;
+        optional bool masked = 3;
+        optional int64 int64_data = 4;
+        repeated float float_array = 5;
+    }
+
+    message RecentEventsLog {
+        option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+        optional string name = 1;
+        optional int32 recent_events_count = 2;
+        repeated Event events = 3;
+    }
+    repeated RecentEventsLog recent_events_logs = 1;
+}
+
+message ActiveSensorProto {
+    option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    optional string name = 1;
+    optional int32 handle = 2;
+    optional int32 num_connections = 3;
+}
+
+// Proto dump of android::SensorService::SensorDirectConnection
+message SensorDirectConnectionProto {
+    option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    message SensorProto {
+        option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+        optional int32 sensor = 1;
+        optional int32 rate = 2;
+    }
+
+    optional string package_name = 1;
+    optional int32 hal_channel_handle = 2;
+    optional int32 num_sensor_activated = 3;
+    repeated SensorProto sensors = 4;
+}
+
+// Proto dump of android::SensorService::SensorEventConnection
+message SensorEventConnectionProto {
+    option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    message FlushInfoProto {
+        option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+        optional string sensor_name = 1;
+        optional int32 sensor_handle = 2;
+        optional bool first_flush_pending = 3;
+        optional int32 pending_flush_events_to_send = 4;
+    }
+
+    enum OperatingModeEnum {
+        OP_MODE_UNKNOWN = 0;
+        OP_MODE_NORMAL = 1;
+        OP_MODE_RESTRICTED = 2;
+        OP_MODE_DATA_INJECTION = 3;
+    }
+
+    optional OperatingModeEnum operating_mode = 1;
+    optional string package_name = 2;
+    optional int32 wake_lock_ref_count = 3;
+    optional int32 uid = 4;
+    optional int32 cache_size = 5;
+    optional int32 max_cache_size = 6;
+    repeated FlushInfoProto flush_infos = 7;
+    optional int32 events_received = 8;
+    optional int32 events_sent = 9;
+    optional int32 events_cache = 10;
+    optional int32 events_dropped = 11;
+    optional int32 total_acks_needed = 12;
+    optional int32 total_acks_received = 13;
+}
+
+// Proto dump of android::SensorService::SensorRegistrationInfo
+message SensorRegistrationInfoProto {
+    option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    optional int64 timestamp_sec = 1;
+    optional int32 sensor_handle = 2;
+    optional string package_name = 3;
+    optional int32 pid = 4;
+    optional int32 uid = 5;
+    optional int64 sampling_rate_us = 6;
+    optional int64 max_report_latency_us = 7;
+    optional bool activated = 8;
+}
diff --git a/core/proto/android/stats/devicepolicy/device_policy_enums.proto b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
index 0f03e69..d1392a5 100644
--- a/core/proto/android/stats/devicepolicy/device_policy_enums.proto
+++ b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
@@ -157,4 +157,6 @@
   SET_FACTORY_RESET_PROTECTION = 130;
   SET_COMMON_CRITERIA_MODE = 131;
   ALLOW_MODIFICATION_OF_ADMIN_CONFIGURED_NETWORKS = 132;
+  SET_TIME = 133;
+  SET_TIME_ZONE = 134;
 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 753d64a..de0d172 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2603,9 +2603,9 @@
 
     <!-- Allows telephony to suggest the time / time zone.
          <p>Not for use by third-party applications.
-         @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @hide
+         @hide
      -->
-    <permission android:name="android.permission.SUGGEST_PHONE_TIME_AND_ZONE"
+    <permission android:name="android.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE"
         android:protectionLevel="signature|telephony" />
 
     <!-- Allows applications like settings to suggest the user's manually chosen time / time zone.
@@ -3448,6 +3448,14 @@
     <permission android:name="android.permission.TUNER_RESOURCE_ACCESS"
          android:protectionLevel="signature|privileged" />
 
+    <!-- This permission is required by Media Resource Manager Service when
+         accessing its overridePid Api.
+         <p>Protection level: signature
+         <p>Not for use by third-party applications.
+         @hide -->
+    <permission android:name="android.permission.MEDIA_RESOURCE_OVERRIDE_PID"
+         android:protectionLevel="signature" />
+
     <!-- Must be required by a {@link android.media.routing.MediaRouteService}
          to ensure that only the system can interact with it.
          @hide -->
@@ -4057,6 +4065,11 @@
     <permission android:name="android.permission.STATSCOMPANION"
         android:protectionLevel="signature" />
 
+    <!--@SystemApi @hide Allows an application to register stats pull atom callbacks.
+    <p>Not for use by third-party applications.-->
+    <permission android:name="android.permission.REGISTER_STATS_PULL_ATOM"
+                android:protectionLevel="signature|privileged" />
+
     <!-- @SystemApi Allows an application to control the backup and restore process.
     <p>Not for use by third-party applications.
          @hide pending API council -->
@@ -4865,6 +4878,19 @@
     <permission android:name="android.permission.ACCESS_SHARED_LIBRARIES"
                 android:protectionLevel="signature|installer" />
 
+    <!-- Allows an app to log compat change usage.
+         @hide  <p>Not for use by third-party applications.</p> -->
+    <permission android:name="android.permission.LOG_COMPAT_CHANGE"
+                android:protectionLevel="signature|privileged" />
+    <!-- Allows an app to read compat change config.
+         @hide  <p>Not for use by third-party applications.</p> -->
+    <permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG"
+                android:protectionLevel="signature|privileged" />
+    <!-- Allows an app to override compat change config.
+         @hide  <p>Not for use by third-party applications.</p> -->
+    <permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG"
+                android:protectionLevel="signature|privileged" />
+
     <!-- Allows input events to be monitored. Very dangerous!  @hide -->
     <permission android:name="android.permission.MONITOR_INPUT"
                 android:protectionLevel="signature" />
@@ -5075,6 +5101,12 @@
                 android:process=":ui">
         </activity>
 
+        <activity android:name="com.android.internal.app.BlockedAppActivity"
+                android:theme="@style/Theme.Dialog.Confirmation"
+                android:excludeFromRecents="true"
+                android:process=":ui">
+        </activity>
+
         <activity android:name="com.android.settings.notification.NotificationAccessConfirmationActivity"
                   android:theme="@style/Theme.Dialog.Confirmation"
                   android:excludeFromRecents="true">
@@ -5296,6 +5328,10 @@
                  android:permission="android.permission.BIND_JOB_SERVICE" >
         </service>
 
+        <service android:name="com.android.server.blob.BlobStoreIdleJobService"
+                 android:permission="android.permission.BIND_JOB_SERVICE">
+        </service>
+
         <service android:name="com.android.server.pm.PackageManagerShellCommandDataLoader">
             <intent-filter>
                 <action android:name="android.intent.action.LOAD_DATA" />
diff --git a/core/res/res/layout/accessibility_button_chooser_item.xml b/core/res/res/layout/accessibility_button_chooser_item.xml
index d19e313..d6fd7aa 100644
--- a/core/res/res/layout/accessibility_button_chooser_item.xml
+++ b/core/res/res/layout/accessibility_button_chooser_item.xml
@@ -20,6 +20,7 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
+    android:clickable="true"
     android:gravity="center"
     android:paddingStart="16dp"
     android:paddingEnd="16dp"
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 3d7b1e1..31e68e8 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3560,25 +3560,16 @@
     -->
     <string name="config_defaultAutofillService" translatable="false"></string>
 
-    <!-- The package name for the default system textclassifier service.
+    <!-- The package name for the OEM custom system textclassifier service.
          This service must be trusted, as it can be activated without explicit consent of the user.
          Example: "com.android.textclassifier"
-         If no textclassifier service with the specified name exists on the device (or if this is
-         set to empty string), a default textclassifier will be loaded in the calling app's process.
+         If this is empty, the default textclassifier service (i.e. config_servicesExtensionPackage)
+         will be used.
+
          See android.view.textclassifier.TextClassificationManager.
     -->
-    <!-- TODO(b/144896755) remove the config -->
     <string name="config_defaultTextClassifierPackage" translatable="false"></string>
 
-    <!-- A list of supported system textClassifier service package names. Only one of the packages
-         will be activated. The first package in the list is the default system textClassifier
-         service. OS only tries to bind and grant permissions to the first trusted service and the
-         others can be selected via device config. These services must be trusted, as they can be
-         activated without explicit consent of the user. Example: "com.android.textclassifier"
-    -->
-    <string-array name="config_defaultTextClassifierPackages" translatable="false">
-        <item>android.ext.services</item>
-    </string-array>
 
     <!-- The package name for the system companion device manager service.
          This service must be trusted, as it can be activated without explicit consent of the user.
@@ -4352,4 +4343,7 @@
          Determines whether the specified key groups can be used to wake up the device. -->
     <bool name="config_wakeOnDpadKeyPress">true</bool>
     <bool name="config_wakeOnAssistKeyPress">true</bool>
+
+    <!-- Whether to default to an expanded list of users on the lock screen user switcher. -->
+    <bool name="config_expandLockScreenUserSwitcher">false</bool>
 </resources>
diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml
index 51ed08b..bddda1b 100644
--- a/core/res/res/values/ids.xml
+++ b/core/res/res/values/ids.xml
@@ -226,4 +226,7 @@
 
   <!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_PRESS_AND_HOLD}. -->
   <item type="id" name="accessibilityActionPressAndHold" />
+
+  <!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_IME_ENTER}. -->
+  <item type="id" name="accessibilityActionImeEnter" />
 </resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 4f61730a..4172044 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3066,6 +3066,7 @@
       <public name="accessibilitySystemActionToggleSplitScreen" />
       <public name="accessibilitySystemActionLockScreen" />
       <public name="accessibilitySystemActionTakeScreenshot" />
+      <public name="accessibilityActionImeEnter" />
     </public-group>
 
     <public-group type="array" first-id="0x01070006">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 11cc365..a54566c 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4936,6 +4936,13 @@
     <!-- Title for button to turn on work profile. [CHAR LIMIT=NONE] -->
     <string name="work_mode_turn_on">Turn on</string>
 
+    <!-- Title of the dialog that is shown when the user tries to launch a blocked application [CHAR LIMIT=50] -->
+    <string name="app_blocked_title">App is not available</string>
+    <!-- Default message shown in the dialog that is shown when the user tries to launch a blocked application [CHAR LIMIT=NONE] -->
+    <string name="app_blocked_message">
+        <xliff:g id="app_name" example="Gmail">%1$s</xliff:g> is not available right now.
+    </string>
+
     <!-- Message displayed in dialog when app is too old to run on this verison of android. [CHAR LIMIT=NONE] -->
     <string name="deprecated_target_sdk_message">This app was built for an older version of Android and may not work properly. Try checking for updates, or contact the developer.</string>
     <!-- Title for button to see application detail in app store which it came from - it may allow user to update to newer version. [CHAR LIMIT=50] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 36296a8..2453bb1 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3048,6 +3048,9 @@
   <java-symbol type="string" name="app_suspended_unsuspend_message" />
   <java-symbol type="string" name="app_suspended_default_message" />
 
+  <java-symbol type="string" name="app_blocked_title" />
+  <java-symbol type="string" name="app_blocked_message" />
+
   <!-- Used internally for assistant to launch activity transitions -->
   <java-symbol type="id" name="cross_task_transition" />
 
@@ -3390,7 +3393,6 @@
   <java-symbol type="string" name="notification_channel_do_not_disturb" />
   <java-symbol type="string" name="config_defaultAutofillService" />
   <java-symbol type="string" name="config_defaultTextClassifierPackage" />
-  <java-symbol type="array" name="config_defaultTextClassifierPackages" />
   <java-symbol type="string" name="config_defaultWellbeingPackage" />
   <java-symbol type="string" name="config_telephonyPackages" />
   <java-symbol type="string" name="config_defaultContentCaptureService" />
@@ -3861,4 +3863,7 @@
 
   <!-- Toast message for background started foreground service while-in-use permission restriction feature -->
   <java-symbol type="string" name="allow_while_in_use_permission_in_fgs" />
+
+  <!-- Whether to expand the lock screen user switcher by default -->
+  <java-symbol type="bool" name="config_expandLockScreenUserSwitcher" />
 </resources>
diff --git a/core/res/res/xml/default_zen_mode_config.xml b/core/res/res/xml/default_zen_mode_config.xml
index 6cf6a82..9110661 100644
--- a/core/res/res/xml/default_zen_mode_config.xml
+++ b/core/res/res/xml/default_zen_mode_config.xml
@@ -18,9 +18,10 @@
 -->
 
 <!-- Default configuration for zen mode.  See android.service.notification.ZenModeConfig. -->
-<zen version="8">
+<zen version="9">
     <allow alarms="true" media="true" system="false" calls="true" callsFrom="2" messages="false"
-           reminders="false" events="false" repeatCallers="true" />
+            reminders="false" events="false" repeatCallers="true" conversations="true"
+            conversationsFrom="2"/>
     <automatic ruleId="EVENTS_DEFAULT_RULE" enabled="false" snoozing="false" name="Event" zen="1"
                component="android/com.android.server.notification.EventConditionProvider"
                conditionId="condition://android/event?userId=-10000&amp;calendar=&amp;reply=1"/>
diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml
index 2d1c61c..70917e7 100644
--- a/core/res/res/xml/sms_short_codes.xml
+++ b/core/res/res/xml/sms_short_codes.xml
@@ -34,11 +34,14 @@
          http://smscoin.net/software/engine/WordPress/Paid+SMS-registration/ -->
 
     <!-- Arab Emirates -->
-    <shortcode country="ae" pattern="\\d{1,5}" free="3214|1017" />
+    <shortcode country="ae" pattern="\\d{1,5}" free="1017|1355|3214" />
 
     <!-- Albania: 5 digits, known short codes listed -->
     <shortcode country="al" pattern="\\d{5}" premium="15191|55[56]00" />
 
+    <!-- Argentia: 5 digits, known short codes listed -->
+    <shortcode country="ar" pattern="\\d{5}" free="11711|28291" />
+
     <!-- Armenia: 3-4 digits, emergency numbers 10[123] -->
     <shortcode country="am" pattern="\\d{3,4}" premium="11[2456]1|3024" free="10[123]" />
 
@@ -80,7 +83,7 @@
     <shortcode country="cn" premium="1066.*" free="1065.*" />
 
     <!-- Colombia: 1-6 digits (not confirmed) -->
-    <shortcode country="co" pattern="\\d{1,6}" free="890350|908160" />
+    <shortcode country="co" pattern="\\d{1,6}" free="890350|908160|892255|898002|898880|899960" />
 
     <!-- Cyprus: 4-6 digits (not confirmed), known premium codes listed, plus EU -->
     <shortcode country="cy" pattern="\\d{4,6}" premium="7510" free="116\\d{3}" />
@@ -181,7 +184,7 @@
     <shortcode country="mk" pattern="\\d{1,6}" free="129005|122" />
 
     <!-- Mexico: 4-5 digits (not confirmed), known premium codes listed -->
-    <shortcode country="mx" pattern="\\d{4,5}" premium="53035|7766" free="46645|5050|26259|50025|50052|9963|76551" />
+    <shortcode country="mx" pattern="\\d{4,5}" premium="53035|7766" free="26259|46645|50025|50052|5050|76551|88778|9963" />
 
     <!-- Malaysia: 5 digits: http://www.skmm.gov.my/attachment/Consumer_Regulation/Mobile_Content_Services_FAQs.pdf -->
     <shortcode country="my" pattern="\\d{5}" premium="32298|33776" free="22099|28288" />
@@ -196,7 +199,7 @@
     <shortcode country="no" pattern="\\d{4,5}" premium="2201|222[67]" free="2171" />
 
     <!-- New Zealand: 3-4 digits, known premium codes listed -->
-    <shortcode country="nz" pattern="\\d{3,4}" premium="3903|8995|4679" free="1737|2141|3067|3068|3110|4006|4053|4061|4062|4202|4300|4334|4412|4575|5626|8006|8681" />
+    <shortcode country="nz" pattern="\\d{3,4}" premium="3903|8995|4679" free="1737|176|2141|3067|3068|3110|4006|4053|4061|4062|4202|4300|4334|4412|4575|5626|8006|8681" />
 
     <!-- Peru: 4-5 digits (not confirmed), known premium codes listed -->
     <shortcode country="pe" pattern="\\d{4,5}" free="9963" />
@@ -264,4 +267,7 @@
     <!-- Mayotte (French Territory): 1-5 digits (not confirmed) -->
     <shortcode country="yt" pattern="\\d{1,5}" free="38600,36300,36303,959" />
 
+    <!-- South Africa -->
+    <shortcode country="za" pattern="\d{1,5}" free="44136" />
+
 </shortcodes>
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 38454ec..9f15faf 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -38,6 +38,7 @@
         "mockito-target-minus-junit4",
         "ub-uiautomator",
         "platform-test-annotations",
+        "platform-compat-test-rules",
         "truth-prebuilt",
         "print-test-util-lib",
         "testng",
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index ee93b39..59335a5 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -111,6 +111,10 @@
     <uses-permission android:name="android.permission.MOVE_PACKAGE" />
     <uses-permission android:name="android.permission.PACKAGE_VERIFICATION_AGENT" />
 
+    <!-- gating and logging permissions -->
+    <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE" />
+    <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
+
     <!-- os storage test permissions -->
     <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
     <uses-permission android:name="android.permission.ASEC_ACCESS" />
diff --git a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
index 6c5d548..02be557 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
@@ -24,6 +24,9 @@
 import android.content.Context;
 import android.content.pm.ConfigurationInfo;
 import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.test.AndroidTestCase;
 
 import androidx.test.filters.SmallTest;
@@ -137,21 +140,7 @@
         // Must overwrite all the fields
         td2.copyFrom(td1);
 
-        assertEquals(td1.getLabel(), td2.getLabel());
-        assertEquals(td1.getInMemoryIcon(), td2.getInMemoryIcon());
-        assertEquals(td1.getIconFilename(), td2.getIconFilename());
-        assertEquals(td1.getIconResource(), td2.getIconResource());
-        assertEquals(td1.getPrimaryColor(), td2.getPrimaryColor());
-        assertEquals(td1.getBackgroundColor(), td2.getBackgroundColor());
-        assertEquals(td1.getStatusBarColor(), td2.getStatusBarColor());
-        assertEquals(td1.getNavigationBarColor(), td2.getNavigationBarColor());
-        assertEquals(td1.getEnsureStatusBarContrastWhenTransparent(),
-                td2.getEnsureStatusBarContrastWhenTransparent());
-        assertEquals(td1.getEnsureNavigationBarContrastWhenTransparent(),
-                td2.getEnsureNavigationBarContrastWhenTransparent());
-        assertEquals(td1.getResizeMode(), td2.getResizeMode());
-        assertEquals(td1.getMinWidth(), td2.getMinWidth());
-        assertEquals(td1.getMinHeight(), td2.getMinHeight());
+        assertTaskDescriptionEqual(td1, td2, true, true);
     }
 
     @SmallTest
@@ -191,44 +180,101 @@
         // Must overwrite all public and hidden fields, since other has all fields set.
         td2.copyFromPreserveHiddenFields(td1);
 
-        assertEquals(td1.getLabel(), td2.getLabel());
-        assertEquals(td1.getInMemoryIcon(), td2.getInMemoryIcon());
-        assertEquals(td1.getIconFilename(), td2.getIconFilename());
-        assertEquals(td1.getIconResource(), td2.getIconResource());
-        assertEquals(td1.getPrimaryColor(), td2.getPrimaryColor());
-        assertEquals(td1.getBackgroundColor(), td2.getBackgroundColor());
-        assertEquals(td1.getStatusBarColor(), td2.getStatusBarColor());
-        assertEquals(td1.getNavigationBarColor(), td2.getNavigationBarColor());
-        assertEquals(td1.getEnsureStatusBarContrastWhenTransparent(),
-                td2.getEnsureStatusBarContrastWhenTransparent());
-        assertEquals(td1.getEnsureNavigationBarContrastWhenTransparent(),
-                td2.getEnsureNavigationBarContrastWhenTransparent());
-        assertEquals(td1.getResizeMode(), td2.getResizeMode());
-        assertEquals(td1.getMinWidth(), td2.getMinWidth());
-        assertEquals(td1.getMinHeight(), td2.getMinHeight());
+        assertTaskDescriptionEqual(td1, td2, true, true);
 
         TaskDescription td3 = new TaskDescription();
         // Must overwrite only public fields, and preserve hidden fields.
         td2.copyFromPreserveHiddenFields(td3);
 
-        // Overwritten fields
-        assertEquals(td3.getLabel(), td2.getLabel());
-        assertEquals(td3.getInMemoryIcon(), td2.getInMemoryIcon());
-        assertEquals(td3.getIconFilename(), td2.getIconFilename());
-        assertEquals(td3.getIconResource(), td2.getIconResource());
-        assertEquals(td3.getPrimaryColor(), td2.getPrimaryColor());
-        assertEquals(td3.getEnsureStatusBarContrastWhenTransparent(),
-                td2.getEnsureStatusBarContrastWhenTransparent());
-        assertEquals(td3.getEnsureNavigationBarContrastWhenTransparent(),
-                td2.getEnsureNavigationBarContrastWhenTransparent());
+        assertTaskDescriptionEqual(td3, td2, true, false);
+        assertTaskDescriptionEqual(td1, td2, false, true);
+    }
 
-        // Preserved fields
-        assertEquals(td1.getBackgroundColor(), td2.getBackgroundColor());
-        assertEquals(td1.getStatusBarColor(), td2.getStatusBarColor());
-        assertEquals(td1.getNavigationBarColor(), td2.getNavigationBarColor());
-        assertEquals(td1.getResizeMode(), td2.getResizeMode());
-        assertEquals(td1.getMinWidth(), td2.getMinWidth());
-        assertEquals(td1.getMinHeight(), td2.getMinHeight());
+    @SmallTest
+    public void testTaskDescriptionParceling() throws Exception {
+        TaskDescription tdBitmapNull = new TaskDescription(
+                "test label",              // label
+                null,                      // bitmap
+                21,                        // iconRes
+                "dummy file",              // iconFilename
+                0x111111,                  // colorPrimary
+                0x222222,                  // colorBackground
+                0x333333,                  // statusBarColor
+                0x444444,                  // navigationBarColor
+                false,                     // ensureStatusBarContrastWhenTransparent
+                false,                     // ensureNavigationBarContrastWhenTransparent
+                RESIZE_MODE_UNRESIZEABLE,  // resizeMode
+                10,                        // minWidth
+                20                         // minHeight
+        );
+
+        // Normal parceling should keep everything the same.
+        TaskDescription tdParcelled = new TaskDescription(parcelingRoundTrip(tdBitmapNull));
+        assertTaskDescriptionEqual(tdBitmapNull, tdParcelled, true, true);
+
+        Bitmap recycledBitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888);
+        recycledBitmap.recycle();
+        assertTrue(recycledBitmap.isRecycled());
+        TaskDescription tdBitmapRecycled = new TaskDescription(
+                "test label",              // label
+                recycledBitmap,                      // bitmap
+                21,                        // iconRes
+                "dummy file",              // iconFilename
+                0x111111,                  // colorPrimary
+                0x222222,                  // colorBackground
+                0x333333,                  // statusBarColor
+                0x444444,                  // navigationBarColor
+                false,                     // ensureStatusBarContrastWhenTransparent
+                false,                     // ensureNavigationBarContrastWhenTransparent
+                RESIZE_MODE_UNRESIZEABLE,  // resizeMode
+                10,                        // minWidth
+                20                         // minHeight
+        );
+        // Recycled bitmap will be ignored while parceling.
+        tdParcelled = new TaskDescription(parcelingRoundTrip(tdBitmapRecycled));
+        assertTaskDescriptionEqual(tdBitmapNull, tdParcelled, true, true);
+
+    }
+
+    private void assertTaskDescriptionEqual(TaskDescription td1, TaskDescription td2,
+            boolean checkOverwrittenFields, boolean checkPreservedFields) {
+        if (checkOverwrittenFields) {
+            assertEquals(td1.getLabel(), td2.getLabel());
+            assertEquals(td1.getInMemoryIcon(), td2.getInMemoryIcon());
+            assertEquals(td1.getIconFilename(), td2.getIconFilename());
+            assertEquals(td1.getIconResource(), td2.getIconResource());
+            assertEquals(td1.getPrimaryColor(), td2.getPrimaryColor());
+            assertEquals(td1.getEnsureStatusBarContrastWhenTransparent(),
+                    td2.getEnsureStatusBarContrastWhenTransparent());
+            assertEquals(td1.getEnsureNavigationBarContrastWhenTransparent(),
+                    td2.getEnsureNavigationBarContrastWhenTransparent());
+        }
+        if (checkPreservedFields) {
+            assertEquals(td1.getBackgroundColor(), td2.getBackgroundColor());
+            assertEquals(td1.getStatusBarColor(), td2.getStatusBarColor());
+            assertEquals(td1.getNavigationBarColor(), td2.getNavigationBarColor());
+            assertEquals(td1.getResizeMode(), td2.getResizeMode());
+            assertEquals(td1.getMinWidth(), td2.getMinWidth());
+            assertEquals(td1.getMinHeight(), td2.getMinHeight());
+        }
+    }
+
+    private <T extends Parcelable> T parcelingRoundTrip(final T in) throws Exception {
+        final Parcel p = Parcel.obtain();
+        in.writeToParcel(p, /* flags */ 0);
+        p.setDataPosition(0);
+        final byte[] marshalledData = p.marshall();
+        p.recycle();
+
+        final Parcel q = Parcel.obtain();
+        q.unmarshall(marshalledData, 0, marshalledData.length);
+        q.setDataPosition(0);
+
+        final Parcelable.Creator<T> creator = (Parcelable.Creator<T>)
+                in.getClass().getField("CREATOR").get(null); // static object, so null receiver
+        final T unmarshalled = (T) creator.createFromParcel(q);
+        q.recycle();
+        return unmarshalled;
     }
 
     // If any entries in appear in the list, sanity check them against all running applications
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index c986db8..c328d72 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -504,15 +504,17 @@
         }
 
         @Override
-        public void onPictureInPictureRequested() {
+        public boolean onPictureInPictureRequested() {
             mPipRequested = true;
             if (getIntent().getBooleanExtra(PIP_REQUESTED_OVERRIDE_ENTER, false)) {
                 enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
                 mPipEntered = true;
+                return true;
             } else if (getIntent().getBooleanExtra(PIP_REQUESTED_OVERRIDE_SKIP, false)) {
                 mPipEnterSkipped = true;
+                return false;
             }
-            super.onPictureInPictureRequested();
+            return super.onPictureInPictureRequested();
         }
 
         boolean pipRequested() {
diff --git a/core/tests/coretests/src/android/app/compat/CompatChangesTest.java b/core/tests/coretests/src/android/app/compat/CompatChangesTest.java
new file mode 100644
index 0000000..fbd02ed
--- /dev/null
+++ b/core/tests/coretests/src/android/app/compat/CompatChangesTest.java
@@ -0,0 +1,81 @@
+/*
+ * 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 android.app.compat;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Instrumentation;
+import android.compat.testing.PlatformCompatChangeRule;
+import android.os.Process;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+
+/**
+ * {@link CompatChanges} tests.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class CompatChangesTest {
+    static final long CHANGE_ID = 1L;
+
+    private Instrumentation mInstrumentation;
+
+    @Rule
+    public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
+    @Before
+    public void setup() {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+    }
+
+
+    private String getPackageName() {
+        return mInstrumentation.getTargetContext().getPackageName();
+    }
+
+    @Test
+    @EnableCompatChanges(CHANGE_ID)
+    public void testEnabledChange() {
+        assertThat(CompatChanges.isChangeEnabled(CHANGE_ID)).isTrue();
+        assertThat(CompatChanges.isChangeEnabled(CHANGE_ID, Process.myUid())).isTrue();
+        assertThat(CompatChanges.isChangeEnabled(CHANGE_ID, getPackageName(),
+                UserHandle.of(UserHandle.myUserId()))).isTrue();
+    }
+
+    @Test
+    @DisableCompatChanges(CHANGE_ID)
+    public void testDisabledChange() {
+        assertThat(CompatChanges.isChangeEnabled(CHANGE_ID)).isFalse();
+        assertThat(CompatChanges.isChangeEnabled(CHANGE_ID, Process.myUid())).isFalse();
+        assertThat(CompatChanges.isChangeEnabled(CHANGE_ID, getPackageName(),
+                UserHandle.of(UserHandle.myUserId()))).isFalse();
+    }
+}
diff --git a/core/tests/coretests/src/android/app/timedetector/PhoneTimeSuggestionTest.java b/core/tests/coretests/src/android/app/timedetector/TelephonyTimeSuggestionTest.java
similarity index 66%
rename from core/tests/coretests/src/android/app/timedetector/PhoneTimeSuggestionTest.java
rename to core/tests/coretests/src/android/app/timedetector/TelephonyTimeSuggestionTest.java
index d17b635..4b64dfc 100644
--- a/core/tests/coretests/src/android/app/timedetector/PhoneTimeSuggestionTest.java
+++ b/core/tests/coretests/src/android/app/timedetector/TelephonyTimeSuggestionTest.java
@@ -26,44 +26,45 @@
 
 import org.junit.Test;
 
-public class PhoneTimeSuggestionTest {
+public class TelephonyTimeSuggestionTest {
     private static final int SLOT_INDEX = 99999;
 
     @Test
     public void testEquals() {
-        PhoneTimeSuggestion.Builder builder1 = new PhoneTimeSuggestion.Builder(SLOT_INDEX);
+        TelephonyTimeSuggestion.Builder builder1 = new TelephonyTimeSuggestion.Builder(SLOT_INDEX);
         {
-            PhoneTimeSuggestion one = builder1.build();
+            TelephonyTimeSuggestion one = builder1.build();
             assertEquals(one, one);
         }
 
-        PhoneTimeSuggestion.Builder builder2 = new PhoneTimeSuggestion.Builder(SLOT_INDEX);
+        TelephonyTimeSuggestion.Builder builder2 = new TelephonyTimeSuggestion.Builder(SLOT_INDEX);
         {
-            PhoneTimeSuggestion one = builder1.build();
-            PhoneTimeSuggestion two = builder2.build();
+            TelephonyTimeSuggestion one = builder1.build();
+            TelephonyTimeSuggestion two = builder2.build();
             assertEquals(one, two);
             assertEquals(two, one);
         }
 
         builder1.setUtcTime(new TimestampedValue<>(1111L, 2222L));
         {
-            PhoneTimeSuggestion one = builder1.build();
+            TelephonyTimeSuggestion one = builder1.build();
             assertEquals(one, one);
         }
 
         builder2.setUtcTime(new TimestampedValue<>(1111L, 2222L));
         {
-            PhoneTimeSuggestion one = builder1.build();
-            PhoneTimeSuggestion two = builder2.build();
+            TelephonyTimeSuggestion one = builder1.build();
+            TelephonyTimeSuggestion two = builder2.build();
             assertEquals(one, two);
             assertEquals(two, one);
         }
 
-        PhoneTimeSuggestion.Builder builder3 = new PhoneTimeSuggestion.Builder(SLOT_INDEX + 1);
+        TelephonyTimeSuggestion.Builder builder3 =
+                new TelephonyTimeSuggestion.Builder(SLOT_INDEX + 1);
         builder3.setUtcTime(new TimestampedValue<>(1111L, 2222L));
         {
-            PhoneTimeSuggestion one = builder1.build();
-            PhoneTimeSuggestion three = builder3.build();
+            TelephonyTimeSuggestion one = builder1.build();
+            TelephonyTimeSuggestion three = builder3.build();
             assertNotEquals(one, three);
             assertNotEquals(three, one);
         }
@@ -72,15 +73,15 @@
         builder1.addDebugInfo("Debug info 1");
         builder2.addDebugInfo("Debug info 2");
         {
-            PhoneTimeSuggestion one = builder1.build();
-            PhoneTimeSuggestion two = builder2.build();
+            TelephonyTimeSuggestion one = builder1.build();
+            TelephonyTimeSuggestion two = builder2.build();
             assertEquals(one, two);
         }
     }
 
     @Test
     public void testParcelable() {
-        PhoneTimeSuggestion.Builder builder = new PhoneTimeSuggestion.Builder(SLOT_INDEX);
+        TelephonyTimeSuggestion.Builder builder = new TelephonyTimeSuggestion.Builder(SLOT_INDEX);
         assertRoundTripParcelable(builder.build());
 
         builder.setUtcTime(new TimestampedValue<>(1111L, 2222L));
@@ -88,9 +89,9 @@
 
         // DebugInfo should also be stored (but is not checked by equals()
         {
-            PhoneTimeSuggestion suggestion1 = builder.build();
+            TelephonyTimeSuggestion suggestion1 = builder.build();
             builder.addDebugInfo("This is debug info");
-            PhoneTimeSuggestion rtSuggestion1 = roundTripParcelable(suggestion1);
+            TelephonyTimeSuggestion rtSuggestion1 = roundTripParcelable(suggestion1);
             assertEquals(suggestion1.getDebugInfo(), rtSuggestion1.getDebugInfo());
         }
     }
diff --git a/core/tests/coretests/src/android/app/timezonedetector/PhoneTimeZoneSuggestionTest.java b/core/tests/coretests/src/android/app/timezonedetector/PhoneTimeZoneSuggestionTest.java
deleted file mode 100644
index 384dbf9..0000000
--- a/core/tests/coretests/src/android/app/timezonedetector/PhoneTimeZoneSuggestionTest.java
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright 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 android.app.timezonedetector;
-
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
-import static android.app.timezonedetector.ParcelableTestSupport.roundTripParcelable;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertTrue;
-
-import org.junit.Test;
-
-public class PhoneTimeZoneSuggestionTest {
-    private static final int SLOT_INDEX = 99999;
-
-    @Test
-    public void testEquals() {
-        PhoneTimeZoneSuggestion.Builder builder1 = new PhoneTimeZoneSuggestion.Builder(SLOT_INDEX);
-        {
-            PhoneTimeZoneSuggestion one = builder1.build();
-            assertEquals(one, one);
-        }
-
-        PhoneTimeZoneSuggestion.Builder builder2 = new PhoneTimeZoneSuggestion.Builder(SLOT_INDEX);
-        {
-            PhoneTimeZoneSuggestion one = builder1.build();
-            PhoneTimeZoneSuggestion two = builder2.build();
-            assertEquals(one, two);
-            assertEquals(two, one);
-        }
-
-        PhoneTimeZoneSuggestion.Builder builder3 =
-                new PhoneTimeZoneSuggestion.Builder(SLOT_INDEX + 1);
-        {
-            PhoneTimeZoneSuggestion one = builder1.build();
-            PhoneTimeZoneSuggestion three = builder3.build();
-            assertNotEquals(one, three);
-            assertNotEquals(three, one);
-        }
-
-        builder1.setZoneId("Europe/London");
-        builder1.setMatchType(PhoneTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY);
-        builder1.setQuality(PhoneTimeZoneSuggestion.QUALITY_SINGLE_ZONE);
-        {
-            PhoneTimeZoneSuggestion one = builder1.build();
-            PhoneTimeZoneSuggestion two = builder2.build();
-            assertNotEquals(one, two);
-        }
-
-        builder2.setZoneId("Europe/Paris");
-        builder2.setMatchType(PhoneTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY);
-        builder2.setQuality(PhoneTimeZoneSuggestion.QUALITY_SINGLE_ZONE);
-        {
-            PhoneTimeZoneSuggestion one = builder1.build();
-            PhoneTimeZoneSuggestion two = builder2.build();
-            assertNotEquals(one, two);
-        }
-
-        builder1.setZoneId("Europe/Paris");
-        {
-            PhoneTimeZoneSuggestion one = builder1.build();
-            PhoneTimeZoneSuggestion two = builder2.build();
-            assertEquals(one, two);
-        }
-
-        builder1.setMatchType(PhoneTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID);
-        builder2.setMatchType(PhoneTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY);
-        {
-            PhoneTimeZoneSuggestion one = builder1.build();
-            PhoneTimeZoneSuggestion two = builder2.build();
-            assertNotEquals(one, two);
-        }
-
-        builder1.setMatchType(PhoneTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY);
-        {
-            PhoneTimeZoneSuggestion one = builder1.build();
-            PhoneTimeZoneSuggestion two = builder2.build();
-            assertEquals(one, two);
-        }
-
-        builder1.setQuality(PhoneTimeZoneSuggestion.QUALITY_SINGLE_ZONE);
-        builder2.setQuality(PhoneTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS);
-        {
-            PhoneTimeZoneSuggestion one = builder1.build();
-            PhoneTimeZoneSuggestion two = builder2.build();
-            assertNotEquals(one, two);
-        }
-
-        builder1.setQuality(PhoneTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS);
-        {
-            PhoneTimeZoneSuggestion one = builder1.build();
-            PhoneTimeZoneSuggestion two = builder2.build();
-            assertEquals(one, two);
-        }
-
-        // DebugInfo must not be considered in equals().
-        {
-            PhoneTimeZoneSuggestion one = builder1.build();
-            PhoneTimeZoneSuggestion two = builder2.build();
-            one.addDebugInfo("Debug info 1");
-            two.addDebugInfo("Debug info 2");
-            assertEquals(one, two);
-        }
-    }
-
-    @Test(expected = RuntimeException.class)
-    public void testBuilderValidates_emptyZone_badMatchType() {
-        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();
-    }
-
-    @Test(expected = RuntimeException.class)
-    public void testBuilderValidates_zoneSet_badMatchType() {
-        PhoneTimeZoneSuggestion.Builder builder = new PhoneTimeZoneSuggestion.Builder(SLOT_INDEX);
-        builder.setZoneId("Europe/London");
-        builder.setQuality(PhoneTimeZoneSuggestion.QUALITY_SINGLE_ZONE);
-        builder.build();
-    }
-
-    @Test
-    public void testParcelable() {
-        PhoneTimeZoneSuggestion.Builder builder = new PhoneTimeZoneSuggestion.Builder(SLOT_INDEX);
-        assertRoundTripParcelable(builder.build());
-
-        builder.setZoneId("Europe/London");
-        builder.setMatchType(PhoneTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID);
-        builder.setQuality(PhoneTimeZoneSuggestion.QUALITY_SINGLE_ZONE);
-        PhoneTimeZoneSuggestion suggestion1 = builder.build();
-        assertRoundTripParcelable(suggestion1);
-
-        // DebugInfo should also be stored (but is not checked by equals()
-        String debugString = "This is debug info";
-        suggestion1.addDebugInfo(debugString);
-        PhoneTimeZoneSuggestion suggestion1_2 = roundTripParcelable(suggestion1);
-        assertEquals(suggestion1, suggestion1_2);
-        assertTrue(suggestion1_2.getDebugInfo().contains(debugString));
-    }
-}
diff --git a/core/tests/coretests/src/android/app/timezonedetector/TelephonyTimeZoneSuggestionTest.java b/core/tests/coretests/src/android/app/timezonedetector/TelephonyTimeZoneSuggestionTest.java
new file mode 100644
index 0000000..59d55b7
--- /dev/null
+++ b/core/tests/coretests/src/android/app/timezonedetector/TelephonyTimeZoneSuggestionTest.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright 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 android.app.timezonedetector;
+
+import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.timezonedetector.ParcelableTestSupport.roundTripParcelable;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+public class TelephonyTimeZoneSuggestionTest {
+    private static final int SLOT_INDEX = 99999;
+
+    @Test
+    public void testEquals() {
+        TelephonyTimeZoneSuggestion.Builder builder1 =
+                new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX);
+        {
+            TelephonyTimeZoneSuggestion one = builder1.build();
+            assertEquals(one, one);
+        }
+
+        TelephonyTimeZoneSuggestion.Builder builder2 =
+                new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX);
+        {
+            TelephonyTimeZoneSuggestion one = builder1.build();
+            TelephonyTimeZoneSuggestion two = builder2.build();
+            assertEquals(one, two);
+            assertEquals(two, one);
+        }
+
+        TelephonyTimeZoneSuggestion.Builder builder3 =
+                new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX + 1);
+        {
+            TelephonyTimeZoneSuggestion one = builder1.build();
+            TelephonyTimeZoneSuggestion three = builder3.build();
+            assertNotEquals(one, three);
+            assertNotEquals(three, one);
+        }
+
+        builder1.setZoneId("Europe/London");
+        builder1.setMatchType(TelephonyTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY);
+        builder1.setQuality(TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE);
+        {
+            TelephonyTimeZoneSuggestion one = builder1.build();
+            TelephonyTimeZoneSuggestion two = builder2.build();
+            assertNotEquals(one, two);
+        }
+
+        builder2.setZoneId("Europe/Paris");
+        builder2.setMatchType(TelephonyTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY);
+        builder2.setQuality(TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE);
+        {
+            TelephonyTimeZoneSuggestion one = builder1.build();
+            TelephonyTimeZoneSuggestion two = builder2.build();
+            assertNotEquals(one, two);
+        }
+
+        builder1.setZoneId("Europe/Paris");
+        {
+            TelephonyTimeZoneSuggestion one = builder1.build();
+            TelephonyTimeZoneSuggestion two = builder2.build();
+            assertEquals(one, two);
+        }
+
+        builder1.setMatchType(TelephonyTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID);
+        builder2.setMatchType(TelephonyTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY);
+        {
+            TelephonyTimeZoneSuggestion one = builder1.build();
+            TelephonyTimeZoneSuggestion two = builder2.build();
+            assertNotEquals(one, two);
+        }
+
+        builder1.setMatchType(TelephonyTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY);
+        {
+            TelephonyTimeZoneSuggestion one = builder1.build();
+            TelephonyTimeZoneSuggestion two = builder2.build();
+            assertEquals(one, two);
+        }
+
+        builder1.setQuality(TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE);
+        builder2.setQuality(
+                TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS);
+        {
+            TelephonyTimeZoneSuggestion one = builder1.build();
+            TelephonyTimeZoneSuggestion two = builder2.build();
+            assertNotEquals(one, two);
+        }
+
+        builder1.setQuality(
+                TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS);
+        {
+            TelephonyTimeZoneSuggestion one = builder1.build();
+            TelephonyTimeZoneSuggestion two = builder2.build();
+            assertEquals(one, two);
+        }
+
+        // DebugInfo must not be considered in equals().
+        {
+            TelephonyTimeZoneSuggestion one = builder1.build();
+            TelephonyTimeZoneSuggestion two = builder2.build();
+            one.addDebugInfo("Debug info 1");
+            two.addDebugInfo("Debug info 2");
+            assertEquals(one, two);
+        }
+    }
+
+    @Test(expected = RuntimeException.class)
+    public void testBuilderValidates_emptyZone_badMatchType() {
+        TelephonyTimeZoneSuggestion.Builder builder =
+                new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX);
+        // No zone ID, so match type should be left unset.
+        builder.setMatchType(TelephonyTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET);
+        builder.build();
+    }
+
+    @Test(expected = RuntimeException.class)
+    public void testBuilderValidates_zoneSet_badMatchType() {
+        TelephonyTimeZoneSuggestion.Builder builder =
+                new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX);
+        builder.setZoneId("Europe/London");
+        builder.setQuality(TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE);
+        builder.build();
+    }
+
+    @Test
+    public void testParcelable() {
+        TelephonyTimeZoneSuggestion.Builder builder =
+                new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX);
+        assertRoundTripParcelable(builder.build());
+
+        builder.setZoneId("Europe/London");
+        builder.setMatchType(TelephonyTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID);
+        builder.setQuality(TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE);
+        TelephonyTimeZoneSuggestion suggestion1 = builder.build();
+        assertRoundTripParcelable(suggestion1);
+
+        // DebugInfo should also be stored (but is not checked by equals()
+        String debugString = "This is debug info";
+        suggestion1.addDebugInfo(debugString);
+        TelephonyTimeZoneSuggestion suggestion1_2 = roundTripParcelable(suggestion1);
+        assertEquals(suggestion1, suggestion1_2);
+        assertTrue(suggestion1_2.getDebugInfo().contains(debugString));
+    }
+}
diff --git a/core/tests/coretests/src/android/content/integrity/AtomicFormulaTest.java b/core/tests/coretests/src/android/content/integrity/AtomicFormulaTest.java
index bf78203..3273e5d 100644
--- a/core/tests/coretests/src/android/content/integrity/AtomicFormulaTest.java
+++ b/core/tests/coretests/src/android/content/integrity/AtomicFormulaTest.java
@@ -90,18 +90,18 @@
     }
 
     @Test
-    public void testValidAtomicFormula_stringValue_appCertificateAutoHashed() {
+    public void testValidAtomicFormula_stringValue_appCertificateIsNotAutoHashed() {
         String appCert = "cert";
         StringAtomicFormula stringAtomicFormula =
                 new StringAtomicFormula(AtomicFormula.APP_CERTIFICATE, appCert);
 
         assertThat(stringAtomicFormula.getKey()).isEqualTo(AtomicFormula.APP_CERTIFICATE);
-        assertThat(stringAtomicFormula.getValue()).doesNotMatch(appCert);
+        assertThat(stringAtomicFormula.getValue()).matches(appCert);
         assertThat(stringAtomicFormula.getIsHashedValue()).isTrue();
     }
 
     @Test
-    public void testValidAtomicFormula_stringValue_installerCertificateAutoHashed() {
+    public void testValidAtomicFormula_stringValue_installerCertificateIsNotAutoHashed() {
         String installerCert = "cert";
         StringAtomicFormula stringAtomicFormula =
                 new StringAtomicFormula(AtomicFormula.INSTALLER_CERTIFICATE,
@@ -109,7 +109,7 @@
 
         assertThat(stringAtomicFormula.getKey()).isEqualTo(
                 AtomicFormula.INSTALLER_CERTIFICATE);
-        assertThat(stringAtomicFormula.getValue()).doesNotMatch(installerCert);
+        assertThat(stringAtomicFormula.getValue()).matches(installerCert);
         assertThat(stringAtomicFormula.getIsHashedValue()).isTrue();
     }
 
diff --git a/core/tests/coretests/src/android/content/integrity/IntegrityFormulaTest.java b/core/tests/coretests/src/android/content/integrity/IntegrityFormulaTest.java
index c180602..75ef1f2 100644
--- a/core/tests/coretests/src/android/content/integrity/IntegrityFormulaTest.java
+++ b/core/tests/coretests/src/android/content/integrity/IntegrityFormulaTest.java
@@ -40,7 +40,7 @@
 
         assertThat(stringAtomicFormula.getKey()).isEqualTo(AtomicFormula.PACKAGE_NAME);
         assertThat(stringAtomicFormula.getValue()).isEqualTo(packageName);
-        assertThat(stringAtomicFormula.getIsHashedValue()).isEqualTo(false);
+        assertThat(stringAtomicFormula.getIsHashedValue()).isFalse();
     }
 
     @Test
@@ -53,8 +53,8 @@
                 (AtomicFormula.StringAtomicFormula) formula;
 
         assertThat(stringAtomicFormula.getKey()).isEqualTo(AtomicFormula.APP_CERTIFICATE);
-        assertThat(stringAtomicFormula.getValue()).doesNotMatch(appCertificate);
-        assertThat(stringAtomicFormula.getIsHashedValue()).isEqualTo(true);
+        assertThat(stringAtomicFormula.getValue()).matches(appCertificate);
+        assertThat(stringAtomicFormula.getIsHashedValue()).isTrue();
     }
 
     @Test
@@ -68,7 +68,7 @@
 
         assertThat(stringAtomicFormula.getKey()).isEqualTo(AtomicFormula.INSTALLER_NAME);
         assertThat(stringAtomicFormula.getValue()).isEqualTo(installerName);
-        assertThat(stringAtomicFormula.getIsHashedValue()).isEqualTo(false);
+        assertThat(stringAtomicFormula.getIsHashedValue()).isFalse();
     }
 
     @Test
@@ -81,8 +81,8 @@
                 (AtomicFormula.StringAtomicFormula) formula;
 
         assertThat(stringAtomicFormula.getKey()).isEqualTo(AtomicFormula.INSTALLER_CERTIFICATE);
-        assertThat(stringAtomicFormula.getValue()).doesNotMatch(installerCertificate);
-        assertThat(stringAtomicFormula.getIsHashedValue()).isEqualTo(true);
+        assertThat(stringAtomicFormula.getValue()).matches(installerCertificate);
+        assertThat(stringAtomicFormula.getIsHashedValue()).isTrue();
     }
 
     @Test
diff --git a/core/tests/coretests/src/android/os/BuildTest.java b/core/tests/coretests/src/android/os/BuildTest.java
index decc768..2295eb9 100644
--- a/core/tests/coretests/src/android/os/BuildTest.java
+++ b/core/tests/coretests/src/android/os/BuildTest.java
@@ -60,7 +60,7 @@
         assertNotEmpty("BRAND", Build.BRAND);
         assertNotEmpty("MODEL", Build.MODEL);
         assertNotEmpty("VERSION.INCREMENTAL", Build.VERSION.INCREMENTAL);
-        assertNotEmpty("VERSION.RELEASE", Build.VERSION.RELEASE);
+        assertNotEmpty("VERSION.RELEASE", Build.VERSION.RELEASE_OR_CODENAME);
         assertNotEmpty("TYPE", Build.TYPE);
         Assert.assertNotNull("TAGS", Build.TAGS); // TAGS is allowed to be empty.
         assertNotEmpty("FINGERPRINT", Build.FINGERPRINT);
diff --git a/core/tests/coretests/src/android/provider/DeviceConfigTest.java b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
index 84c42db..d649b94 100644
--- a/core/tests/coretests/src/android/provider/DeviceConfigTest.java
+++ b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
@@ -376,6 +376,24 @@
     }
 
     @Test
+    public void resetToDefault_makeDefault() {
+        DeviceConfig.setProperty(NAMESPACE, KEY, VALUE, true);
+        assertThat(DeviceConfig.getProperty(NAMESPACE, KEY)).isEqualTo(VALUE);
+
+        DeviceConfig.resetToDefaults(Settings.RESET_MODE_PACKAGE_DEFAULTS, NAMESPACE);
+        assertThat(DeviceConfig.getProperty(NAMESPACE, KEY)).isEqualTo(VALUE);
+    }
+
+    @Test
+    public void resetToDefault_doNotMakeDefault() {
+        DeviceConfig.setProperty(NAMESPACE, KEY, VALUE, false);
+        assertThat(DeviceConfig.getProperty(NAMESPACE, KEY)).isEqualTo(VALUE);
+
+        DeviceConfig.resetToDefaults(Settings.RESET_MODE_PACKAGE_DEFAULTS, NAMESPACE);
+        assertThat(DeviceConfig.getProperty(NAMESPACE, KEY)).isNull();
+    }
+
+    @Test
     public void getProperties_fullNamespace() {
         Properties properties = DeviceConfig.getProperties(NAMESPACE);
         assertThat(properties.getKeyset()).isEmpty();
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
index 8b8e9ea..b71c580 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
@@ -19,7 +19,6 @@
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accessibilityservice.IAccessibilityServiceConnection;
 import android.content.pm.ParceledListSlice;
-import android.graphics.Bitmap;
 import android.graphics.Region;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -157,9 +156,5 @@
         return -1;
     }
 
-    public Bitmap takeScreenshot(int displayId) {
-        return null;
-    }
-
-    public void takeScreenshotWithCallback(int displayId, RemoteCallback callback) {}
+    public void takeScreenshot(int displayId, RemoteCallback callback) {}
 }
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java
index 8faf790..1ca4649 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java
@@ -25,7 +25,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.os.LocaleList;
-import android.service.textclassifier.TextClassifierService;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -64,9 +63,7 @@
 
     @Test
     public void testGetSystemTextClassifier() {
-        assertTrue(
-                TextClassifierService.getServiceComponentName(mContext) == null
-                || mTcm.getTextClassifier(TextClassifier.SYSTEM) instanceof SystemTextClassifier);
+        assertTrue(mTcm.getTextClassifier(TextClassifier.SYSTEM) instanceof SystemTextClassifier);
     }
 
     @Test
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java
index 2304ba6..372a478 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java
@@ -27,6 +27,7 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.LocaleList;
+import android.service.textclassifier.TextClassifierService;
 import android.text.Spannable;
 import android.text.SpannableString;
 
@@ -58,11 +59,12 @@
 public class TextClassifierTest {
     private static final String LOCAL = "local";
     private static final String SESSION = "session";
+    private static final String DEFAULT = "default";
 
     // TODO: Add SYSTEM, which tests TextClassifier.SYSTEM.
     @Parameterized.Parameters(name = "{0}")
     public static Iterable<Object> textClassifierTypes() {
-        return Arrays.asList(LOCAL, SESSION);
+        return Arrays.asList(LOCAL, SESSION, DEFAULT);
     }
 
     @Parameterized.Parameter
@@ -84,13 +86,15 @@
 
         if (mTextClassifierType.equals(LOCAL)) {
             mClassifier = mTcm.getTextClassifier(TextClassifier.LOCAL);
-        } else {
+        } else if (mTextClassifierType.equals(SESSION)) {
             mClassifier = mTcm.createTextClassificationSession(
                     new TextClassificationContext.Builder(
                             "android",
                             TextClassifier.WIDGET_TYPE_NOTIFICATION)
                             .build(),
                     mTcm.getTextClassifier(TextClassifier.LOCAL));
+        } else {
+            mClassifier = TextClassifierService.getDefaultTextClassifierImplementation(mContext);
         }
     }
 
@@ -369,16 +373,14 @@
                 mClassifier.generateLinks(request).apply(url, 0, null));
     }
 
-
-    @Test(expected = IllegalArgumentException.class)
+    @Test
     public void testGenerateLinks_tooLong() {
-        if (isTextClassifierDisabled()) {
-            throw new IllegalArgumentException("pass if disabled");
-        }
+        if (isTextClassifierDisabled()) return;
         char[] manySpaces = new char[mClassifier.getMaxGenerateLinksTextLength() + 1];
         Arrays.fill(manySpaces, ' ');
         TextLinks.Request request = new TextLinks.Request.Builder(new String(manySpaces)).build();
-        mClassifier.generateLinks(request);
+        TextLinks links = mClassifier.generateLinks(request);
+        assertTrue(links.getLinks().isEmpty());
     }
 
     @Test
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index df6b906..ce71beb 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -38,6 +38,7 @@
 import static org.hamcrest.CoreMatchers.notNullValue;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
@@ -355,6 +356,7 @@
         // enable the work tab feature flag
         ResolverActivity.ENABLE_TABBED_VIEW = true;
 
+        markWorkProfileUserAvailable();
         Intent sendIntent = createSendTextIntent();
         List<ResolvedComponentInfo> resolvedComponentInfos =
                 createResolvedComponentsForTestWithOtherProfile(2);
@@ -1209,17 +1211,24 @@
         // enable the work tab feature flag
         ResolverActivity.ENABLE_TABBED_VIEW = true;
         int personalProfileTargets = 3;
+        int otherProfileTargets = 1;
         List<ResolvedComponentInfo> personalResolvedComponentInfos =
-                createResolvedComponentsForTest(personalProfileTargets);
+                createResolvedComponentsForTestWithOtherProfile(
+                        personalProfileTargets + otherProfileTargets, /* userID */ 10);
         int workProfileTargets = 4;
         List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(
                 workProfileTargets);
         when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
                 Mockito.anyBoolean(),
-                Mockito.isA(List.class))).thenReturn(personalResolvedComponentInfos);
+                Mockito.isA(List.class)))
+                .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
         when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
                 Mockito.anyBoolean(),
-                Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
+                Mockito.isA(List.class))).thenReturn(new ArrayList<>(workResolvedComponentInfos));
+        when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class),
+                eq(UserHandle.SYSTEM))).thenReturn(new ArrayList<>(personalResolvedComponentInfos));
         Intent sendIntent = createSendTextIntent();
         sendIntent.setType("TestType");
         markWorkProfileUserAvailable();
@@ -1229,8 +1238,6 @@
         waitForIdle();
 
         assertThat(activity.getCurrentUserHandle().getIdentifier(), is(0));
-        // The work list adapter must only be filled when we open the work tab
-        assertThat(activity.getWorkListAdapter().getCount(), is(0));
         onView(withText(R.string.resolver_work_tab)).perform(click());
         assertThat(activity.getCurrentUserHandle().getIdentifier(), is(10));
         assertThat(activity.getPersonalListAdapter().getCount(), is(personalProfileTargets));
@@ -1243,11 +1250,22 @@
         ResolverActivity.ENABLE_TABBED_VIEW = true;
         markWorkProfileUserAvailable();
         int workProfileTargets = 4;
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
         List<ResolvedComponentInfo> workResolvedComponentInfos =
                 createResolvedComponentsForTest(workProfileTargets);
+        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class)))
+                .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
         when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
                 Mockito.anyBoolean(),
                 Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
+        when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class),
+                eq(UserHandle.SYSTEM)))
+                .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
         Intent sendIntent = createSendTextIntent();
         sendIntent.setType("TestType");
 
@@ -1357,6 +1375,20 @@
         return infoList;
     }
 
+    private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile(
+            int numberOfResults, int userId) {
+        List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
+        for (int i = 0; i < numberOfResults; i++) {
+            if (i == 0) {
+                infoList.add(
+                        ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId));
+            } else {
+                infoList.add(ResolverDataProvider.createResolvedComponentInfo(i));
+            }
+        }
+        return infoList;
+    }
+
     private List<ResolvedComponentInfo> createResolvedComponentsForTestWithUserId(
             int numberOfResults, int userId) {
         List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
index eee62bb..a68b5908 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
@@ -16,10 +16,15 @@
 
 package com.android.internal.app;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import android.annotation.Nullable;
+import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionManager;
+import android.app.prediction.AppPredictor;
 import android.app.usage.UsageStatsManager;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -40,6 +45,8 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
+import org.mockito.Mockito;
+
 import java.util.function.Function;
 
 public class ChooserWrapperActivity extends ChooserActivity {
@@ -173,6 +180,12 @@
         return mMultiProfilePagerAdapter.getCurrentUserHandle();
     }
 
+    @Override
+    public Context createContextAsUser(UserHandle user, int flags) {
+        // return the current context as a work profile doesn't really exist in these tests
+        return getApplicationContext();
+    }
+
     /**
      * We cannot directly mock the activity created since instrumentation creates it.
      * <p>
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
index 911490f..5f4194a 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
@@ -225,6 +225,7 @@
         // enable the work tab feature flag
         ResolverActivity.ENABLE_TABBED_VIEW = true;
 
+        markWorkProfileUserAvailable();
         Intent sendIntent = createSendImageIntent();
         List<ResolvedComponentInfo> resolvedComponentInfos =
                 createResolvedComponentsForTestWithOtherProfile(2);
@@ -246,7 +247,6 @@
             chosen[0] = targetInfo.getResolveInfo();
             return true;
         };
-
         // Make a stable copy of the components as the original list may be modified
         List<ResolvedComponentInfo> stableCopy =
                 createResolvedComponentsForTestWithOtherProfile(2);
@@ -443,7 +443,7 @@
         // enable the work tab feature flag
         ResolverActivity.ENABLE_TABBED_VIEW = true;
         List<ResolvedComponentInfo> personalResolvedComponentInfos =
-                createResolvedComponentsForTest(3);
+                createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
         List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
         when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
                 Mockito.anyBoolean(),
@@ -451,6 +451,11 @@
         when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
                 Mockito.anyBoolean(),
                 Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
+        when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class),
+                eq(UserHandle.SYSTEM)))
+                .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
         Intent sendIntent = createSendImageIntent();
         markWorkProfileUserAvailable();
 
@@ -478,17 +483,20 @@
                 Mockito.anyBoolean(),
                 Mockito.anyBoolean(),
                 Mockito.isA(List.class),
-                eq(sOverrides.workProfileUserHandle))).thenReturn(new ArrayList<>(workResolvedComponentInfos));
+                eq(sOverrides.workProfileUserHandle)))
+                .thenReturn(new ArrayList<>(workResolvedComponentInfos));
         when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(),
                 Mockito.anyBoolean(),
                 Mockito.isA(List.class),
-                eq(sOverrides.workProfileUserHandle))).thenReturn(new ArrayList<>(workResolvedComponentInfos));
+                eq(sOverrides.workProfileUserHandle)))
+                .thenReturn(new ArrayList<>(workResolvedComponentInfos));
         when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
                 Mockito.anyBoolean(),
                 Mockito.isA(List.class))).thenReturn(new ArrayList<>(workResolvedComponentInfos));
         when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
                 Mockito.anyBoolean(),
-                Mockito.isA(List.class))).thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+                Mockito.isA(List.class)))
+                .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
         when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(),
                 Mockito.anyBoolean(),
                 Mockito.isA(List.class),
@@ -502,7 +510,7 @@
         onView(withText(R.string.resolver_work_tab)).perform(click());
 
         assertThat(activity.getCurrentUserHandle().getIdentifier(), is(10));
-        assertThat(activity.getPersonalListAdapter().getCount(), is(3));
+        assertThat(activity.getPersonalListAdapter().getCount(), is(2));
     }
 
     @Test
@@ -511,14 +519,20 @@
         ResolverActivity.ENABLE_TABBED_VIEW = true;
         markWorkProfileUserAvailable();
         List<ResolvedComponentInfo> personalResolvedComponentInfos =
-                createResolvedComponentsForTest(3);
+                createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
         List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
         when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
                 Mockito.anyBoolean(),
-                Mockito.isA(List.class))).thenReturn(personalResolvedComponentInfos);
+                Mockito.isA(List.class)))
+                .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
         when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
                 Mockito.anyBoolean(),
                 Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
+        when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class),
+                eq(UserHandle.SYSTEM)))
+                .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
         Intent sendIntent = createSendImageIntent();
 
         final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
@@ -536,14 +550,20 @@
         ResolverActivity.ENABLE_TABBED_VIEW = true;
         markWorkProfileUserAvailable();
         List<ResolvedComponentInfo> personalResolvedComponentInfos =
-                createResolvedComponentsForTest(3);
+                createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
         List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
         when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
                 Mockito.anyBoolean(),
-                Mockito.isA(List.class))).thenReturn(personalResolvedComponentInfos);
+                Mockito.isA(List.class)))
+                .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
         when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
                 Mockito.anyBoolean(),
                 Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
+        when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class),
+                eq(UserHandle.SYSTEM)))
+                .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
         Intent sendIntent = createSendImageIntent();
         ResolveInfo[] chosen = new ResolveInfo[1];
         sOverrides.onSafelyStartCallback = targetInfo -> {
@@ -587,17 +607,20 @@
                 Mockito.anyBoolean(),
                 Mockito.anyBoolean(),
                 Mockito.isA(List.class),
-                eq(sOverrides.workProfileUserHandle))).thenReturn(new ArrayList<>(workResolvedComponentInfos));
+                eq(sOverrides.workProfileUserHandle)))
+                .thenReturn(new ArrayList<>(workResolvedComponentInfos));
         when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(),
                 Mockito.anyBoolean(),
                 Mockito.isA(List.class),
-                eq(sOverrides.workProfileUserHandle))).thenReturn(new ArrayList<>(workResolvedComponentInfos));
+                eq(sOverrides.workProfileUserHandle)))
+                .thenReturn(new ArrayList<>(workResolvedComponentInfos));
         when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
                 Mockito.anyBoolean(),
                 Mockito.isA(List.class))).thenReturn(new ArrayList<>(workResolvedComponentInfos));
         when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
                 Mockito.anyBoolean(),
-                Mockito.isA(List.class))).thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+                Mockito.isA(List.class)))
+                .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
         when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(),
                 Mockito.anyBoolean(),
                 Mockito.isA(List.class),
@@ -678,6 +701,20 @@
         return infoList;
     }
 
+    private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile(
+            int numberOfResults, int userId) {
+        List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
+        for (int i = 0; i < numberOfResults; i++) {
+            if (i == 0) {
+                infoList.add(
+                        ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId));
+            } else {
+                infoList.add(ResolverDataProvider.createResolvedComponentInfo(i));
+            }
+        }
+        return infoList;
+    }
+
     private void waitForIdle() {
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
diff --git a/data/etc/com.android.settings.xml b/data/etc/com.android.settings.xml
index a200a51..fe1182e 100644
--- a/data/etc/com.android.settings.xml
+++ b/data/etc/com.android.settings.xml
@@ -26,6 +26,7 @@
         <permission name="android.permission.DELETE_PACKAGES"/>
         <permission name="android.permission.FORCE_STOP_PACKAGES"/>
         <permission name="android.permission.LOCAL_MAC_ADDRESS"/>
+        <permission name="android.permission.LOG_COMPAT_CHANGE" />
         <permission name="android.permission.MANAGE_DEBUGGING"/>
         <permission name="android.permission.MANAGE_DEVICE_ADMINS"/>
         <permission name="android.permission.MANAGE_FINGERPRINT"/>
@@ -37,8 +38,10 @@
         <permission name="android.permission.MODIFY_PHONE_STATE"/>
         <permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
         <permission name="android.permission.MOVE_PACKAGE"/>
+        <permission name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG" />
         <permission name="android.permission.OVERRIDE_WIFI_CONFIG"/>
         <permission name="android.permission.PACKAGE_USAGE_STATS"/>
+        <permission name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
         <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
         <permission name="android.permission.READ_SEARCH_INDEXABLES"/>
         <permission name="android.permission.REBOOT"/>
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index da50550..6929d0d 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -72,6 +72,11 @@
         <group gid="net_admin" />
     </permission>
 
+    <permission name="android.permission.MAINLINE_NETWORK_STACK" >
+        <group gid="net_admin" />
+        <group gid="net_raw" />
+    </permission>
+
     <!-- The group that /cache belongs to, linked to the permission
          set on the applications that can access /cache -->
     <permission name="android.permission.ACCESS_CACHE_FILESYSTEM" >
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index b5eba09..f83fb3f 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -169,7 +169,7 @@
         <permission name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"/>
         <permission name="android.permission.STATUS_BAR"/>
         <permission name="android.permission.STOP_APP_SWITCHES"/>
-        <permission name="android.permission.SUGGEST_PHONE_TIME_AND_ZONE"/>
+        <permission name="android.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE"/>
         <permission name="android.permission.UPDATE_APP_OPS_STATS"/>
         <permission name="android.permission.UPDATE_DEVICE_STATS"/>
         <permission name="android.permission.UPDATE_LOCK"/>
@@ -366,6 +366,10 @@
         <permission name="android.permission.CONTROL_INCALL_EXPERIENCE"/>
         <!-- Permission required for Tethering CTS tests. -->
         <permission name="android.permission.TETHER_PRIVILEGED"/>
+        <!-- Permissions required for ganting and logging -->
+        <permission name="android.permission.LOG_COMPAT_CHANGE" />
+        <permission name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
+        <permission name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG" />
         <!-- Permissions required to test ambient display. -->
         <permission name="android.permission.READ_DREAM_STATE" />
         <permission name="android.permission.WRITE_DREAM_STATE" />
diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
index 3b86413..d08bfcf 100644
--- a/graphics/java/android/graphics/HardwareRenderer.java
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -157,7 +157,7 @@
     public HardwareRenderer() {
         mRootNode = RenderNode.adopt(nCreateRootRenderNode());
         mRootNode.setClipToBounds(false);
-        mNativeProxy = nCreateProxy(!mOpaque, mRootNode.mNativeRenderNode);
+        mNativeProxy = nCreateProxy(!mOpaque, mIsWideGamut, mRootNode.mNativeRenderNode);
         if (mNativeProxy == 0) {
             throw new OutOfMemoryError("Unable to create hardware renderer");
         }
@@ -1085,7 +1085,8 @@
 
     private static native long nCreateRootRenderNode();
 
-    private static native long nCreateProxy(boolean translucent, long rootRenderNode);
+    private static native long nCreateProxy(boolean translucent, boolean isWideGamut,
+            long rootRenderNode);
 
     private static native void nDeleteProxy(long nativeProxy);
 
diff --git a/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java b/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java
index dcc6b95..1290633 100644
--- a/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java
+++ b/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java
@@ -38,6 +38,10 @@
         ICredentialStoreFactory storeFactory =
                 ICredentialStoreFactory.Stub.asInterface(
                     ServiceManager.getService("android.security.identity"));
+        if (storeFactory == null) {
+            // This can happen if credstore is not running or not installed.
+            return null;
+        }
 
         ICredentialStore credStore = null;
         try {
diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp
index 3681c69..a3d552f 100644
--- a/libs/hwui/HardwareBitmapUploader.cpp
+++ b/libs/hwui/HardwareBitmapUploader.cpp
@@ -187,7 +187,9 @@
             EGLSyncKHR fence = mUploadThread->queue().runSync([&]() -> EGLSyncKHR {
                 AutoSkiaGlTexture glTexture;
                 glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, autoImage.image);
-                GL_CHECKPOINT(MODERATE);
+                if (GLUtils::dumpGLErrors()) {
+                    return EGL_NO_SYNC_KHR;
+                }
 
                 // glTexSubImage2D is synchronous in sense that it memcpy() from pointer that we
                 // provide.
@@ -195,19 +197,26 @@
                 // when we first use it in drawing
                 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bitmap.width(), bitmap.height(),
                                 format.format, format.type, bitmap.getPixels());
-                GL_CHECKPOINT(MODERATE);
+                if (GLUtils::dumpGLErrors()) {
+                    return EGL_NO_SYNC_KHR;
+                }
 
                 EGLSyncKHR uploadFence =
                         eglCreateSyncKHR(eglGetCurrentDisplay(), EGL_SYNC_FENCE_KHR, NULL);
-                LOG_ALWAYS_FATAL_IF(uploadFence == EGL_NO_SYNC_KHR,
-                                    "Could not create sync fence %#x", eglGetError());
+                if (uploadFence == EGL_NO_SYNC_KHR) {
+                    ALOGW("Could not create sync fence %#x", eglGetError());
+                };
                 glFlush();
+                GLUtils::dumpGLErrors();
                 return uploadFence;
             });
 
+            if (fence == EGL_NO_SYNC_KHR) {
+                return false;
+            }
             EGLint waitStatus = eglClientWaitSyncKHR(display, fence, 0, FENCE_TIMEOUT);
-            LOG_ALWAYS_FATAL_IF(waitStatus != EGL_CONDITION_SATISFIED_KHR,
-                                "Failed to wait for the fence %#x", eglGetError());
+            ALOGE_IF(waitStatus != EGL_CONDITION_SATISFIED_KHR,
+                    "Failed to wait for the fence %#x", eglGetError());
 
             eglDestroySyncKHR(display, fence);
         }
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index e7efe2f..8d5acc6 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -171,17 +171,15 @@
 }
 
 bool SkiaOpenGLPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBehavior,
-                                    ColorMode colorMode, uint32_t extraBuffers) {
+                                    uint32_t extraBuffers) {
     if (mEglSurface != EGL_NO_SURFACE) {
         mEglManager.destroySurface(mEglSurface);
         mEglSurface = EGL_NO_SURFACE;
     }
 
-    setSurfaceColorProperties(colorMode);
-
     if (surface) {
         mRenderThread.requireGlContext();
-        auto newSurface = mEglManager.createSurface(surface, colorMode, mSurfaceColorSpace);
+        auto newSurface = mEglManager.createSurface(surface, mColorMode, mSurfaceColorSpace);
         if (!newSurface) {
             return false;
         }
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
index 3fe0f92..e482cad 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
@@ -44,7 +44,7 @@
                      FrameInfo* currentFrameInfo, bool* requireSwap) override;
     DeferredLayerUpdater* createTextureLayer() override;
     bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior,
-                    renderthread::ColorMode colorMode, uint32_t extraBuffers) override;
+                    uint32_t extraBuffers) override;
     void onStop() override;
     bool isSurfaceReady() override;
     bool isContextReady() override;
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 6f4af3d..29b4dd7 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -44,6 +44,7 @@
 namespace skiapipeline {
 
 SkiaPipeline::SkiaPipeline(RenderThread& thread) : mRenderThread(thread) {
+    setSurfaceColorProperties(mColorMode);
 }
 
 SkiaPipeline::~SkiaPipeline() {
@@ -584,6 +585,7 @@
 }
 
 void SkiaPipeline::setSurfaceColorProperties(ColorMode colorMode) {
+    mColorMode = colorMode;
     if (colorMode == ColorMode::SRGB) {
         mSurfaceColorType = SkColorType::kN32_SkColorType;
         mSurfaceColorSpace = SkColorSpace::MakeSRGB();
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h
index af8414d..8341164 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.h
@@ -50,6 +50,7 @@
     bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator,
                              ErrorHandler* errorHandler) override;
 
+    void setSurfaceColorProperties(renderthread::ColorMode colorMode) override;
     SkColorType getSurfaceColorType() const override { return mSurfaceColorType; }
     sk_sp<SkColorSpace> getSurfaceColorSpace() override { return mSurfaceColorSpace; }
 
@@ -72,9 +73,10 @@
 
 protected:
     void dumpResourceCacheUsage() const;
-    void setSurfaceColorProperties(renderthread::ColorMode colorMode);
 
     renderthread::RenderThread& mRenderThread;
+
+    renderthread::ColorMode mColorMode = renderthread::ColorMode::SRGB;
     SkColorType mSurfaceColorType;
     sk_sp<SkColorSpace> mSurfaceColorSpace;
 
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
index ad7c706..535a199 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
@@ -117,17 +117,16 @@
 void SkiaVulkanPipeline::onStop() {}
 
 bool SkiaVulkanPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBehavior,
-                                    ColorMode colorMode, uint32_t extraBuffers) {
+                                    uint32_t extraBuffers) {
     if (mVkSurface) {
         mVkManager.destroySurface(mVkSurface);
         mVkSurface = nullptr;
     }
 
-    setSurfaceColorProperties(colorMode);
     if (surface) {
         mRenderThread.requireVkContext();
         mVkSurface =
-                mVkManager.createSurface(surface, colorMode, mSurfaceColorSpace, mSurfaceColorType,
+                mVkManager.createSurface(surface, mColorMode, mSurfaceColorSpace, mSurfaceColorType,
                                          mRenderThread.getGrContext(), extraBuffers);
     }
 
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
index 3173478..c8bf233 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
@@ -43,7 +43,7 @@
                      FrameInfo* currentFrameInfo, bool* requireSwap) override;
     DeferredLayerUpdater* createTextureLayer() override;
     bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior,
-                    renderthread::ColorMode colorMode, uint32_t extraBuffers) override;
+                    uint32_t extraBuffers) override;
     void onStop() override;
     bool isSurfaceReady() override;
     bool isContextReady() override;
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index c1435d1e..91f9447 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -161,9 +161,8 @@
         mRenderAheadCapacity = mRenderAheadDepth;
     }
 
-    ColorMode colorMode = mWideColorGamut ? ColorMode::WideColorGamut : ColorMode::SRGB;
     bool hasSurface = mRenderPipeline->setSurface(
-            mNativeSurface ? mNativeSurface->getNativeWindow() : nullptr, mSwapBehavior, colorMode,
+            mNativeSurface ? mNativeSurface->getNativeWindow() : nullptr, mSwapBehavior,
             mRenderAheadCapacity);
 
     mFrameNumber = -1;
@@ -174,7 +173,7 @@
         // Enable frame stats after the surface has been bound to the appropriate graphics API.
         // Order is important when new and old surfaces are the same, because old surface has
         // its frame stats disabled automatically.
-        mNativeSurface->enableFrameTimestamps(true);
+        native_window_enable_frame_timestamps(mNativeSurface->getNativeWindow(), true);
     } else {
         mRenderThread.removeFrameCallback(this);
         mGenerationID++;
@@ -225,7 +224,8 @@
 }
 
 void CanvasContext::setWideGamut(bool wideGamut) {
-    mWideColorGamut = wideGamut;
+    ColorMode colorMode = wideGamut ? ColorMode::WideColorGamut : ColorMode::SRGB;
+    mRenderPipeline->setSurfaceColorProperties(colorMode);
 }
 
 bool CanvasContext::makeCurrent() {
@@ -429,7 +429,8 @@
 
     if (renderAhead) {
         presentTime = mCurrentFrameInfo->get(FrameInfoIndex::Vsync) +
-                (frameIntervalNanos * (renderAhead + 1));
+                (frameIntervalNanos * (renderAhead + 1)) - DeviceInfo::get()->getAppOffset() +
+                (frameIntervalNanos / 2);
     }
     native_window_set_buffers_timestamp(mNativeSurface->getNativeWindow(), presentTime);
 }
@@ -555,8 +556,9 @@
         FrameInfo* forthBehind = mLast4FrameInfos.front().first;
         int64_t composedFrameId = mLast4FrameInfos.front().second;
         nsecs_t acquireTime = -1;
-        mNativeSurface->getFrameTimestamps(composedFrameId, nullptr, &acquireTime, nullptr, nullptr,
-            nullptr, nullptr, nullptr, nullptr, nullptr);
+        native_window_get_frame_timestamps(mNativeSurface->getNativeWindow(), composedFrameId,
+                                           nullptr, &acquireTime, nullptr, nullptr, nullptr,
+                                           nullptr, nullptr, nullptr, nullptr);
         // Ignore default -1, NATIVE_WINDOW_TIMESTAMP_INVALID and NATIVE_WINDOW_TIMESTAMP_PENDING
         forthBehind->set(FrameInfoIndex::GpuCompleted) = acquireTime > 0 ? acquireTime : -1;
         mJankTracker.finishGpuDraw(*forthBehind);
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 0967b20..629c741 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -251,7 +251,6 @@
     nsecs_t mLastDropVsync = 0;
 
     bool mOpaque;
-    bool mWideColorGamut = false;
     bool mUseForceDark = false;
     LightInfo mLightInfo;
     LightGeometry mLightGeometry = {{0, 0, 0}, 0};
diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h
index ef0aa98..ba0d64c 100644
--- a/libs/hwui/renderthread/IRenderPipeline.h
+++ b/libs/hwui/renderthread/IRenderPipeline.h
@@ -66,7 +66,7 @@
     virtual bool swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty,
                              FrameInfo* currentFrameInfo, bool* requireSwap) = 0;
     virtual DeferredLayerUpdater* createTextureLayer() = 0;
-    virtual bool setSurface(ANativeWindow* window, SwapBehavior swapBehavior, ColorMode colorMode,
+    virtual bool setSurface(ANativeWindow* window, SwapBehavior swapBehavior,
                             uint32_t extraBuffers) = 0;
     virtual void onStop() = 0;
     virtual bool isSurfaceReady() = 0;
@@ -80,6 +80,8 @@
     virtual bool pinImages(std::vector<SkImage*>& mutableImages) = 0;
     virtual bool pinImages(LsaVector<sk_sp<Bitmap>>& images) = 0;
     virtual void unpinImages() = 0;
+
+    virtual void setSurfaceColorProperties(ColorMode colorMode) = 0;
     virtual SkColorType getSurfaceColorType() const = 0;
     virtual sk_sp<SkColorSpace> getSurfaceColorSpace() = 0;
     virtual GrSurfaceOrigin getSurfaceOrigin() = 0;
diff --git a/libs/hwui/renderthread/ReliableSurface.h b/libs/hwui/renderthread/ReliableSurface.h
index da5097c..e3cd8c0 100644
--- a/libs/hwui/renderthread/ReliableSurface.h
+++ b/libs/hwui/renderthread/ReliableSurface.h
@@ -49,21 +49,6 @@
         return ret;
     }
 
-    status_t getFrameTimestamps(uint64_t frameNumber,
-            nsecs_t* outRequestedPresentTime, nsecs_t* outAcquireTime,
-            nsecs_t* outLatchTime, nsecs_t* outFirstRefreshStartTime,
-            nsecs_t* outLastRefreshStartTime, nsecs_t* outGlCompositionDoneTime,
-            nsecs_t* outDisplayPresentTime, nsecs_t* outDequeueReadyTime,
-            nsecs_t* outReleaseTime) {
-        return mSurface->getFrameTimestamps(frameNumber, outRequestedPresentTime, outAcquireTime,
-            outLatchTime, outFirstRefreshStartTime, outLastRefreshStartTime,
-            outGlCompositionDoneTime, outDisplayPresentTime, outDequeueReadyTime, outReleaseTime);
-    }
-
-    void enableFrameTimestamps(bool enable) {
-        return mSurface->enableFrameTimestamps(enable);
-    }
-
 private:
     sp<Surface> mSurface;
 
diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
index 307d136..90bcd1c 100644
--- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp
+++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
@@ -398,7 +398,7 @@
     auto surface = context.surface();
     auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
     EXPECT_FALSE(pipeline->isSurfaceReady());
-    EXPECT_TRUE(pipeline->setSurface(surface.get(), SwapBehavior::kSwap_default, ColorMode::SRGB, 0));
+    EXPECT_TRUE(pipeline->setSurface(surface.get(), SwapBehavior::kSwap_default, 0));
     EXPECT_TRUE(pipeline->isSurfaceReady());
     renderThread.destroyRenderingContext();
     EXPECT_FALSE(pipeline->isSurfaceReady());
diff --git a/libs/incident/Android.bp b/libs/incident/Android.bp
index 150f6dc..512b8c4 100644
--- a/libs/incident/Android.bp
+++ b/libs/incident/Android.bp
@@ -12,8 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-cc_library_shared {
-    name: "libincident",
+
+cc_defaults {
+    name: "libincidentpriv_defaults",
 
     cflags: [
         "-Wall",
@@ -50,6 +51,70 @@
         ":libincident_aidl",
         "src/IncidentReportArgs.cpp",
     ],
+}
+
+cc_library_shared {
+    name: "libincidentpriv",
+    defaults: ["libincidentpriv_defaults"],
+    export_include_dirs: ["include_priv"],
+}
+
+cc_library_shared {
+    name: "libincident",
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wno-missing-field-initializers",
+        "-Wno-unused-variable",
+        "-Wunused-parameter",
+    ],
+
+    shared_libs: [
+        "libbinder",
+        "liblog",
+        "libutils",
+        "libincidentpriv",
+    ],
+
+    srcs: [
+        "src/incident_report.cpp",
+    ],
 
     export_include_dirs: ["include"],
+
+    stubs: {
+        symbol_file: "libincident.map.txt",
+        versions: [
+            "30",
+        ],
+    },
 }
+
+cc_test {
+    name: "libincident_test",
+    defaults: ["libincidentpriv_defaults"],
+    test_suites: ["device-tests"],
+
+    include_dirs: [
+        "frameworks/base/libs/incident/include",
+        "frameworks/base/libs/incident/include_priv",
+    ],
+
+    srcs: [
+        "tests/IncidentReportArgs_test.cpp",
+        "tests/IncidentReportRequest_test.cpp",
+        "tests/c_api_compile_test.c",
+    ],
+
+    shared_libs: [
+        "libincident",
+    ],
+
+    static_libs: [
+        "libgmock",
+    ],
+}
+
+
+
diff --git a/libs/incident/include/incident/incident_report.h b/libs/incident/include/incident/incident_report.h
new file mode 100644
index 0000000..49fe5b9
--- /dev/null
+++ b/libs/incident/include/incident/incident_report.h
@@ -0,0 +1,192 @@
+/**
+ * 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.
+ */
+
+/**
+ * @file incident_report.h
+ */
+
+#ifndef ANDROID_INCIDENT_INCIDENT_REPORT_H
+#define ANDROID_INCIDENT_INCIDENT_REPORT_H
+
+#include <stdbool.h>
+
+#if __cplusplus
+#include <set>
+#include <string>
+#include <vector>
+
+extern "C" {
+#endif // __cplusplus
+
+struct AIncidentReportArgs;
+/**
+ * Opaque class to represent the arguments to an incident report request.
+ * Incident reports contain debugging data about the device at runtime.
+ * For more information see the android.os.IncidentManager java class.
+ */
+typedef struct AIncidentReportArgs AIncidentReportArgs;
+
+// Privacy policy enum value, sync with frameworks/base/core/proto/android/privacy.proto,
+// IncidentReportArgs.h and IncidentReportArgs.java.
+enum {
+    /**
+     * Flag marking fields and incident reports than can be taken
+     * off the device only via adb.
+     */
+    INCIDENT_REPORT_PRIVACY_POLICY_LOCAL = 0,
+
+    /**
+     * Flag marking fields and incident reports than can be taken
+     * off the device with contemporary consent.
+     */
+    INCIDENT_REPORT_PRIVACY_POLICY_EXPLICIT = 100,
+
+    /**
+     * Flag marking fields and incident reports than can be taken
+     * off the device with prior consent.
+     */
+    INCIDENT_REPORT_PRIVACY_POLICY_AUTOMATIC = 200,
+
+    /**
+     * Flag to indicate that a given field has not been marked
+     * with a privacy policy.
+     */
+    INCIDENT_REPORT_PRIVACY_POLICY_UNSET = 255
+};
+
+/**
+ * Allocate and initialize an AIncidentReportArgs object.
+ */
+AIncidentReportArgs* AIncidentReportArgs_init();
+
+/**
+ * Duplicate an existing AIncidentReportArgs object.
+ */
+AIncidentReportArgs* AIncidentReportArgs_clone(AIncidentReportArgs* that);
+
+/**
+ * Clean up and delete an AIncidentReportArgs object.
+ */
+void AIncidentReportArgs_delete(AIncidentReportArgs* args);
+
+/**
+ * Set this incident report to include all sections.
+ */
+void AIncidentReportArgs_setAll(AIncidentReportArgs* args, bool all);
+
+/**
+ * Set this incident report privacy policy spec.
+ */
+void AIncidentReportArgs_setPrivacyPolicy(AIncidentReportArgs* args, int privacyPolicy);
+
+/**
+ * Add this section to the incident report. The section IDs are the field numbers
+ * from the android.os.IncidentProto protobuf message.
+ */
+void AIncidentReportArgs_addSection(AIncidentReportArgs* args, int section);
+
+/**
+ * Set the apk package name that will be sent a broadcast when the incident
+ * report completes.  Must be called in conjunction with AIncidentReportArgs_setReceiverClass.
+ */
+void AIncidentReportArgs_setReceiverPackage(AIncidentReportArgs* args, char const* pkg);
+
+/**
+ * Set the fully qualified class name of the java BroadcastReceiver class that will be
+ * sent a broadcast when the report completes.  Must be called in conjunction with
+ * AIncidentReportArgs_setReceiverPackage.
+ */
+void AIncidentReportArgs_setReceiverClass(AIncidentReportArgs* args, char const* cls);
+
+/**
+ * Add protobuf data as a header to the incident report. The buffer should be a serialized
+ * android.os.IncidentHeaderProto object.
+ */
+void AIncidentReportArgs_addHeader(AIncidentReportArgs* args, uint8_t const* buf, size_t size);
+
+/**
+ * Initiate taking the report described in the args object.  Returns 0 on success,
+ * and non-zero otherwise.
+ */
+int AIncidentReportArgs_takeReport(AIncidentReportArgs* args);
+
+#if __cplusplus
+} // extern "C"
+
+namespace android {
+namespace os {
+
+class IncidentReportRequest {
+public:
+    inline IncidentReportRequest() {
+        mImpl = AIncidentReportArgs_init();
+    }
+
+    inline IncidentReportRequest(const IncidentReportRequest& that) {
+        mImpl = AIncidentReportArgs_clone(that.mImpl);
+    }
+
+    inline ~IncidentReportRequest() {
+        AIncidentReportArgs_delete(mImpl);
+    }
+
+    inline AIncidentReportArgs* getImpl() {
+        return mImpl;
+    }
+
+    inline void setAll(bool all) {
+        AIncidentReportArgs_setAll(mImpl, all);
+    }
+
+    inline void setPrivacyPolicy(int privacyPolicy) {
+        AIncidentReportArgs_setPrivacyPolicy(mImpl, privacyPolicy);
+    }
+
+    inline void addSection(int section) {
+        AIncidentReportArgs_addSection(mImpl, section);
+    }
+
+    inline void setReceiverPackage(const std::string& pkg) {
+        AIncidentReportArgs_setReceiverPackage(mImpl, pkg.c_str());
+    };
+
+    inline void setReceiverClass(const std::string& cls) {
+        AIncidentReportArgs_setReceiverClass(mImpl, cls.c_str());
+    };
+
+    inline void addHeader(const std::vector<uint8_t>& headerProto) {
+        AIncidentReportArgs_addHeader(mImpl, headerProto.data(), headerProto.size());
+    };
+
+    inline void addHeader(const uint8_t* buf, size_t size) {
+        AIncidentReportArgs_addHeader(mImpl, buf, size);
+    };
+
+    // returns a status_t
+    inline int takeReport() {
+        return AIncidentReportArgs_takeReport(mImpl);
+    }
+
+private:
+    AIncidentReportArgs* mImpl;
+};
+
+} // namespace os
+} // namespace android
+
+#endif // __cplusplus
+
+#endif // ANDROID_INCIDENT_INCIDENT_REPORT_H
diff --git a/libs/incident/include/android/os/IncidentReportArgs.h b/libs/incident/include_priv/android/os/IncidentReportArgs.h
similarity index 89%
rename from libs/incident/include/android/os/IncidentReportArgs.h
rename to libs/incident/include_priv/android/os/IncidentReportArgs.h
index 94b4ad6..0e61590 100644
--- a/libs/incident/include/android/os/IncidentReportArgs.h
+++ b/libs/incident/include_priv/android/os/IncidentReportArgs.h
@@ -14,9 +14,10 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_OS_DUMPSTATE_ARGS_H_
-#define ANDROID_OS_DUMPSTATE_ARGS_H_
+#ifndef ANDROID_OS_INCIDENT_REPORT_ARGS_H
+#define ANDROID_OS_INCIDENT_REPORT_ARGS_H
 
+#include <binder/IServiceManager.h>
 #include <binder/Parcel.h>
 #include <binder/Parcelable.h>
 #include <utils/String16.h>
@@ -29,7 +30,8 @@
 
 using namespace std;
 
-// DESTINATION enum value, sync with frameworks/base/core/proto/android/privacy.proto
+// DESTINATION enum value, sync with frameworks/base/core/proto/android/privacy.proto,
+// incident/incident_report.h and IncidentReportArgs.java
 const uint8_t PRIVACY_POLICY_LOCAL = 0;
 const uint8_t PRIVACY_POLICY_EXPLICIT = 100;
 const uint8_t PRIVACY_POLICY_AUTOMATIC = 200;
@@ -74,4 +76,4 @@
 }
 }
 
-#endif // ANDROID_OS_DUMPSTATE_ARGS_H_
+#endif // ANDROID_OS_INCIDENT_REPORT_ARGS_H
diff --git a/libs/incident/libincident.map.txt b/libs/incident/libincident.map.txt
new file mode 100644
index 0000000..f157763
--- /dev/null
+++ b/libs/incident/libincident.map.txt
@@ -0,0 +1,15 @@
+LIBINCIDENT {
+    global:
+        AIncidentReportArgs_init; # apex # introduced=30
+        AIncidentReportArgs_clone; # apex # introduced=30
+        AIncidentReportArgs_delete; # apex # introduced=30
+        AIncidentReportArgs_setAll; # apex # introduced=30
+        AIncidentReportArgs_setPrivacyPolicy; # apex # introduced=30
+        AIncidentReportArgs_addSection; # apex # introduced=30
+        AIncidentReportArgs_setReceiverPackage; # apex # introduced=30
+        AIncidentReportArgs_setReceiverClass; # apex # introduced=30
+        AIncidentReportArgs_addHeader; # apex # introduced=30
+        AIncidentReportArgs_takeReport; # apex # introduced=30
+    local:
+        *;
+};
diff --git a/libs/incident/src/incident_report.cpp b/libs/incident/src/incident_report.cpp
new file mode 100644
index 0000000..7897ddf
--- /dev/null
+++ b/libs/incident/src/incident_report.cpp
@@ -0,0 +1,83 @@
+/**
+ * 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 "libincident"
+
+#include <incident/incident_report.h>
+
+#include <android/os/IIncidentManager.h>
+#include <android/os/IncidentReportArgs.h>
+#include <binder/IServiceManager.h>
+#include <binder/Status.h>
+#include <log/log.h>
+
+using android::sp;
+using android::binder::Status;
+using android::os::IncidentReportArgs;
+using android::os::IIncidentManager;
+using std::string;
+using std::vector;
+
+AIncidentReportArgs* AIncidentReportArgs_init() {
+    return reinterpret_cast<AIncidentReportArgs*>(new IncidentReportArgs());
+}
+
+AIncidentReportArgs* AIncidentReportArgs_clone(AIncidentReportArgs* that) {
+    return reinterpret_cast<AIncidentReportArgs*>(
+            new IncidentReportArgs(*reinterpret_cast<IncidentReportArgs*>(that)));
+}
+
+void AIncidentReportArgs_delete(AIncidentReportArgs* args) {
+    delete reinterpret_cast<IncidentReportArgs*>(args);
+}
+
+void AIncidentReportArgs_setAll(AIncidentReportArgs* args, bool all) {
+    reinterpret_cast<IncidentReportArgs*>(args)->setAll(all);
+}
+
+void AIncidentReportArgs_setPrivacyPolicy(AIncidentReportArgs* args, int privacyPolicy) {
+    reinterpret_cast<IncidentReportArgs*>(args)->setPrivacyPolicy(privacyPolicy);
+}
+
+void AIncidentReportArgs_addSection(AIncidentReportArgs* args, int section) {
+    reinterpret_cast<IncidentReportArgs*>(args)->addSection(section);
+}
+
+void AIncidentReportArgs_setReceiverPackage(AIncidentReportArgs* args, char const* pkg) {
+    reinterpret_cast<IncidentReportArgs*>(args)->setReceiverPkg(string(pkg));
+}
+
+void AIncidentReportArgs_setReceiverClass(AIncidentReportArgs* args, char const* cls) {
+    reinterpret_cast<IncidentReportArgs*>(args)->setReceiverCls(string(cls));
+}
+
+void AIncidentReportArgs_addHeader(AIncidentReportArgs* args, uint8_t const* buf, size_t size) {
+    vector<uint8_t> vec(buf, buf+size);
+    reinterpret_cast<IncidentReportArgs*>(args)->addHeader(vec);
+}
+
+int AIncidentReportArgs_takeReport(AIncidentReportArgs* argp) {
+    IncidentReportArgs* args = reinterpret_cast<IncidentReportArgs*>(argp);
+
+    sp<IIncidentManager> service = android::interface_cast<IIncidentManager>(
+            android::defaultServiceManager()->getService(android::String16("incident")));
+    if (service == nullptr) {
+        ALOGW("Failed to fetch incident service.");
+        return false;
+    }
+    Status s = service->reportIncident(*args);
+    return s.transactionError();
+}
diff --git a/cmds/statsd/tests/external/IncidentReportArgs_test.cpp b/libs/incident/tests/IncidentReportArgs_test.cpp
similarity index 93%
rename from cmds/statsd/tests/external/IncidentReportArgs_test.cpp
rename to libs/incident/tests/IncidentReportArgs_test.cpp
index 38bc194..224b343 100644
--- a/cmds/statsd/tests/external/IncidentReportArgs_test.cpp
+++ b/libs/incident/tests/IncidentReportArgs_test.cpp
@@ -20,6 +20,8 @@
 namespace os {
 namespace statsd {
 
+// Checks that all of the inline methods on IncidentReportRequest and the real C functions
+// result in a working IncidentReportArgs.
 TEST(IncidentReportArgsTest, testSerialization) {
     IncidentReportArgs args;
     args.setAll(0);
diff --git a/libs/incident/tests/IncidentReportRequest_test.cpp b/libs/incident/tests/IncidentReportRequest_test.cpp
new file mode 100644
index 0000000..6d218b6
--- /dev/null
+++ b/libs/incident/tests/IncidentReportRequest_test.cpp
@@ -0,0 +1,65 @@
+// Copyright (C) 2018 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.
+
+#include <android/os/IncidentReportArgs.h>
+#include <incident/incident_report.h>
+
+#include <gtest/gtest.h>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+TEST(IncidentReportRequestTest, testWrite) {
+    IncidentReportRequest request;
+    request.setAll(0);
+    request.addSection(1000);
+    request.addSection(1001);
+
+    vector<uint8_t> header1;
+    header1.push_back(0x1);
+    header1.push_back(0x2);
+    vector<uint8_t> header2;
+    header1.push_back(0x22);
+    header1.push_back(0x33);
+
+    request.addHeader(header1);
+    request.addHeader(header2);
+
+    request.setPrivacyPolicy(1);
+
+    request.setReceiverPackage("com.android.os");
+    request.setReceiverClass("com.android.os.Receiver");
+
+    IncidentReportArgs* args = reinterpret_cast<IncidentReportArgs*>(request.getImpl());
+
+    EXPECT_EQ(0, args->all());
+    set<int> sections;
+    sections.insert(1000);
+    sections.insert(1001);
+    EXPECT_EQ(sections, args->sections());
+    EXPECT_EQ(1, args->getPrivacyPolicy());
+
+    EXPECT_EQ(string("com.android.os"), args->receiverPkg());
+    EXPECT_EQ(string("com.android.os.Receiver"), args->receiverCls());
+
+    vector<vector<uint8_t>> headers;
+    headers.push_back(header1);
+    headers.push_back(header2);
+    EXPECT_EQ(headers, args->headers());
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/libs/incident/tests/c_api_compile_test.c b/libs/incident/tests/c_api_compile_test.c
new file mode 100644
index 0000000..e1620df
--- /dev/null
+++ b/libs/incident/tests/c_api_compile_test.c
@@ -0,0 +1,11 @@
+#include <stdio.h>
+#include <incident/incident_report.h>
+
+/*
+ * This file ensures that incident/incident_report.h actually compiles with C,
+ * since there is no other place in the tree that actually uses it from C.
+ */
+int not_called() {
+    return 0;
+}
+
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 197787e..1c10edb 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -33,6 +33,9 @@
 import android.annotation.TestApi;
 import android.app.AlarmManager;
 import android.app.PendingIntent;
+import android.compat.Compatibility;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -50,7 +53,6 @@
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.ArrayMap;
-import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.location.ProviderProperties;
@@ -82,6 +84,36 @@
     private static final String TAG = "LocationManager";
 
     /**
+     * For apps targeting Android K and above, supplied {@link PendingIntent}s must be targeted to a
+     * specific package.
+     *
+     * @hide
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.JELLY_BEAN)
+    public static final long TARGETED_PENDING_INTENT = 148963590L;
+
+    /**
+     * For apps targeting Android K and above, incomplete locations may not be passed to
+     * {@link #setTestProviderLocation}.
+     *
+     * @hide
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.JELLY_BEAN)
+    private static final long INCOMPLETE_LOCATION = 148964793L;
+
+    /**
+     * For apps targeting Android S and above, all {@link GpsStatus} API usage must be replaced with
+     * {@link GnssStatus} APIs.
+     *
+     * @hide
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R)
+    private static final long GPS_STATUS_USAGE = 144027538L;
+
+    /**
      * Name of the network location provider.
      *
      * <p>This provider determines location based on nearby of cell tower and WiFi access points.
@@ -771,7 +803,6 @@
     public void requestSingleUpdate(@NonNull String provider,
             @NonNull PendingIntent pendingIntent) {
         Preconditions.checkArgument(provider != null, "invalid null provider");
-        checkPendingIntent(pendingIntent);
 
         LocationRequest request = LocationRequest.createFromDeprecatedProvider(
                 provider, 0, 0, true);
@@ -800,7 +831,6 @@
     public void requestSingleUpdate(@NonNull Criteria criteria,
             @NonNull PendingIntent pendingIntent) {
         Preconditions.checkArgument(criteria != null, "invalid null criteria");
-        checkPendingIntent(pendingIntent);
 
         LocationRequest request = LocationRequest.createFromDeprecatedCriteria(
                 criteria, 0, 0, true);
@@ -1021,7 +1051,6 @@
     public void requestLocationUpdates(@NonNull String provider, long minTimeMs, float minDistanceM,
             @NonNull PendingIntent pendingIntent) {
         Preconditions.checkArgument(provider != null, "invalid null provider");
-        checkPendingIntent(pendingIntent);
 
         LocationRequest request = LocationRequest.createFromDeprecatedProvider(
                 provider, minTimeMs, minDistanceM, false);
@@ -1048,7 +1077,6 @@
     public void requestLocationUpdates(long minTimeMs, float minDistanceM,
             @NonNull Criteria criteria, @NonNull PendingIntent pendingIntent) {
         Preconditions.checkArgument(criteria != null, "invalid null criteria");
-        checkPendingIntent(pendingIntent);
 
         LocationRequest request = LocationRequest.createFromDeprecatedCriteria(
                 criteria, minTimeMs, minDistanceM, false);
@@ -1164,9 +1192,9 @@
             @NonNull PendingIntent pendingIntent) {
         Preconditions.checkArgument(locationRequest != null, "invalid null location request");
         Preconditions.checkArgument(pendingIntent != null, "invalid null pending intent");
-        if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.JELLY_BEAN) {
+        if (Compatibility.isChangeEnabled(TARGETED_PENDING_INTENT)) {
             Preconditions.checkArgument(pendingIntent.isTargetedToPackage(),
-                    "pending intent must be targeted to package");
+                    "pending intent must be targeted to a package");
         }
 
         try {
@@ -1198,15 +1226,9 @@
      */
     @RequiresPermission(allOf = {LOCATION_HARDWARE, ACCESS_FINE_LOCATION})
     public boolean injectLocation(@NonNull Location location) {
-        if (location == null) {
-            IllegalArgumentException e = new IllegalArgumentException("invalid null location");
-            if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R) {
-                throw e;
-            } else {
-                Log.w(TAG, e);
-                return false;
-            }
-        }
+        Preconditions.checkArgument(location != null, "invalid null location");
+        Preconditions.checkArgument(location.isComplete(),
+                "incomplete location object, missing timestamp or accuracy?");
 
         try {
             return mService.injectLocation(location);
@@ -1487,15 +1509,11 @@
         Preconditions.checkArgument(provider != null, "invalid null provider");
         Preconditions.checkArgument(location != null, "invalid null location");
 
-        if (!location.isComplete()) {
-            IllegalArgumentException e = new IllegalArgumentException(
-                    "Incomplete location object, missing timestamp or accuracy? " + location);
-            if (mContext.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN) {
-                Log.w(TAG, e);
-                location.makeComplete();
-            } else {
-                throw e;
-            }
+        if (Compatibility.isChangeEnabled(INCOMPLETE_LOCATION)) {
+            Preconditions.checkArgument(location.isComplete(),
+                    "incomplete location object, missing timestamp or accuracy?");
+        } else {
+            location.makeComplete();
         }
 
         try {
@@ -1629,7 +1647,11 @@
     @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
     public void addProximityAlert(double latitude, double longitude, float radius, long expiration,
             @NonNull PendingIntent intent) {
-        checkPendingIntent(intent);
+        Preconditions.checkArgument(intent != null, "invalid null pending intent");
+        if (Compatibility.isChangeEnabled(TARGETED_PENDING_INTENT)) {
+            Preconditions.checkArgument(intent.isTargetedToPackage(),
+                    "pending intent must be targeted to a package");
+        }
         if (expiration < 0) expiration = Long.MAX_VALUE;
 
         Geofence fence = Geofence.createCircle(latitude, longitude, radius);
@@ -1659,7 +1681,11 @@
      * permission is not present
      */
     public void removeProximityAlert(@NonNull PendingIntent intent) {
-        checkPendingIntent(intent);
+        Preconditions.checkArgument(intent != null, "invalid null pending intent");
+        if (Compatibility.isChangeEnabled(TARGETED_PENDING_INTENT)) {
+            Preconditions.checkArgument(intent.isTargetedToPackage(),
+                    "pending intent must be targeted to a package");
+        }
 
         try {
             mService.removeGeofence(null, intent, mContext.getPackageName());
@@ -1709,8 +1735,13 @@
             @NonNull LocationRequest request,
             @NonNull Geofence fence,
             @NonNull PendingIntent intent) {
-        checkPendingIntent(intent);
+        Preconditions.checkArgument(request != null, "invalid null location request");
         Preconditions.checkArgument(fence != null, "invalid null geofence");
+        Preconditions.checkArgument(intent != null, "invalid null pending intent");
+        if (Compatibility.isChangeEnabled(TARGETED_PENDING_INTENT)) {
+            Preconditions.checkArgument(intent.isTargetedToPackage(),
+                    "pending intent must be targeted to a package");
+        }
 
         try {
             mService.requestGeofence(request, fence, intent, mContext.getPackageName(),
@@ -1737,8 +1768,12 @@
      * @hide
      */
     public void removeGeofence(@NonNull Geofence fence, @NonNull PendingIntent intent) {
-        checkPendingIntent(intent);
         Preconditions.checkArgument(fence != null, "invalid null geofence");
+        Preconditions.checkArgument(intent != null, "invalid null pending intent");
+        if (Compatibility.isChangeEnabled(TARGETED_PENDING_INTENT)) {
+            Preconditions.checkArgument(intent.isTargetedToPackage(),
+                    "pending intent must be targeted to a package");
+        }
 
         try {
             mService.removeGeofence(fence, intent, mContext.getPackageName());
@@ -1759,7 +1794,11 @@
      * @hide
      */
     public void removeAllGeofences(@NonNull PendingIntent intent) {
-        checkPendingIntent(intent);
+        Preconditions.checkArgument(intent != null, "invalid null pending intent");
+        if (Compatibility.isChangeEnabled(TARGETED_PENDING_INTENT)) {
+            Preconditions.checkArgument(intent.isTargetedToPackage(),
+                    "pending intent must be targeted to a package");
+        }
 
         try {
             mService.removeGeofence(null, intent, mContext.getPackageName());
@@ -1833,14 +1872,15 @@
      * @param status object containing GPS status details, or null.
      * @return status object containing updated GPS status.
      *
-     * @deprecated GpsStatus APIs are deprecated, use {@link GnssStatus} APIs instead.
+     * @deprecated GpsStatus APIs are deprecated, use {@link GnssStatus} APIs instead. No longer
+     * supported in apps targeting S and above.
      */
     @Deprecated
     @RequiresPermission(ACCESS_FINE_LOCATION)
     public @Nullable GpsStatus getGpsStatus(@Nullable GpsStatus status) {
-        if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.R) {
+        if (Compatibility.isChangeEnabled(GPS_STATUS_USAGE)) {
             throw new UnsupportedOperationException(
-                    "GpsStatus APIs not supported in S and above, use GnssStatus APIs instead");
+                    "GpsStatus APIs not supported, please use GnssStatus APIs instead");
         }
 
         GnssStatus gnssStatus = mGnssStatusListenerManager.getGnssStatus();
@@ -1863,17 +1903,14 @@
      * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present
      *
      * @deprecated use {@link #registerGnssStatusCallback(GnssStatus.Callback)} instead. No longer
-     * supported in apps targeting R and above.
+     * supported in apps targeting S and above.
      */
     @Deprecated
     @RequiresPermission(ACCESS_FINE_LOCATION)
     public boolean addGpsStatusListener(GpsStatus.Listener listener) {
-        UnsupportedOperationException ex = new UnsupportedOperationException(
-                "GpsStatus APIs not supported in S and above, use GnssStatus APIs instead");
-        if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.R) {
-            throw ex;
-        } else {
-            Log.w(TAG, ex);
+        if (Compatibility.isChangeEnabled(GPS_STATUS_USAGE)) {
+            throw new UnsupportedOperationException(
+                    "GpsStatus APIs not supported, please use GnssStatus APIs instead");
         }
 
         try {
@@ -1889,16 +1926,13 @@
      * @param listener GPS status listener object to remove
      *
      * @deprecated use {@link #unregisterGnssStatusCallback(GnssStatus.Callback)} instead. No longer
-     * supported in apps targeting R and above.
+     * supported in apps targeting S and above.
      */
     @Deprecated
     public void removeGpsStatusListener(GpsStatus.Listener listener) {
-        UnsupportedOperationException ex = new UnsupportedOperationException(
-                "GpsStatus APIs not supported in S and above, use GnssStatus APIs instead");
-        if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.R) {
-            throw ex;
-        } else {
-            Log.w(TAG, ex);
+        if (Compatibility.isChangeEnabled(GPS_STATUS_USAGE)) {
+            throw new UnsupportedOperationException(
+                    "GpsStatus APIs not supported, please use GnssStatus APIs instead");
         }
 
         try {
@@ -2397,19 +2431,6 @@
         }
     }
 
-    private void checkPendingIntent(PendingIntent pendingIntent) {
-        Preconditions.checkArgument(pendingIntent != null, "invalid null pending intent");
-        if (!pendingIntent.isTargetedToPackage()) {
-            IllegalArgumentException e = new IllegalArgumentException(
-                    "invalid pending intent - must be targeted to package");
-            if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.JELLY_BEAN) {
-                throw e;
-            } else {
-                Log.w(TAG, e);
-            }
-        }
-    }
-
     private static class GetCurrentLocationTransport extends ILocationListener.Stub implements
             AlarmManager.OnAlarmListener {
 
diff --git a/media/java/android/media/AudioRecordingConfiguration.java b/media/java/android/media/AudioRecordingConfiguration.java
index f3613d3..42841d1 100644
--- a/media/java/android/media/AudioRecordingConfiguration.java
+++ b/media/java/android/media/AudioRecordingConfiguration.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -229,13 +230,19 @@
      * <p>This information is only available if the caller has the
      * {@link android.Manifest.permission.MODIFY_AUDIO_ROUTING}
      * permission.
-     * <br>The result is -1 without the permission.
      * @return the user id
+     * @throws SecurityException Thrown if the caller is missing the MODIFY_AUDIO_ROUTING permission
      *
      * @hide
      */
     @SystemApi
-    public int getClientUid() { return mClientUid; }
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public int getClientUid() {
+        if (mClientUid == -1) {
+            throw new SecurityException("MODIFY_AUDIO_ROUTING permission is missing");
+        }
+        return mClientUid;
+    }
 
     /**
      * Returns information about the audio input device used for this recording.
diff --git a/media/java/android/media/IMediaRoute2Provider.aidl b/media/java/android/media/IMediaRoute2Provider.aidl
index a25aff6..9131f3b 100644
--- a/media/java/android/media/IMediaRoute2Provider.aidl
+++ b/media/java/android/media/IMediaRoute2Provider.aidl
@@ -18,6 +18,7 @@
 
 import android.content.Intent;
 import android.media.IMediaRoute2ProviderClient;
+import android.media.RouteDiscoveryPreference;
 import android.os.Bundle;
 
 /**
@@ -28,6 +29,7 @@
     void requestCreateSession(String packageName, String routeId, long requestId,
             in @nullable Bundle sessionHints);
     void releaseSession(String sessionId);
+    void updateDiscoveryPreference(in RouteDiscoveryPreference discoveryPreference);
 
     void selectRoute(String sessionId, String routeId);
     void deselectRoute(String sessionId, String routeId);
@@ -35,5 +37,4 @@
 
     void notifyControlRequestSent(String id, in Intent request);
     void requestSetVolume(String id, int volume);
-    void requestUpdateVolume(String id, int delta);
 }
diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl
index dac0fba..6fef468 100644
--- a/media/java/android/media/IMediaRouterService.aidl
+++ b/media/java/android/media/IMediaRouterService.aidl
@@ -52,7 +52,6 @@
     void sendControlRequest(IMediaRouter2Client client, in MediaRoute2Info route,
             in Intent request);
     void requestSetVolume2(IMediaRouter2Client client, in MediaRoute2Info route, int volume);
-    void requestUpdateVolume2(IMediaRouter2Client client, in MediaRoute2Info route, int direction);
 
     void requestCreateSession(IMediaRouter2Client client, in MediaRoute2Info route, int requestId,
             in @nullable Bundle sessionHints);
@@ -70,8 +69,6 @@
 
     void requestSetVolume2Manager(IMediaRouter2Manager manager,
             in MediaRoute2Info route, int volume);
-    void requestUpdateVolume2Manager(IMediaRouter2Manager manager,
-            in MediaRoute2Info route, int direction);
 
     List<RoutingSessionInfo> getActiveSessions(IMediaRouter2Manager manager);
     void selectClientRoute(IMediaRouter2Manager manager,
diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java
index e39b7bc..20a59bba5 100644
--- a/media/java/android/media/MediaRoute2ProviderService.java
+++ b/media/java/android/media/MediaRoute2ProviderService.java
@@ -46,13 +46,20 @@
 /**
  * Base class for media route provider services.
  * <p>
+ * Media route provider services are used to publish {@link MediaRoute2Info media routes} such as
+ * speakers, TVs, etc. The routes are published by calling {@link #notifyRoutes(Collection)}.
+ * Media apps which use {@link MediaRouter2} can request to play their media on the routes.
+ * </p><p>
+ * When {@link MediaRouter2 media router} wants to play media on a route,
+ * {@link #onCreateSession(String, String, long, Bundle)} will be called to handle the request.
+ * A session can be considered as a group of currently selected routes for each connection.
+ * Create and manage the sessions by yourself, and notify the {@link RoutingSessionInfo
+ * session infos} when there are any changes.
+ * </p><p>
  * The system media router service will bind to media route provider services when a
  * {@link RouteDiscoveryPreference discovery preference} is registered via
- * a {@link MediaRouter2 media router} by an application.
- * </p><p>
- * To implement your own media route provider service, extend this class and
- * override {@link #onDiscoveryPreferenceChanged(RouteDiscoveryPreference)} to publish
- * {@link MediaRoute2Info routes}.
+ * a {@link MediaRouter2 media router} by an application. See
+ * {@link #onDiscoveryPreferenceChanged(RouteDiscoveryPreference)} for the details.
  * </p>
  */
 public abstract class MediaRoute2ProviderService extends Service {
@@ -118,22 +125,15 @@
     public abstract void onControlRequest(@NonNull String routeId, @NonNull Intent request);
 
     /**
-     * Called when requestSetVolume is called on a route of the provider
+     * Called when requestSetVolume is called on a route of the provider.
      *
      * @param routeId the id of the route
      * @param volume the target volume
+     * @see MediaRoute2Info#getVolumeMax()
      */
     public abstract void onSetVolume(@NonNull String routeId, int volume);
 
     /**
-     * Called when requestUpdateVolume is called on a route of the provider
-     *
-     * @param routeId id of the route
-     * @param delta the delta to add to the current volume
-     */
-    public abstract void onUpdateVolume(@NonNull String routeId, int delta);
-
-    /**
      * Gets information of the session with the given id.
      *
      * @param sessionId id of the session
@@ -370,7 +370,6 @@
      *
      * @param preference the new discovery preference
      */
-    // TODO: This method needs tests.
     public void onDiscoveryPreferenceChanged(@NonNull RouteDiscoveryPreference preference) {}
 
     /**
@@ -456,6 +455,16 @@
         }
 
         @Override
+        public void updateDiscoveryPreference(RouteDiscoveryPreference discoveryPreference) {
+            if (!checkCallerisSystem()) {
+                return;
+            }
+            mHandler.sendMessage(obtainMessage(
+                    MediaRoute2ProviderService::onDiscoveryPreferenceChanged,
+                    MediaRoute2ProviderService.this, discoveryPreference));
+        }
+
+        @Override
         public void selectRoute(@NonNull String sessionId, String routeId) {
             if (!checkCallerisSystem()) {
                 return;
@@ -511,14 +520,5 @@
             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSetVolume,
                     MediaRoute2ProviderService.this, routeId, volume));
         }
-
-        @Override
-        public void requestUpdateVolume(String routeId, int delta) {
-            if (!checkCallerisSystem()) {
-                return;
-            }
-            mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onUpdateVolume,
-                    MediaRoute2ProviderService.this, routeId, delta));
-        }
     }
 }
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index f751a22..d7b74df 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -182,11 +182,16 @@
                 Client2 client = new Client2();
                 try {
                     mMediaRouterService.registerClient2(client, mPackageName);
-                    updateDiscoveryRequestLocked();
-                    mMediaRouterService.setDiscoveryRequest2(client, mDiscoveryPreference);
                     mClient = client;
                 } catch (RemoteException ex) {
-                    Log.e(TAG, "Unable to register media router.", ex);
+                    Log.e(TAG, "registerRouteCallback: Unable to register client.", ex);
+                }
+            }
+            if (mClient != null && updateDiscoveryPreferenceIfNeededLocked()) {
+                try {
+                    mMediaRouterService.setDiscoveryRequest2(mClient, mDiscoveryPreference);
+                } catch (RemoteException ex) {
+                    Log.e(TAG, "registerRouteCallback: Unable to set discovery request.");
                 }
             }
         }
@@ -209,22 +214,37 @@
         }
 
         synchronized (sRouterLock) {
-            if (mRouteCallbackRecords.size() == 0 && mClient != null) {
-                try {
-                    mMediaRouterService.unregisterClient2(mClient);
-                } catch (RemoteException ex) {
-                    Log.e(TAG, "Unable to unregister media router.", ex);
+            if (mClient != null) {
+                if (updateDiscoveryPreferenceIfNeededLocked()) {
+                    try {
+                        mMediaRouterService.setDiscoveryRequest2(mClient, mDiscoveryPreference);
+                    } catch (RemoteException ex) {
+                        Log.e(TAG, "unregisterRouteCallback: Unable to set discovery request.");
+                    }
                 }
-                //TODO: Clean up mRoutes. (onHandler?)
+                if (mRouteCallbackRecords.size() == 0) {
+                    try {
+                        mMediaRouterService.unregisterClient2(mClient);
+                    } catch (RemoteException ex) {
+                        Log.e(TAG, "Unable to unregister media router.", ex);
+                    }
+                }
+                mShouldUpdateRoutes = true;
                 mClient = null;
             }
         }
     }
 
-    private void updateDiscoveryRequestLocked() {
-        mDiscoveryPreference = new RouteDiscoveryPreference.Builder(
+    private boolean updateDiscoveryPreferenceIfNeededLocked() {
+        RouteDiscoveryPreference newDiscoveryPreference = new RouteDiscoveryPreference.Builder(
                 mRouteCallbackRecords.stream().map(record -> record.mPreference).collect(
                         Collectors.toList())).build();
+        if (Objects.equals(mDiscoveryPreference, newDiscoveryPreference)) {
+            return false;
+        }
+        mDiscoveryPreference = newDiscoveryPreference;
+        mShouldUpdateRoutes = true;
+        return true;
     }
 
     /**
@@ -442,31 +462,6 @@
         }
     }
 
-    /**
-     * Requests an incremental volume update  for the route asynchronously.
-     * <p>
-     * It may have no effect if the route is currently not selected.
-     * </p>
-     *
-     * @param delta The delta to add to the current volume.
-     * @hide
-     */
-    public void requestUpdateVolume(@NonNull MediaRoute2Info route, int delta) {
-        Objects.requireNonNull(route, "route must not be null");
-
-        Client2 client;
-        synchronized (sRouterLock) {
-            client = mClient;
-        }
-        if (client != null) {
-            try {
-                mMediaRouterService.requestUpdateVolume2(client, route, delta);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Unable to send control request.", ex);
-            }
-        }
-    }
-
     void addRoutesOnHandler(List<MediaRoute2Info> routes) {
         // TODO: When onRoutesAdded is first called,
         //  1) clear mRoutes before adding the routes
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 662eeb1..b1f930d 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -294,30 +294,6 @@
         }
     }
 
-    /**
-     * Requests an incremental volume update  for the route asynchronously.
-     * <p>
-     * It may have no effect if the route is currently not selected.
-     * </p>
-     *
-     * @param delta The delta to add to the current volume.
-     */
-    public void requestUpdateVolume(@NonNull MediaRoute2Info route, int delta) {
-        Objects.requireNonNull(route, "route must not be null");
-
-        Client client;
-        synchronized (sLock) {
-            client = mClient;
-        }
-        if (client != null) {
-            try {
-                mMediaRouterService.requestUpdateVolume2Manager(client, route, delta);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Unable to send control request.", ex);
-            }
-        }
-    }
-
     void addRoutesOnHandler(List<MediaRoute2Info> routes) {
         synchronized (mRoutesLock) {
             for (MediaRoute2Info route : routes) {
diff --git a/media/java/android/media/RouteDiscoveryPreference.java b/media/java/android/media/RouteDiscoveryPreference.java
index 7ec1123..ebcb9ed 100644
--- a/media/java/android/media/RouteDiscoveryPreference.java
+++ b/media/java/android/media/RouteDiscoveryPreference.java
@@ -31,7 +31,7 @@
 import java.util.Set;
 
 /**
- * A media route discovery preference  describing the kinds of routes that media router
+ * A media route discovery preference describing the kinds of routes that media router
  * would like to discover and whether to perform active scanning.
  *
  * @see MediaRouter2#registerRouteCallback
@@ -58,6 +58,7 @@
     private final Bundle mExtras;
 
     /**
+     * An empty discovery preference.
      * @hide
      */
     public static final RouteDiscoveryPreference EMPTY =
diff --git a/media/java/android/media/soundtrigger/SoundTriggerDetector.java b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
index 118f65c..0895339 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerDetector.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
@@ -113,7 +113,7 @@
      * This capability may or may not be supported by the system, and support can be queried
      * by calling {@link SoundTriggerManager#getModuleProperties()} and checking
      * {@link ModuleProperties#audioCapabilities}. The corresponding capabilities field for
-     * this flag is {@link SoundTrigger.ModuleProperties#CAPABILITY_ECHO_CANCELLATION}.
+     * this flag is {@link SoundTrigger.ModuleProperties#AUDIO_CAPABILITY_ECHO_CANCELLATION}.
      * If this flag is passed without the audio capability supported, there will be no audio effect
      * applied.
      */
@@ -125,8 +125,9 @@
      * This capability may or may not be supported by the system, and support can be queried
      * by calling {@link SoundTriggerManager#getModuleProperties()} and checking
      * {@link ModuleProperties#audioCapabilities}. The corresponding capabilities field for
-     * this flag is {@link SoundTrigger.ModuleProperties#CAPABILITY_NOISE_SUPPRESSION}. If this flag
-     * is passed without the audio capability supported, there will be no audio effect applied.
+     * this flag is {@link SoundTrigger.ModuleProperties#AUDIO_CAPABILITY_NOISE_SUPPRESSION}.
+     * If this flag is passed without the audio capability supported, there will be no audio effect
+     * applied.
      */
     public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 0x8;
 
@@ -296,10 +297,10 @@
 
         int audioCapabilities = 0;
         if ((recognitionFlags & RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION) != 0) {
-            audioCapabilities |= SoundTrigger.ModuleProperties.CAPABILITY_ECHO_CANCELLATION;
+            audioCapabilities |= SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_ECHO_CANCELLATION;
         }
         if ((recognitionFlags & RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION) != 0) {
-            audioCapabilities |= SoundTrigger.ModuleProperties.CAPABILITY_NOISE_SUPPRESSION;
+            audioCapabilities |= SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_NOISE_SUPPRESSION;
         }
 
         int status;
diff --git a/media/java/android/media/soundtrigger/SoundTriggerManager.java b/media/java/android/media/soundtrigger/SoundTriggerManager.java
index dd4dac2..6a8483c 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerManager.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerManager.java
@@ -173,8 +173,13 @@
         }
 
         /**
-         * Factory constructor to create a SoundModel instance for use with methods in this
-         * class.
+         * Factory constructor to a voice model to be used with {@link SoundTriggerManager}
+         *
+         * @param modelUuid Unique identifier associated with the model.
+         * @param vendorUuid Unique identifier associated the calling vendor.
+         * @param data Model's data.
+         * @param version Version identifier for the model.
+         * @return Voice model
          */
         @NonNull
         public static Model create(@NonNull UUID modelUuid, @NonNull UUID vendorUuid,
@@ -186,8 +191,12 @@
         }
 
         /**
-         * Factory constructor to create a SoundModel instance for use with methods in this
-         * class.
+         * Factory constructor to a voice model to be used with {@link SoundTriggerManager}
+         *
+         * @param modelUuid Unique identifier associated with the model.
+         * @param vendorUuid Unique identifier associated the calling vendor.
+         * @param data Model's data.
+         * @return Voice model
          */
         @NonNull
         public static Model create(@NonNull UUID modelUuid, @NonNull UUID vendorUuid,
@@ -195,20 +204,40 @@
             return create(modelUuid, vendorUuid, data, -1);
         }
 
+        /**
+         * Get the model's unique identifier
+         *
+         * @return UUID associated with the model
+         */
         @NonNull
         public UUID getModelUuid() {
             return mGenericSoundModel.uuid;
         }
 
+        /**
+         * Get the model's vendor identifier
+         *
+         * @return UUID associated with the vendor of the model
+         */
         @NonNull
         public UUID getVendorUuid() {
             return mGenericSoundModel.vendorUuid;
         }
 
+        /**
+         * Get the model's version
+         *
+         * @return Version associated with the model
+         */
         public int getVersion() {
             return mGenericSoundModel.version;
         }
 
+        /**
+         * Get the underlying model data
+         *
+         * @return Backing data of the model
+         */
         @Nullable
         public byte[] getModelData() {
             return mGenericSoundModel.data;
diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java
index 09b7559..433c622 100644
--- a/media/java/android/media/tv/TvContract.java
+++ b/media/java/android/media/tv/TvContract.java
@@ -1109,6 +1109,24 @@
          * <p>Type: TEXT
          */
         String COLUMN_SERIES_ID = "series_id";
+
+        /**
+         * The split ID of this TV program for multi-part content, as a URI.
+         *
+         * <p>A content may consist of multiple programs within the same channel or over several
+         * channels. For example, a film might be divided into two parts interrupted by a news in
+         * the middle or a longer sport event might be split into several parts over several
+         * channels. The split ID is used to identify all the programs in the same multi-part
+         * content. Suitable URIs include
+         * <ul>
+         * <li>{@code crid://<CRIDauthority>/<data>#<IMI>} from ETSI TS 102 323
+         * </ul>
+         *
+         * <p>Can be empty.
+         *
+         * <p>Type: TEXT
+         */
+        String COLUMN_SPLIT_ID = "split_id";
     }
 
     /**
@@ -1677,6 +1695,7 @@
                 TYPE_ATSC_T,
                 TYPE_ATSC_C,
                 TYPE_ATSC_M_H,
+                TYPE_ATSC3_T,
                 TYPE_ISDB_T,
                 TYPE_ISDB_TB,
                 TYPE_ISDB_S,
@@ -1801,6 +1820,13 @@
         public static final String TYPE_ATSC_M_H = "TYPE_ATSC_M_H";
 
         /**
+         * The channel type for ATSC3.0 (terrestrial).
+         *
+         * @see #COLUMN_TYPE
+         */
+        public static final String TYPE_ATSC3_T = "TYPE_ATSC3_T";
+
+        /**
          * The channel type for ISDB-T (terrestrial).
          *
          * @see #COLUMN_TYPE
@@ -2022,6 +2048,7 @@
          * {@link #TYPE_ATSC_C},
          * {@link #TYPE_ATSC_M_H},
          * {@link #TYPE_ATSC_T},
+         * {@link #TYPE_ATSC3_T},
          * {@link #TYPE_CMMB},
          * {@link #TYPE_DTMB},
          * {@link #TYPE_DVB_C},
@@ -2407,6 +2434,22 @@
          */
         public static final String COLUMN_TRANSIENT = "transient";
 
+        /**
+         * The global content ID of this TV channel, as a URI.
+         *
+         * <p>A globally unique URI that identifies this TV channel, if applicable. Suitable URIs
+         * include
+         * <ul>
+         * <li>{@code globalServiceId} from ATSC A/331. ex {@code https://doi.org/10.5239/7E4E-B472}
+         * <li>Other broadcast ID provider. ex {@code http://example.com/tv_channel/1234}
+         * </ul>
+         *
+         * <p>Can be empty.
+         *
+         * <p>Type: TEXT
+         */
+        public static final String COLUMN_GLOBAL_CONTENT_ID = "global_content_id";
+
         private Channels() {}
 
         /**
@@ -2562,6 +2605,37 @@
          */
         public static final String COLUMN_RECORDING_PROHIBITED = "recording_prohibited";
 
+        /**
+         * The event ID of this TV program.
+         *
+         * <p>It is used to identify the current TV program in the same channel, if applicable.
+         * Use the same coding for {@code event_id} in the underlying broadcast standard if it
+         * is defined there (e.g. ATSC A/65, ETSI EN 300 468 and ARIB STD-B10).
+         *
+         * <p>This is a required field only if the underlying broadcast standard defines the same
+         * name field. Otherwise, leave empty.
+         *
+         * <p>Type: INTEGER
+         */
+        public static final String COLUMN_EVENT_ID = "event_id";
+
+        /**
+         * The global content ID of this TV program, as a URI.
+         *
+         * <p>A globally unique ID that identifies this TV program, if applicable. Suitable URIs
+         * include
+         * <ul>
+         * <li>{@code crid://<CRIDauthority>/<data>} from ETSI TS 102 323
+         * <li>{@code globalContentId} from ATSC A/332
+         * <li>Other broadcast ID provider. ex {@code http://example.com/tv_program/1234}
+         * </ul>
+         *
+         * <p>Can be empty.
+         *
+         * <p>Type: TEXT
+         */
+        public static final String COLUMN_GLOBAL_CONTENT_ID = "global_content_id";
+
         private Programs() {}
 
         /** Canonical genres for TV programs. */
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 5e01244..3561f83 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -74,6 +74,8 @@
     private List<Integer> mFrontendIds;
     private Frontend mFrontend;
     private EventHandler mHandler;
+    @Nullable
+    private FrontendInfo mFrontendInfo;
 
     private List<Integer> mLnbIds;
     private Lnb mLnb;
@@ -97,6 +99,7 @@
     public Tuner(@NonNull Context context, @NonNull String tvInputSessionId,
             @TvInputService.PriorityHintUseCaseType int useCase,
             @Nullable OnResourceLostListener listener) {
+        nativeSetup();
         mContext = context;
     }
 
@@ -185,7 +188,7 @@
     /**
      * Listener for resource lost.
      *
-     * <p>Resource is reclaimed and tuner instance is forced to close.
+     * <p>Insufficient resources are reclaimed by higher priority clients.
      */
     public interface OnResourceLostListener {
         /**
@@ -292,6 +295,7 @@
     @Result
     public int tune(@NonNull FrontendSettings settings) {
         TunerUtils.checkTunerPermission(mContext);
+        mFrontendInfo = null;
         return nativeTune(settings.getType(), settings);
     }
 
@@ -333,6 +337,7 @@
         }
         mScanCallback = scanCallback;
         mScanCallbackExecutor = executor;
+        mFrontendInfo = null;
         return nativeScan(settings.getType(), settings, scanType);
     }
 
@@ -468,7 +473,10 @@
         if (mFrontend == null) {
             throw new IllegalStateException("frontend is not initialized");
         }
-        return nativeGetFrontendInfo(mFrontend.mId);
+        if (mFrontendInfo == null) {
+            mFrontendInfo = nativeGetFrontendInfo(mFrontend.mId);
+        }
+        return mFrontendInfo;
     }
 
     /**
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 9b37f95..71ba59c 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -724,18 +724,21 @@
     }
 
     if (buffer->size() > 0) {
-        // asC2Buffer clears internal reference, so set the reference again.
         std::shared_ptr<C2Buffer> c2Buffer = buffer->asC2Buffer();
-        buffer->copy(c2Buffer);
         if (c2Buffer) {
+            // asC2Buffer clears internal reference, so set the reference again.
+            buffer->copy(c2Buffer);
             switch (c2Buffer->data().type()) {
                 case C2BufferData::LINEAR: {
                     std::unique_ptr<JMediaCodecLinearBlock> context{new JMediaCodecLinearBlock};
                     context->mBuffer = c2Buffer;
                     ScopedLocalRef<jobject> linearBlock{env, env->NewObject(
                             gLinearBlockInfo.clazz, gLinearBlockInfo.ctorId)};
-                    env->SetLongField(
-                            linearBlock.get(), gLinearBlockInfo.contextId, (jlong)context.release());
+                    env->CallVoidMethod(
+                            linearBlock.get(),
+                            gLinearBlockInfo.setInternalStateId,
+                            (jlong)context.release(),
+                            true);
                     env->SetObjectField(frame, gFields.outputFrameLinearBlockID, linearBlock.get());
                     break;
                 }
@@ -744,8 +747,11 @@
                     context->mBuffer = c2Buffer;
                     ScopedLocalRef<jobject> graphicBlock{env, env->NewObject(
                             gGraphicBlockInfo.clazz, gGraphicBlockInfo.ctorId)};
-                    env->SetLongField(
-                            graphicBlock.get(), gGraphicBlockInfo.contextId, (jlong)context.release());
+                    env->CallVoidMethod(
+                            graphicBlock.get(),
+                            gGraphicBlockInfo.setInternalStateId,
+                            (jlong)context.release(),
+                            true);
                     env->SetObjectField(frame, gFields.outputFrameGraphicBlockID, graphicBlock.get());
                     break;
                 }
@@ -761,16 +767,22 @@
                 context->mLegacyBuffer = buffer;
                 ScopedLocalRef<jobject> linearBlock{env, env->NewObject(
                         gLinearBlockInfo.clazz, gLinearBlockInfo.ctorId)};
-                env->SetLongField(
-                        linearBlock.get(), gLinearBlockInfo.contextId, (jlong)context.release());
+                env->CallVoidMethod(
+                        linearBlock.get(),
+                        gLinearBlockInfo.setInternalStateId,
+                        (jlong)context.release(),
+                        true);
                 env->SetObjectField(frame, gFields.outputFrameLinearBlockID, linearBlock.get());
             } else {
                 std::unique_ptr<JMediaCodecGraphicBlock> context{new JMediaCodecGraphicBlock};
                 context->mLegacyBuffer = buffer;
                 ScopedLocalRef<jobject> graphicBlock{env, env->NewObject(
                         gGraphicBlockInfo.clazz, gGraphicBlockInfo.ctorId)};
-                env->SetLongField(
-                        graphicBlock.get(), gGraphicBlockInfo.contextId, (jlong)context.release());
+                env->CallVoidMethod(
+                        graphicBlock.get(),
+                        gGraphicBlockInfo.setInternalStateId,
+                        (jlong)context.release(),
+                        true);
                 env->SetObjectField(frame, gFields.outputFrameGraphicBlockID, graphicBlock.get());
             }
         }
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 1fbe7f4..08c3f98 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -322,6 +322,179 @@
             (jint) jId);
 }
 
+jobject JTuner::getAnalogFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps) {
+    jclass clazz = env->FindClass("android/media/tv/tuner/frontend/AnalogFrontendCapabilities");
+    jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(II)V");
+
+    jint typeCap = caps.analogCaps().typeCap;
+    jint sifStandardCap = caps.analogCaps().sifStandardCap;
+    return env->NewObject(clazz, capsInit, typeCap, sifStandardCap);
+}
+
+jobject JTuner::getAtsc3FrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps) {
+    jclass clazz = env->FindClass("android/media/tv/tuner/frontend/Atsc3FrontendCapabilities");
+    jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(IIIIII)V");
+
+    jint bandwidthCap = caps.atsc3Caps().bandwidthCap;
+    jint modulationCap = caps.atsc3Caps().modulationCap;
+    jint timeInterleaveModeCap = caps.atsc3Caps().timeInterleaveModeCap;
+    jint codeRateCap = caps.atsc3Caps().codeRateCap;
+    jint fecCap = caps.atsc3Caps().fecCap;
+    jint demodOutputFormatCap = caps.atsc3Caps().demodOutputFormatCap;
+
+    return env->NewObject(clazz, capsInit, bandwidthCap, modulationCap, timeInterleaveModeCap,
+            codeRateCap, fecCap, demodOutputFormatCap);
+}
+
+jobject JTuner::getAtscFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps) {
+    jclass clazz = env->FindClass("android/media/tv/tuner/frontend/AtscFrontendCapabilities");
+    jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(I)V");
+
+    jint modulationCap = caps.atscCaps().modulationCap;
+
+    return env->NewObject(clazz, capsInit, modulationCap);
+}
+
+jobject JTuner::getDvbcFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps) {
+    jclass clazz = env->FindClass("android/media/tv/tuner/frontend/DvbcFrontendCapabilities");
+    jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(III)V");
+
+    jint modulationCap = caps.dvbcCaps().modulationCap;
+    jint fecCap = caps.dvbcCaps().fecCap;
+    jint annexCap = caps.dvbcCaps().annexCap;
+
+    return env->NewObject(clazz, capsInit, modulationCap, fecCap, annexCap);
+}
+
+jobject JTuner::getDvbsFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps) {
+    jclass clazz = env->FindClass("android/media/tv/tuner/frontend/DvbsFrontendCapabilities");
+    jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(IJI)V");
+
+    jint modulationCap = caps.dvbsCaps().modulationCap;
+    jlong innerfecCap = caps.dvbsCaps().innerfecCap;
+    jint standard = caps.dvbsCaps().standard;
+
+    return env->NewObject(clazz, capsInit, modulationCap, innerfecCap, standard);
+}
+
+jobject JTuner::getDvbtFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps) {
+    jclass clazz = env->FindClass("android/media/tv/tuner/frontend/DvbtFrontendCapabilities");
+    jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(IIIIIIZZ)V");
+
+    jint transmissionModeCap = caps.dvbtCaps().transmissionModeCap;
+    jint bandwidthCap = caps.dvbtCaps().bandwidthCap;
+    jint constellationCap = caps.dvbtCaps().constellationCap;
+    jint coderateCap = caps.dvbtCaps().coderateCap;
+    jint hierarchyCap = caps.dvbtCaps().hierarchyCap;
+    jint guardIntervalCap = caps.dvbtCaps().guardIntervalCap;
+    jboolean isT2Supported = caps.dvbtCaps().isT2Supported;
+    jboolean isMisoSupported = caps.dvbtCaps().isMisoSupported;
+
+    return env->NewObject(clazz, capsInit, transmissionModeCap, bandwidthCap, constellationCap,
+            coderateCap, hierarchyCap, guardIntervalCap, isT2Supported, isMisoSupported);
+}
+
+jobject JTuner::getIsdbs3FrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps) {
+    jclass clazz = env->FindClass("android/media/tv/tuner/frontend/Isdbs3FrontendCapabilities");
+    jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(II)V");
+
+    jint modulationCap = caps.isdbs3Caps().modulationCap;
+    jint coderateCap = caps.isdbs3Caps().coderateCap;
+
+    return env->NewObject(clazz, capsInit, modulationCap, coderateCap);
+}
+
+jobject JTuner::getIsdbsFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps) {
+    jclass clazz = env->FindClass("android/media/tv/tuner/frontend/IsdbsFrontendCapabilities");
+    jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(II)V");
+
+    jint modulationCap = caps.isdbsCaps().modulationCap;
+    jint coderateCap = caps.isdbsCaps().coderateCap;
+
+    return env->NewObject(clazz, capsInit, modulationCap, coderateCap);
+}
+
+jobject JTuner::getIsdbtFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps) {
+    jclass clazz = env->FindClass("android/media/tv/tuner/frontend/IsdbtFrontendCapabilities");
+    jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(IIIII)V");
+
+    jint modeCap = caps.isdbtCaps().modeCap;
+    jint bandwidthCap = caps.isdbtCaps().bandwidthCap;
+    jint modulationCap = caps.isdbtCaps().modulationCap;
+    jint coderateCap = caps.isdbtCaps().coderateCap;
+    jint guardIntervalCap = caps.isdbtCaps().guardIntervalCap;
+
+    return env->NewObject(clazz, capsInit, modeCap, bandwidthCap, modulationCap, coderateCap,
+            guardIntervalCap);
+}
+
+jobject JTuner::getFrontendInfo(int id) {
+    FrontendInfo feInfo;
+    Result res;
+    mTuner->getFrontendInfo(id, [&](Result r, const FrontendInfo& info) {
+        feInfo = info;
+        res = r;
+    });
+    if (res != Result::SUCCESS) {
+        return NULL;
+    }
+
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    jclass clazz = env->FindClass("android/media/tv/tuner/frontend/FrontendInfo");
+    jmethodID infoInit = env->GetMethodID(clazz, "<init>",
+            "(IIIIIIII[ILandroid/media/tv/tuner/frontend/FrontendCapabilities;)V");
+
+    jint type = (jint) feInfo.type;
+    jint minFrequency = feInfo.minFrequency;
+    jint maxFrequency = feInfo.maxFrequency;
+    jint minSymbolRate = feInfo.minSymbolRate;
+    jint maxSymbolRate = feInfo.maxSymbolRate;
+    jint acquireRange = feInfo.acquireRange;
+    jint exclusiveGroupId = feInfo.exclusiveGroupId;
+    jintArray statusCaps = env->NewIntArray(feInfo.statusCaps.size());
+    env->SetIntArrayRegion(
+            statusCaps, 0, feInfo.statusCaps.size(),
+            reinterpret_cast<jint*>(&feInfo.statusCaps[0]));
+    FrontendInfo::FrontendCapabilities caps = feInfo.frontendCaps;
+
+    jobject jcaps = NULL;
+    switch(feInfo.type) {
+        case FrontendType::ANALOG:
+            jcaps = getAnalogFrontendCaps(env, caps);
+            break;
+        case FrontendType::ATSC3:
+            jcaps = getAtsc3FrontendCaps(env, caps);
+            break;
+        case FrontendType::ATSC:
+            jcaps = getAtscFrontendCaps(env, caps);
+            break;
+        case FrontendType::DVBC:
+            jcaps = getDvbcFrontendCaps(env, caps);
+            break;
+        case FrontendType::DVBS:
+            jcaps = getDvbsFrontendCaps(env, caps);
+            break;
+        case FrontendType::DVBT:
+            jcaps = getDvbtFrontendCaps(env, caps);
+            break;
+        case FrontendType::ISDBS:
+            jcaps = getIsdbsFrontendCaps(env, caps);
+            break;
+        case FrontendType::ISDBS3:
+            jcaps = getIsdbs3FrontendCaps(env, caps);
+            break;
+        case FrontendType::ISDBT:
+            jcaps = getIsdbtFrontendCaps(env, caps);
+            break;
+        default:
+            break;
+    }
+
+    return env->NewObject(
+            clazz, infoInit, (jint) id, type, minFrequency, maxFrequency, minSymbolRate,
+            maxSymbolRate, acquireRange, exclusiveGroupId, statusCaps, jcaps);
+}
+
 jobject JTuner::getLnbIds() {
     ALOGD("JTuner::getLnbIds()");
     mTuner->getLnbIds([&](Result, const hidl_vec<FrontendId>& lnbIds) {
@@ -377,6 +550,15 @@
     return (int)result;
 }
 
+int JTuner::stopTune() {
+    if (mFe == NULL) {
+        ALOGE("frontend is not initialized");
+        return (int)Result::INVALID_STATE;
+    }
+    Result result = mFe->stopTune();
+    return (int)result;
+}
+
 int JTuner::scan(const FrontendSettings& settings, FrontendScanType scanType) {
     if (mFe == NULL) {
         ALOGE("frontend is not initialized");
@@ -386,6 +568,33 @@
     return (int)result;
 }
 
+int JTuner::stopScan() {
+    if (mFe == NULL) {
+        ALOGE("frontend is not initialized");
+        return (int)Result::INVALID_STATE;
+    }
+    Result result = mFe->stopScan();
+    return (int)result;
+}
+
+int JTuner::setLnb(int id) {
+    if (mFe == NULL) {
+        ALOGE("frontend is not initialized");
+        return (int)Result::INVALID_STATE;
+    }
+    Result result = mFe->setLnb(id);
+    return (int)result;
+}
+
+int JTuner::setLna(bool enable) {
+    if (mFe == NULL) {
+        ALOGE("frontend is not initialized");
+        return (int)Result::INVALID_STATE;
+    }
+    Result result = mFe->setLna(enable);
+    return (int)result;
+}
+
 bool JTuner::openDemux() {
     if (mTuner == nullptr) {
         return false;
@@ -1079,8 +1288,9 @@
     return tuner->tune(getFrontendSettings(env, type, settings));
 }
 
-static int android_media_tv_Tuner_stop_tune(JNIEnv*, jobject) {
-    return 0;
+static int android_media_tv_Tuner_stop_tune(JNIEnv *env, jobject thiz) {
+    sp<JTuner> tuner = getTuner(env, thiz);
+    return tuner->stopTune();
 }
 
 static int android_media_tv_Tuner_scan(
@@ -1090,16 +1300,19 @@
             env, settingsType, settings), static_cast<FrontendScanType>(scanType));
 }
 
-static int android_media_tv_Tuner_stop_scan(JNIEnv*, jobject) {
-    return 0;
+static int android_media_tv_Tuner_stop_scan(JNIEnv *env, jobject thiz) {
+    sp<JTuner> tuner = getTuner(env, thiz);
+    return tuner->stopScan();
 }
 
-static int android_media_tv_Tuner_set_lnb(JNIEnv*, jobject, jint) {
-    return 0;
+static int android_media_tv_Tuner_set_lnb(JNIEnv *env, jobject thiz, jint id) {
+    sp<JTuner> tuner = getTuner(env, thiz);
+    return tuner->setLnb(id);
 }
 
-static int android_media_tv_Tuner_set_lna(JNIEnv*, jobject, jint, jboolean) {
-    return 0;
+static int android_media_tv_Tuner_set_lna(JNIEnv *env, jobject thiz, jboolean enable) {
+    sp<JTuner> tuner = getTuner(env, thiz);
+    return tuner->setLna(enable);
 }
 
 static jobject android_media_tv_Tuner_get_frontend_status(JNIEnv, jobject, jintArray) {
@@ -1122,8 +1335,9 @@
     return 0;
 }
 
-static jobject android_media_tv_Tuner_get_frontend_info(JNIEnv*, jobject, jint) {
-    return NULL;
+static jobject android_media_tv_Tuner_get_frontend_info(JNIEnv *env, jobject thiz, jint id) {
+    sp<JTuner> tuner = getTuner(env, thiz);
+    return tuner->getFrontendInfo(id);
 }
 
 static jobject android_media_tv_Tuner_get_lnb_ids(JNIEnv *env, jobject thiz) {
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index 5c012bb..cfe99b3 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -39,6 +39,7 @@
 using ::android::hardware::tv::tuner::V1_0::DvrType;
 using ::android::hardware::tv::tuner::V1_0::FrontendEventType;
 using ::android::hardware::tv::tuner::V1_0::FrontendId;
+using ::android::hardware::tv::tuner::V1_0::FrontendInfo;
 using ::android::hardware::tv::tuner::V1_0::FrontendScanMessage;
 using ::android::hardware::tv::tuner::V1_0::FrontendScanMessageType;
 using ::android::hardware::tv::tuner::V1_0::FrontendScanType;
@@ -132,8 +133,13 @@
     sp<ITuner> getTunerService();
     jobject getFrontendIds();
     jobject openFrontendById(int id);
+    jobject getFrontendInfo(int id);
     int tune(const FrontendSettings& settings);
+    int stopTune();
     int scan(const FrontendSettings& settings, FrontendScanType scanType);
+    int stopScan();
+    int setLnb(int id);
+    int setLna(bool enable);
     jobject getLnbIds();
     jobject openLnbById(int id);
     jobject openFilter(DemuxFilterType type, int bufferSize);
@@ -154,6 +160,15 @@
     sp<ILnb> mLnb;
     sp<IDemux> mDemux;
     int mDemuxId;
+    static jobject getAnalogFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps);
+    static jobject getAtsc3FrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps);
+    static jobject getAtscFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps);
+    static jobject getDvbcFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps);
+    static jobject getDvbsFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps);
+    static jobject getDvbtFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps);
+    static jobject getIsdbs3FrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps);
+    static jobject getIsdbsFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps);
+    static jobject getIsdbtFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps);
 };
 
 }  // namespace android
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
index 4316e42..f10e5eb 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
@@ -289,12 +289,13 @@
         MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
 
         int originalVolume = volRoute.getVolume();
-        int deltaVolume = (originalVolume == volRoute.getVolumeMax() ? -1 : 1);
+        int targetVolume = originalVolume == volRoute.getVolumeMax()
+                ? originalVolume - 1 : originalVolume + 1;
 
         awaitOnRouteChangedManager(
-                () -> mManager.requestUpdateVolume(volRoute, deltaVolume),
+                () -> mManager.requestSetVolume(volRoute, targetVolume),
                 ROUTE_ID_VARIABLE_VOLUME,
-                (route -> route.getVolume() == originalVolume + deltaVolume));
+                (route -> route.getVolume() == targetVolume));
 
         awaitOnRouteChangedManager(
                 () -> mManager.requestSetVolume(volRoute, originalVolume),
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/SampleMediaRoute2ProviderService.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/SampleMediaRoute2ProviderService.java
index e29b323..1a866ca 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/SampleMediaRoute2ProviderService.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/SampleMediaRoute2ProviderService.java
@@ -154,21 +154,6 @@
     }
 
     @Override
-    public void onUpdateVolume(String routeId, int delta) {
-        android.util.Log.d(TAG, "onUpdateVolume routeId= " + routeId + "delta=" + delta);
-        MediaRoute2Info route = mRoutes.get(routeId);
-        if (route == null) {
-            return;
-        }
-        int volume = route.getVolume() + delta;
-        volume = Math.min(volume, Math.max(0, route.getVolumeMax()));
-        mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
-                .setVolume(volume)
-                .build());
-        publishRoutes();
-    }
-
-    @Override
     public void onCreateSession(String packageName, String routeId, long requestId,
             @Nullable Bundle sessionHints) {
         MediaRoute2Info route = mRoutes.get(routeId);
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index 392c9f6..ba793e8 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -294,7 +294,7 @@
 
         auto& aSurfaceControlStats = aSurfaceTransactionStats.aSurfaceControlStats;
 
-        for (const auto& [surfaceControl, acquireTime, previousReleaseFence, transformHint] : surfaceControlStats) {
+        for (const auto& [surfaceControl, latchTime, acquireTime, presentFence, previousReleaseFence, transformHint, frameEvents] : surfaceControlStats) {
             ASurfaceControl* aSurfaceControl = reinterpret_cast<ASurfaceControl*>(surfaceControl.get());
             aSurfaceControlStats[aSurfaceControl].acquireTime = acquireTime;
             aSurfaceControlStats[aSurfaceControl].previousReleaseFence = previousReleaseFence;
diff --git a/packages/DynamicSystemInstallationService/res/values/strings.xml b/packages/DynamicSystemInstallationService/res/values/strings.xml
index 9bd5be7..7595d2b 100644
--- a/packages/DynamicSystemInstallationService/res/values/strings.xml
+++ b/packages/DynamicSystemInstallationService/res/values/strings.xml
@@ -35,4 +35,7 @@
     <!-- Toast when we fail to launch into Dynamic System [CHAR LIMIT=64] -->
     <string name="toast_failed_to_reboot_to_dynsystem">Can\u2019t restart or load dynamic system</string>
 
+    <!-- URL of Dynamic System Key Revocation List [DO NOT TRANSLATE] -->
+    <string name="key_revocation_list_url" translatable="false">https://dl.google.com/developers/android/gsi/gsi-keyblacklist.json</string>
+
 </resources>
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
index 9ccb837..9bae223 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
@@ -46,6 +46,7 @@
 import android.app.Service;
 import android.content.Context;
 import android.content.Intent;
+import android.net.http.HttpResponseCache;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -60,6 +61,8 @@
 import android.util.Log;
 import android.widget.Toast;
 
+import java.io.File;
+import java.io.IOException;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 
@@ -146,10 +149,26 @@
         prepareNotification();
 
         mDynSystem = (DynamicSystemManager) getSystemService(Context.DYNAMIC_SYSTEM_SERVICE);
+
+        // Install an HttpResponseCache in the application cache directory so we can cache
+        // gsi key revocation list. The http(s) protocol handler uses this cache transparently.
+        // The cache size is chosen heuristically. Since we don't have too much traffic right now,
+        // a moderate size of 1MiB should be enough.
+        try {
+            File httpCacheDir = new File(getCacheDir(), "httpCache");
+            long httpCacheSize = 1 * 1024 * 1024; // 1 MiB
+            HttpResponseCache.install(httpCacheDir, httpCacheSize);
+        } catch (IOException e) {
+            Log.d(TAG, "HttpResponseCache.install() failed: " + e);
+        }
     }
 
     @Override
     public void onDestroy() {
+        HttpResponseCache cache = HttpResponseCache.getInstalled();
+        if (cache != null) {
+            cache.flush();
+        }
         // Cancel the persistent notification.
         mNM.cancel(NOTIFICATION_ID);
     }
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
index 9aea0e7..438c435 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
@@ -25,6 +25,8 @@
 import android.util.Log;
 import android.webkit.URLUtil;
 
+import org.json.JSONException;
+
 import java.io.BufferedInputStream;
 import java.io.File;
 import java.io.IOException;
@@ -100,7 +102,9 @@
     private final Context mContext;
     private final DynamicSystemManager mDynSystem;
     private final ProgressListener mListener;
+    private final boolean mIsNetworkUrl;
     private DynamicSystemManager.Session mInstallationSession;
+    private KeyRevocationList mKeyRevocationList;
 
     private boolean mIsZip;
     private boolean mIsCompleted;
@@ -123,6 +127,7 @@
         mContext = context;
         mDynSystem = dynSystem;
         mListener = listener;
+        mIsNetworkUrl = URLUtil.isNetworkUrl(mUrl);
     }
 
     @Override
@@ -152,9 +157,11 @@
                 return null;
             }
 
+            // TODO(yochiang): do post-install public key check (revocation list / boot-ramdisk)
+
             mDynSystem.finishInstallation();
         } catch (Exception e) {
-            e.printStackTrace();
+            Log.e(TAG, e.toString(), e);
             mDynSystem.remove();
             return e;
         } finally {
@@ -220,7 +227,7 @@
                 String.format(Locale.US, "Unsupported file format: %s", mUrl));
         }
 
-        if (URLUtil.isNetworkUrl(mUrl)) {
+        if (mIsNetworkUrl) {
             mStream = new URL(mUrl).openStream();
         } else if (URLUtil.isFileUrl(mUrl)) {
             if (mIsZip) {
@@ -234,6 +241,25 @@
             throw new UnsupportedUrlException(
                     String.format(Locale.US, "Unsupported URL: %s", mUrl));
         }
+
+        // TODO(yochiang): Bypass this check if device is unlocked
+        try {
+            String listUrl = mContext.getString(R.string.key_revocation_list_url);
+            mKeyRevocationList = KeyRevocationList.fromUrl(new URL(listUrl));
+        } catch (IOException | JSONException e) {
+            Log.d(TAG, "Failed to fetch Dynamic System Key Revocation List");
+            mKeyRevocationList = new KeyRevocationList();
+            keyRevocationThrowOrWarning(e);
+        }
+    }
+
+    private void keyRevocationThrowOrWarning(Exception e) throws Exception {
+        if (mIsNetworkUrl) {
+            throw e;
+        } else {
+            // If DSU is being installed from a local file URI, then be permissive
+            Log.w(TAG, e.toString());
+        }
     }
 
     private void installUserdata() throws Exception {
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/KeyRevocationList.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/KeyRevocationList.java
new file mode 100644
index 0000000..522bc54
--- /dev/null
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/KeyRevocationList.java
@@ -0,0 +1,148 @@
+/*
+ * 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.dynsystem;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.HashMap;
+
+class KeyRevocationList {
+
+    private static final String TAG = "KeyRevocationList";
+
+    private static final String JSON_ENTRIES = "entries";
+    private static final String JSON_PUBLIC_KEY = "public_key";
+    private static final String JSON_STATUS = "status";
+    private static final String JSON_REASON = "reason";
+
+    private static final String STATUS_REVOKED = "REVOKED";
+
+    @VisibleForTesting
+    HashMap<String, RevocationStatus> mEntries;
+
+    static class RevocationStatus {
+        final String mStatus;
+        final String mReason;
+
+        RevocationStatus(String status, String reason) {
+            mStatus = status;
+            mReason = reason;
+        }
+    }
+
+    KeyRevocationList() {
+        mEntries = new HashMap<String, RevocationStatus>();
+    }
+
+    /**
+     * Returns the revocation status of a public key.
+     *
+     * @return a RevocationStatus for |publicKey|, null if |publicKey| doesn't exist.
+     */
+    RevocationStatus getRevocationStatusForKey(String publicKey) {
+        return mEntries.get(publicKey);
+    }
+
+    /** Test if a public key is revoked or not. */
+    boolean isRevoked(String publicKey) {
+        RevocationStatus entry = getRevocationStatusForKey(publicKey);
+        return entry != null && TextUtils.equals(entry.mStatus, STATUS_REVOKED);
+    }
+
+    @VisibleForTesting
+    void addEntry(String publicKey, String status, String reason) {
+        mEntries.put(publicKey, new RevocationStatus(status, reason));
+    }
+
+    /**
+     * Creates a KeyRevocationList from a JSON String.
+     *
+     * @param jsonString the revocation list, for example:
+     *     <pre>{@code
+     *      {
+     *        "entries": [
+     *          {
+     *            "public_key": "00fa2c6637c399afa893fe83d85f3569998707d5",
+     *            "status": "REVOKED",
+     *            "reason": "Revocation Reason"
+     *          }
+     *        ]
+     *      }
+     *     }</pre>
+     *
+     * @throws JSONException if |jsonString| is malformed.
+     */
+    static KeyRevocationList fromJsonString(String jsonString) throws JSONException {
+        JSONObject jsonObject = new JSONObject(jsonString);
+        KeyRevocationList list = new KeyRevocationList();
+        Log.d(TAG, "Begin of revocation list");
+        if (jsonObject.has(JSON_ENTRIES)) {
+            JSONArray entries = jsonObject.getJSONArray(JSON_ENTRIES);
+            for (int i = 0; i < entries.length(); ++i) {
+                JSONObject entry = entries.getJSONObject(i);
+                String publicKey = entry.getString(JSON_PUBLIC_KEY);
+                String status = entry.getString(JSON_STATUS);
+                String reason = entry.has(JSON_REASON) ? entry.getString(JSON_REASON) : "";
+                list.addEntry(publicKey, status, reason);
+                Log.d(TAG, "Revocation entry: " + entry.toString());
+            }
+        }
+        Log.d(TAG, "End of revocation list");
+        return list;
+    }
+
+    /**
+     * Creates a KeyRevocationList from a URL.
+     *
+     * @throws IOException if |url| is inaccessible.
+     * @throws JSONException if fetched content is malformed.
+     */
+    static KeyRevocationList fromUrl(URL url) throws IOException, JSONException {
+        Log.d(TAG, "Fetch from URL: " + url.toString());
+        // Force "conditional GET"
+        // Force validate the cached result with server each time, and use the cached result
+        // only if it is validated by server, else fetch new data from server.
+        // Ref: https://developer.android.com/reference/android/net/http/HttpResponseCache#force-a-network-response
+        URLConnection connection = url.openConnection();
+        connection.setUseCaches(true);
+        connection.addRequestProperty("Cache-Control", "max-age=0");
+        try (InputStream stream = connection.getInputStream()) {
+            return fromJsonString(readFully(stream));
+        }
+    }
+
+    private static String readFully(InputStream in) throws IOException {
+        int n;
+        byte[] buffer = new byte[4096];
+        StringBuilder builder = new StringBuilder();
+        while ((n = in.read(buffer, 0, 4096)) > -1) {
+            builder.append(new String(buffer, 0, n));
+        }
+        return builder.toString();
+    }
+}
diff --git a/packages/DynamicSystemInstallationService/tests/Android.bp b/packages/DynamicSystemInstallationService/tests/Android.bp
new file mode 100644
index 0000000..3bdf829
--- /dev/null
+++ b/packages/DynamicSystemInstallationService/tests/Android.bp
@@ -0,0 +1,15 @@
+android_test {
+    name: "DynamicSystemInstallationServiceTests",
+
+    srcs: ["src/**/*.java"],
+    static_libs: [
+        "androidx.test.runner",
+        "androidx.test.rules",
+        "mockito-target-minus-junit4",
+    ],
+
+    resource_dirs: ["res"],
+    platform_apis: true,
+    instrumentation_for: "DynamicSystemInstallationService",
+    certificate: "platform",
+}
diff --git a/packages/DynamicSystemInstallationService/tests/AndroidManifest.xml b/packages/DynamicSystemInstallationService/tests/AndroidManifest.xml
new file mode 100644
index 0000000..f5f0ae6
--- /dev/null
+++ b/packages/DynamicSystemInstallationService/tests/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.dynsystem.tests">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.dynsystem"
+        android:label="Tests for DynamicSystemInstallationService" />
+
+</manifest>
diff --git a/packages/DynamicSystemInstallationService/tests/res/values/strings.xml b/packages/DynamicSystemInstallationService/tests/res/values/strings.xml
new file mode 100644
index 0000000..fdb620b
--- /dev/null
+++ b/packages/DynamicSystemInstallationService/tests/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!-- testFromJsonString -->
+    <string name="blacklist_json_string" translatable="false">
+      {
+        \"entries\":[
+          {
+            \"public_key\":\"00fa2c6637c399afa893fe83d85f3569998707d5\",
+            \"status\":\"REVOKED\",
+            \"reason\":\"Key revocation test key\"
+          },
+          {
+            \"public_key\":\"key2\",
+            \"status\":\"REVOKED\"
+          }
+        ]
+      }
+    </string>
+</resources>
diff --git a/packages/DynamicSystemInstallationService/tests/src/com/android/dynsystem/KeyRevocationListTest.java b/packages/DynamicSystemInstallationService/tests/src/com/android/dynsystem/KeyRevocationListTest.java
new file mode 100644
index 0000000..82ce542
--- /dev/null
+++ b/packages/DynamicSystemInstallationService/tests/src/com/android/dynsystem/KeyRevocationListTest.java
@@ -0,0 +1,132 @@
+/*
+ * 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.dynsystem;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import android.content.Context;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.json.JSONException;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+
+/**
+ * A test for KeyRevocationList.java
+ */
+@RunWith(AndroidJUnit4.class)
+public class KeyRevocationListTest {
+
+    private static final String TAG = "KeyRevocationListTest";
+
+    private static Context sContext;
+
+    private static String sBlacklistJsonString;
+
+    @BeforeClass
+    public static void setUpClass() throws Exception {
+        sContext = InstrumentationRegistry.getInstrumentation().getContext();
+        sBlacklistJsonString =
+                sContext.getString(com.android.dynsystem.tests.R.string.blacklist_json_string);
+    }
+
+    @Test
+    @SmallTest
+    public void testFromJsonString() throws JSONException {
+        KeyRevocationList blacklist;
+        blacklist = KeyRevocationList.fromJsonString(sBlacklistJsonString);
+        Assert.assertNotNull(blacklist);
+        Assert.assertFalse(blacklist.mEntries.isEmpty());
+        blacklist = KeyRevocationList.fromJsonString("{}");
+        Assert.assertNotNull(blacklist);
+        Assert.assertTrue(blacklist.mEntries.isEmpty());
+    }
+
+    @Test
+    @SmallTest
+    public void testFromUrl() throws IOException, JSONException {
+        URLConnection mockConnection = mock(URLConnection.class);
+        doReturn(new ByteArrayInputStream(sBlacklistJsonString.getBytes()))
+                .when(mockConnection).getInputStream();
+        URL mockUrl = new URL(
+                "http",     // protocol
+                "foo.bar",  // host
+                80,         // port
+                "baz",      // file
+                new URLStreamHandler() {
+                    @Override
+                    protected URLConnection openConnection(URL url) {
+                        return mockConnection;
+                    }
+                });
+        URL mockBadUrl = new URL(
+                "http",     // protocol
+                "foo.bar",  // host
+                80,         // port
+                "baz",      // file
+                new URLStreamHandler() {
+                    @Override
+                    protected URLConnection openConnection(URL url) throws IOException {
+                        throw new IOException();
+                    }
+                });
+
+        KeyRevocationList blacklist = KeyRevocationList.fromUrl(mockUrl);
+        Assert.assertNotNull(blacklist);
+        Assert.assertFalse(blacklist.mEntries.isEmpty());
+
+        blacklist = null;
+        try {
+            blacklist = KeyRevocationList.fromUrl(mockBadUrl);
+            // Up should throw, down should be unreachable
+            Assert.fail("Expected IOException not thrown");
+        } catch (IOException e) {
+            // This is expected, do nothing
+        }
+        Assert.assertNull(blacklist);
+    }
+
+    @Test
+    @SmallTest
+    public void testIsRevoked() {
+        KeyRevocationList blacklist = new KeyRevocationList();
+        blacklist.addEntry("key1", "REVOKED", "reason for key1");
+
+        KeyRevocationList.RevocationStatus revocationStatus =
+                blacklist.getRevocationStatusForKey("key1");
+        Assert.assertNotNull(revocationStatus);
+        Assert.assertEquals(revocationStatus.mReason, "reason for key1");
+
+        revocationStatus = blacklist.getRevocationStatusForKey("key2");
+        Assert.assertNull(revocationStatus);
+
+        Assert.assertTrue(blacklist.isRevoked("key1"));
+        Assert.assertFalse(blacklist.isRevoked("key2"));
+    }
+}
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index 9c8345d..6212493 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -10,6 +10,7 @@
         "androidx.appcompat_appcompat",
         "androidx.lifecycle_lifecycle-runtime",
         "androidx.mediarouter_mediarouter-nodeps",
+        "iconloader",
 
         "SettingsLibHelpUtils",
         "SettingsLibRestrictedLockUtils",
diff --git a/packages/SettingsLib/res/values/config.xml b/packages/SettingsLib/res/values/config.xml
new file mode 100644
index 0000000..332d6c7
--- /dev/null
+++ b/packages/SettingsLib/res/values/config.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<!-- These resources are around just to allow their values to be customized -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Threshold in micro watts below which a charger is rated as "slow"; 1A @ 5V -->
+    <integer name="config_chargingSlowlyThreshold">5000000</integer>
+
+    <!-- Threshold in micro watts above which a charger is rated as "fast"; 1.5A @ 5V  -->
+    <integer name="config_chargingFastThreshold">7500000</integer>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index dd21e5c..2e7a3c0 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1034,8 +1034,10 @@
     <string name="battery_info_status_unknown">Unknown</string>
     <!-- [CHAR_LIMIT=20] Battery use screen.  Battery status shown in chart label when charging from an unknown source.  -->
     <string name="battery_info_status_charging">Charging</string>
-    <!-- [CHAR_LIMIT=20] Battery use screen with lower case.  Battery status shown in chart label when charging from an unknown source.  -->
-    <string name="battery_info_status_charging_lower">charging</string>
+    <!-- [CHAR_LIMIT=20] Battery use screen.  Battery status shown in chart label when charging speed is fast.  -->
+    <string name="battery_info_status_charging_fast">Charging rapidly</string>
+    <!-- [CHAR_LIMIT=20] Battery use screen.  Battery status shown in chart label when charging speed is slow.  -->
+    <string name="battery_info_status_charging_slow">Charging slowly</string>
     <!-- Battery Info screen. Value for a status item.  Used for diagnostic info screens, precise translation isn't needed -->
     <string name="battery_info_status_discharging">Not charging</string>
     <!-- Battery Info screen. Value for a status item.  Used for diagnostic info screens, precise translation isn't needed -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index de523d9..f485793 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -3,6 +3,7 @@
 import android.annotation.ColorInt;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
@@ -13,6 +14,7 @@
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.Color;
+import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.location.LocationManager;
 import android.media.AudioManager;
@@ -27,9 +29,13 @@
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.ServiceState;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.UserIcons;
+import com.android.launcher3.icons.IconFactory;
 import com.android.settingslib.drawable.UserIconDrawable;
+import com.android.settingslib.fuelgauge.BatteryStatus;
 
 import java.text.NumberFormat;
 
@@ -117,7 +123,7 @@
     public static Drawable getUserIcon(Context context, UserManager um, UserInfo user) {
         final int iconSize = UserIconDrawable.getSizeForList(context);
         if (user.isManagedProfile()) {
-            Drawable drawable =  UserIconDrawable.getManagedUserDrawable(context);
+            Drawable drawable = UserIconDrawable.getManagedUserDrawable(context);
             drawable.setBounds(0, 0, iconSize, iconSize);
             return drawable;
         }
@@ -159,20 +165,43 @@
         return (level * 100) / scale;
     }
 
-    public static String getBatteryStatus(Resources res, Intent batteryChangedIntent) {
-        int status = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_STATUS,
+    /**
+     * Get battery status string
+     *
+     * @param context the context
+     * @param batteryChangedIntent battery broadcast intent received from {@link
+     *                             Intent.ACTION_BATTERY_CHANGED}.
+     * @return battery status string
+     */
+    public static String getBatteryStatus(Context context, Intent batteryChangedIntent) {
+        final int status = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_STATUS,
                 BatteryManager.BATTERY_STATUS_UNKNOWN);
-        String statusString;
-        if (status == BatteryManager.BATTERY_STATUS_CHARGING) {
-            statusString = res.getString(R.string.battery_info_status_charging);
-        } else if (status == BatteryManager.BATTERY_STATUS_DISCHARGING) {
-            statusString = res.getString(R.string.battery_info_status_discharging);
-        } else if (status == BatteryManager.BATTERY_STATUS_NOT_CHARGING) {
-            statusString = res.getString(R.string.battery_info_status_not_charging);
-        } else if (status == BatteryManager.BATTERY_STATUS_FULL) {
+        final Resources res = context.getResources();
+
+        String statusString = res.getString(R.string.battery_info_status_unknown);
+        final BatteryStatus batteryStatus = new BatteryStatus(batteryChangedIntent);
+
+        if (batteryStatus.isCharged()) {
             statusString = res.getString(R.string.battery_info_status_full);
         } else {
-            statusString = res.getString(R.string.battery_info_status_unknown);
+            if (status == BatteryManager.BATTERY_STATUS_CHARGING) {
+                switch (batteryStatus.getChargingSpeed(context)) {
+                    case BatteryStatus.CHARGING_FAST:
+                        statusString = res.getString(R.string.battery_info_status_charging_fast);
+                        break;
+                    case BatteryStatus.CHARGING_SLOWLY:
+                        statusString = res.getString(R.string.battery_info_status_charging_slow);
+                        break;
+                    default:
+                        statusString = res.getString(R.string.battery_info_status_charging);
+                        break;
+                }
+
+            } else if (status == BatteryManager.BATTERY_STATUS_DISCHARGING) {
+                statusString = res.getString(R.string.battery_info_status_discharging);
+            } else if (status == BatteryManager.BATTERY_STATUS_NOT_CHARGING) {
+                statusString = res.getString(R.string.battery_info_status_not_charging);
+            }
         }
 
         return statusString;
@@ -206,7 +235,7 @@
     /**
      * This method computes disabled color from normal color
      *
-     * @param context
+     * @param context the context
      * @param inputColor normal color.
      * @return disabled color.
      */
@@ -424,6 +453,19 @@
         return state;
     }
 
+    /**
+     * Get the {@link Drawable} that represents the app icon
+     */
+    public static @NonNull Drawable getBadgedIcon(
+            @NonNull Context context, @NonNull ApplicationInfo appInfo) {
+        final UserHandle user = UserHandle.getUserHandleForUid(appInfo.uid);
+        try (IconFactory iconFactory = IconFactory.obtain(context)) {
+            final Bitmap iconBmp = iconFactory.createBadgedIconBitmap(
+                    appInfo.loadUnbadgedIcon(context.getPackageManager()), user, false).icon;
+            return new BitmapDrawable(context.getResources(), iconBmp);
+        }
+    }
+
     private static boolean isNotInIwlan(ServiceState serviceState) {
         final NetworkRegistrationInfo networkRegWlan = serviceState.getNetworkRegistrationInfo(
                 NetworkRegistrationInfo.DOMAIN_PS,
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index 19c6664..af72888 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -59,6 +59,7 @@
 
 import com.android.internal.R;
 import com.android.internal.util.ArrayUtils;
+import com.android.settingslib.Utils;
 
 import java.io.File;
 import java.io.IOException;
@@ -495,7 +496,7 @@
             return;
         }
         synchronized (entry) {
-            entry.ensureIconLocked(mContext, mDrawableFactory);
+            entry.ensureIconLocked(mContext);
         }
     }
 
@@ -1216,7 +1217,7 @@
                                 AppEntry entry = mAppEntries.get(i);
                                 if (entry.icon == null || !entry.mounted) {
                                     synchronized (entry) {
-                                        if (entry.ensureIconLocked(mContext, mDrawableFactory)) {
+                                        if (entry.ensureIconLocked(mContext)) {
                                             if (!mRunning) {
                                                 mRunning = true;
                                                 Message m = mMainHandler.obtainMessage(
@@ -1587,10 +1588,10 @@
             }
         }
 
-        boolean ensureIconLocked(Context context, IconDrawableFactory drawableFactory) {
+        boolean ensureIconLocked(Context context) {
             if (this.icon == null) {
                 if (this.apkFile.exists()) {
-                    this.icon = drawableFactory.getBadgedIcon(info);
+                    this.icon = Utils.getBadgedIcon(context, info);
                     return true;
                 } else {
                     this.mounted = false;
@@ -1601,7 +1602,7 @@
                 // its icon.
                 if (this.apkFile.exists()) {
                     this.mounted = true;
-                    this.icon = drawableFactory.getBadgedIcon(info);
+                    this.icon = Utils.getBadgedIcon(context, info);
                     return true;
                 }
             }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
index ddb7341..1ebe917 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
@@ -153,21 +153,6 @@
         return mService.getDevicesMatchingConnectionStates(states);
     }
 
-    public boolean connect(BluetoothDevice device) {
-        if (mService == null) {
-            return false;
-        }
-        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
-    }
-
-    public boolean disconnect(BluetoothDevice device) {
-        if (mService == null) {
-            return false;
-        }
-
-        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
-    }
-
     public int getConnectionStatus(BluetoothDevice device) {
         if (mService == null) {
             return BluetoothProfile.STATE_DISCONNECTED;
@@ -187,31 +172,37 @@
         return mService.getActiveDevice();
     }
 
-    public boolean isPreferred(BluetoothDevice device) {
+    @Override
+    public boolean isEnabled(BluetoothDevice device) {
         if (mService == null) {
             return false;
         }
         return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
-    public int getPreferred(BluetoothDevice device) {
+    @Override
+    public int getConnectionPolicy(BluetoothDevice device) {
         if (mService == null) {
             return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
 
-    public void setPreferred(BluetoothDevice device, boolean preferred) {
+    @Override
+    public boolean setEnabled(BluetoothDevice device, boolean enabled) {
+        boolean isEnabled = false;
         if (mService == null) {
-            return;
+            return false;
         }
-        if (preferred) {
+        if (enabled) {
             if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+                isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
+
+        return isEnabled;
     }
     boolean isA2dpPlaying() {
         if (mService == null) return false;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
index 8ca5a74..c7a5bd8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
@@ -115,21 +115,6 @@
                          BluetoothProfile.STATE_DISCONNECTING});
     }
 
-    public boolean connect(BluetoothDevice device) {
-        if (mService == null) {
-            return false;
-        }
-        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
-    }
-
-    public boolean disconnect(BluetoothDevice device) {
-        if (mService == null) {
-            return false;
-        }
-
-        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
-    }
-
     public int getConnectionStatus(BluetoothDevice device) {
         if (mService == null) {
             return BluetoothProfile.STATE_DISCONNECTED;
@@ -137,31 +122,37 @@
         return mService.getConnectionState(device);
     }
 
-    public boolean isPreferred(BluetoothDevice device) {
+    @Override
+    public boolean isEnabled(BluetoothDevice device) {
         if (mService == null) {
             return false;
         }
         return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
-    public int getPreferred(BluetoothDevice device) {
+    @Override
+    public int getConnectionPolicy(BluetoothDevice device) {
         if (mService == null) {
             return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
 
-    public void setPreferred(BluetoothDevice device, boolean preferred) {
+    @Override
+    public boolean setEnabled(BluetoothDevice device, boolean enabled) {
+        boolean isEnabled = false;
         if (mService == null) {
-            return;
+            return false;
         }
-        if (preferred) {
+        if (enabled) {
             if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+                isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
+
+        return isEnabled;
     }
 
     boolean isAudioPlaying() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 50d3a5d..3aa35cb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -195,7 +195,7 @@
 
             if (newProfileState == BluetoothProfile.STATE_CONNECTED) {
                 if (profile instanceof MapProfile) {
-                    profile.setPreferred(mDevice, true);
+                    profile.setEnabled(mDevice, true);
                 }
                 if (!mProfiles.contains(profile)) {
                     mRemovedProfiles.remove(profile);
@@ -208,7 +208,7 @@
                 }
             } else if (profile instanceof MapProfile
                     && newProfileState == BluetoothProfile.STATE_DISCONNECTED) {
-                profile.setPreferred(mDevice, false);
+                profile.setEnabled(mDevice, false);
             } else if (mLocalNapRoleConnected && profile instanceof PanProfile
                     && ((PanProfile) profile).isLocalRoleNap(mDevice)
                     && newProfileState == BluetoothProfile.STATE_DISCONNECTED) {
@@ -250,12 +250,12 @@
         PbapServerProfile PbapProfile = mProfileManager.getPbapProfile();
         if (PbapProfile != null && isConnectedProfile(PbapProfile))
         {
-            PbapProfile.disconnect(mDevice);
+            PbapProfile.setEnabled(mDevice, false);
         }
     }
 
     public void disconnect(LocalBluetoothProfile profile) {
-        if (profile.disconnect(mDevice)) {
+        if (profile.setEnabled(mDevice, false)) {
             if (BluetoothUtils.D) {
                 Log.d(TAG, "Command sent successfully:DISCONNECT " + describe(profile));
             }
@@ -342,7 +342,7 @@
         if (!ensurePaired()) {
             return;
         }
-        if (profile.connect(mDevice)) {
+        if (profile.setEnabled(mDevice, true)) {
             if (BluetoothUtils.D) {
                 Log.d(TAG, "Command sent successfully:CONNECT " + describe(profile));
             }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
index 218d0b2..9dfc4d9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
@@ -114,21 +114,6 @@
         return true;
     }
 
-    public boolean connect(BluetoothDevice device) {
-        if (mService == null) {
-            return false;
-        }
-        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
-    }
-
-    public boolean disconnect(BluetoothDevice device) {
-        if (mService == null) {
-            return false;
-        }
-
-        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
-    }
-
     public int getConnectionStatus(BluetoothDevice device) {
         if (mService == null) {
             return BluetoothProfile.STATE_DISCONNECTED;
@@ -164,31 +149,37 @@
         return mService.getAudioState(device);
     }
 
-    public boolean isPreferred(BluetoothDevice device) {
+    @Override
+    public boolean isEnabled(BluetoothDevice device) {
         if (mService == null) {
             return false;
         }
         return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
-    public int getPreferred(BluetoothDevice device) {
+    @Override
+    public int getConnectionPolicy(BluetoothDevice device) {
         if (mService == null) {
             return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
 
-    public void setPreferred(BluetoothDevice device, boolean preferred) {
+    @Override
+    public boolean setEnabled(BluetoothDevice device, boolean enabled) {
+        boolean isEnabled = false;
         if (mService == null) {
-            return;
+            return false;
         }
-        if (preferred) {
+        if (enabled) {
             if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+                isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
+
+        return isEnabled;
     }
 
     public List<BluetoothDevice> getConnectedDevices() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
index b82fb37..a3b68b4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
@@ -151,21 +151,6 @@
         return mService.getDevicesMatchingConnectionStates(states);
     }
 
-    public boolean connect(BluetoothDevice device) {
-        if (mService == null) {
-            return false;
-        }
-        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
-    }
-
-    public boolean disconnect(BluetoothDevice device) {
-        if (mService == null) {
-            return false;
-        }
-
-        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
-    }
-
     public int getConnectionStatus(BluetoothDevice device) {
         if (mService == null) {
             return BluetoothProfile.STATE_DISCONNECTED;
@@ -185,31 +170,37 @@
         return mService.getActiveDevices();
     }
 
-    public boolean isPreferred(BluetoothDevice device) {
+    @Override
+    public boolean isEnabled(BluetoothDevice device) {
         if (mService == null) {
             return false;
         }
         return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
-    public int getPreferred(BluetoothDevice device) {
+    @Override
+    public int getConnectionPolicy(BluetoothDevice device) {
         if (mService == null) {
             return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
 
-    public void setPreferred(BluetoothDevice device, boolean preferred) {
+    @Override
+    public boolean setEnabled(BluetoothDevice device, boolean enabled) {
+        boolean isEnabled = false;
         if (mService == null) {
-            return;
+            return false;
         }
-        if (preferred) {
+        if (enabled) {
             if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+                isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
+
+        return isEnabled;
     }
 
     public void setVolume(int volume) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java
index 678f2e3..66225a2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java
@@ -125,23 +125,6 @@
     }
 
     @Override
-    public boolean connect(BluetoothDevice device) {
-        if (mService == null) {
-            return false;
-        }
-        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
-    }
-
-    @Override
-    public boolean disconnect(BluetoothDevice device) {
-        if (mService == null) {
-            return false;
-        }
-
-        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
-    }
-
-    @Override
     public int getConnectionStatus(BluetoothDevice device) {
         if (mService == null) {
             return BluetoothProfile.STATE_DISCONNECTED;
@@ -150,7 +133,7 @@
     }
 
     @Override
-    public boolean isPreferred(BluetoothDevice device) {
+    public boolean isEnabled(BluetoothDevice device) {
         if (mService == null) {
             return false;
         }
@@ -158,7 +141,7 @@
     }
 
     @Override
-    public int getPreferred(BluetoothDevice device) {
+    public int getConnectionPolicy(BluetoothDevice device) {
         if (mService == null) {
             return CONNECTION_POLICY_FORBIDDEN;
         }
@@ -166,17 +149,20 @@
     }
 
     @Override
-    public void setPreferred(BluetoothDevice device, boolean preferred) {
+    public boolean setEnabled(BluetoothDevice device, boolean enabled) {
+        boolean isEnabled = false;
         if (mService == null) {
-            return;
+            return false;
         }
-        if (preferred) {
+        if (enabled) {
             if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+                isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
+
+        return isEnabled;
     }
 
     @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java
index 35600b5..8a2c4f8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java
@@ -16,6 +16,8 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
@@ -102,20 +104,6 @@
     }
 
     @Override
-    public boolean connect(BluetoothDevice device) {
-        // Don't invoke method in service because settings is not allowed to connect this profile.
-        return false;
-    }
-
-    @Override
-    public boolean disconnect(BluetoothDevice device) {
-        if (mService == null) {
-            return false;
-        }
-        return mService.disconnect(device);
-    }
-
-    @Override
     public int getConnectionStatus(BluetoothDevice device) {
         if (mService == null) {
             return BluetoothProfile.STATE_DISCONNECTED;
@@ -124,21 +112,24 @@
     }
 
     @Override
-    public boolean isPreferred(BluetoothDevice device) {
+    public boolean isEnabled(BluetoothDevice device) {
         return getConnectionStatus(device) != BluetoothProfile.STATE_DISCONNECTED;
     }
 
     @Override
-    public int getPreferred(BluetoothDevice device) {
+    public int getConnectionPolicy(BluetoothDevice device) {
         return PREFERRED_VALUE;
     }
 
     @Override
-    public void setPreferred(BluetoothDevice device, boolean preferred) {
+    public boolean setEnabled(BluetoothDevice device, boolean enabled) {
+        boolean isEnabled = false;
         // if set preferred to false, then disconnect to the current device
-        if (!preferred) {
-            mService.disconnect(device);
+        if (!enabled) {
+            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
+
+        return isEnabled;
     }
 
     @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
index 588083e..3c24b4a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
@@ -101,20 +101,6 @@
         return true;
     }
 
-    public boolean connect(BluetoothDevice device) {
-        if (mService == null) {
-            return false;
-        }
-        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
-    }
-
-    public boolean disconnect(BluetoothDevice device) {
-        if (mService == null) {
-            return false;
-        }
-        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
-    }
-
     public int getConnectionStatus(BluetoothDevice device) {
         if (mService == null) {
             return BluetoothProfile.STATE_DISCONNECTED;
@@ -122,29 +108,37 @@
         return mService.getConnectionState(device);
     }
 
-    public boolean isPreferred(BluetoothDevice device) {
+    @Override
+    public boolean isEnabled(BluetoothDevice device) {
         if (mService == null) {
             return false;
         }
         return mService.getConnectionPolicy(device) != CONNECTION_POLICY_FORBIDDEN;
     }
 
-    public int getPreferred(BluetoothDevice device) {
+    @Override
+    public int getConnectionPolicy(BluetoothDevice device) {
         if (mService == null) {
             return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
 
-    public void setPreferred(BluetoothDevice device, boolean preferred) {
-        if (mService == null) return;
-        if (preferred) {
+    @Override
+    public boolean setEnabled(BluetoothDevice device, boolean enabled) {
+        boolean isEnabled = false;
+        if (mService == null) {
+            return false;
+        }
+        if (enabled) {
             if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+                isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
+
+        return isEnabled;
     }
 
     public String toString() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfile.java
index 4b0ca74..f609e43 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfile.java
@@ -35,17 +35,26 @@
      */
     boolean isAutoConnectable();
 
-    boolean connect(BluetoothDevice device);
-
-    boolean disconnect(BluetoothDevice device);
-
     int getConnectionStatus(BluetoothDevice device);
 
-    boolean isPreferred(BluetoothDevice device);
+    /**
+     * Return {@code true} if the profile is enabled, otherwise return {@code false}.
+     * @param device the device to query for enable status
+     */
+    boolean isEnabled(BluetoothDevice device);
 
-    int getPreferred(BluetoothDevice device);
+    /**
+     * Get the connection policy of the profile.
+     * @param device the device to query for enable status
+     */
+    int getConnectionPolicy(BluetoothDevice device);
 
-    void setPreferred(BluetoothDevice device, boolean preferred);
+    /**
+     * Enable the profile if {@code enabled} is {@code true}, otherwise disable profile.
+     * @param device the device to set profile status
+     * @param enabled {@code true} for enable profile, otherwise disable profile.
+     */
+    boolean setEnabled(BluetoothDevice device, boolean enabled);
 
     boolean isProfileReady();
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
index ae2acbe..c72efb7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
@@ -528,14 +528,14 @@
             (mMapProfile.getConnectionStatus(device) == BluetoothProfile.STATE_CONNECTED)) {
             profiles.add(mMapProfile);
             removedProfiles.remove(mMapProfile);
-            mMapProfile.setPreferred(device, true);
+            mMapProfile.setEnabled(device, true);
         }
 
         if ((mPbapProfile != null) &&
             (mPbapProfile.getConnectionStatus(device) == BluetoothProfile.STATE_CONNECTED)) {
             profiles.add(mPbapProfile);
             removedProfiles.remove(mPbapProfile);
-            mPbapProfile.setPreferred(device, true);
+            mPbapProfile.setEnabled(device, true);
         }
 
         if (mMapClientProfile != null) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java
index 7d121aa..19cb2f5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java
@@ -114,21 +114,6 @@
         return true;
     }
 
-    public boolean connect(BluetoothDevice device) {
-        if (mService == null) {
-            return false;
-        }
-        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
-    }
-
-    public boolean disconnect(BluetoothDevice device) {
-        if (mService == null) {
-            return false;
-        }
-
-        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
-    }
-
     public int getConnectionStatus(BluetoothDevice device) {
         if (mService == null) {
             return BluetoothProfile.STATE_DISCONNECTED;
@@ -136,31 +121,37 @@
         return mService.getConnectionState(device);
     }
 
-    public boolean isPreferred(BluetoothDevice device) {
+    @Override
+    public boolean isEnabled(BluetoothDevice device) {
         if (mService == null) {
             return false;
         }
         return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
-    public int getPreferred(BluetoothDevice device) {
+    @Override
+    public int getConnectionPolicy(BluetoothDevice device) {
         if (mService == null) {
             return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
 
-    public void setPreferred(BluetoothDevice device, boolean preferred) {
+    @Override
+    public boolean setEnabled(BluetoothDevice device, boolean enabled) {
+        boolean isEnabled = false;
         if (mService == null) {
-            return;
+            return false;
         }
-        if (preferred) {
+        if (enabled) {
             if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+                isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
+
+        return isEnabled;
     }
 
     public List<BluetoothDevice> getConnectedDevices() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java
index a96a4e7..75c1926 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java
@@ -16,6 +16,7 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
 import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
 
 import android.bluetooth.BluetoothAdapter;
@@ -112,19 +113,6 @@
         return true;
     }
 
-    public boolean connect(BluetoothDevice device) {
-        Log.d(TAG, "connect() - should not get called");
-        return false; // MAP never connects out
-    }
-
-    public boolean disconnect(BluetoothDevice device) {
-        if (mService == null) {
-            return false;
-        }
-
-        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
-    }
-
     public int getConnectionStatus(BluetoothDevice device) {
         if (mService == null) {
             return BluetoothProfile.STATE_DISCONNECTED;
@@ -132,31 +120,37 @@
         return mService.getConnectionState(device);
     }
 
-    public boolean isPreferred(BluetoothDevice device) {
+    @Override
+    public boolean isEnabled(BluetoothDevice device) {
         if (mService == null) {
             return false;
         }
         return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
-    public int getPreferred(BluetoothDevice device) {
+    @Override
+    public int getConnectionPolicy(BluetoothDevice device) {
         if (mService == null) {
             return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
 
-    public void setPreferred(BluetoothDevice device, boolean preferred) {
+    @Override
+    public boolean setEnabled(BluetoothDevice device, boolean enabled) {
+        boolean isEnabled = false;
         if (mService == null) {
-            return;
+            return false;
         }
-        if (preferred) {
-            if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        if (enabled) {
+            if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+                isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
+
+        return isEnabled;
     }
 
     public List<BluetoothDevice> getConnectedDevices() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java
index 8e3f3ed..5a6e6e8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java
@@ -40,27 +40,23 @@
         return false;
     }
 
-    public boolean connect(BluetoothDevice device) {
-        return false;
-    }
-
-    public boolean disconnect(BluetoothDevice device) {
-        return false;
-    }
-
     public int getConnectionStatus(BluetoothDevice device) {
         return BluetoothProfile.STATE_DISCONNECTED; // Settings app doesn't handle OPP
     }
 
-    public boolean isPreferred(BluetoothDevice device) {
+    @Override
+    public boolean isEnabled(BluetoothDevice device) {
         return false;
     }
 
-    public int getPreferred(BluetoothDevice device) {
+    @Override
+    public int getConnectionPolicy(BluetoothDevice device) {
         return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; // Settings app doesn't handle OPP
     }
 
-    public void setPreferred(BluetoothDevice device, boolean preferred) {
+    @Override
+    public boolean setEnabled(BluetoothDevice device, boolean enabled) {
+        return false;
     }
 
     public boolean isProfileReady() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java
index 6638592..767df35 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
@@ -83,22 +86,6 @@
         return false;
     }
 
-    public boolean connect(BluetoothDevice device) {
-        if (mService == null) return false;
-        List<BluetoothDevice> sinks = mService.getConnectedDevices();
-        if (sinks != null) {
-            for (BluetoothDevice sink : sinks) {
-                mService.disconnect(sink);
-            }
-        }
-        return mService.connect(device);
-    }
-
-    public boolean disconnect(BluetoothDevice device) {
-        if (mService == null) return false;
-        return mService.disconnect(device);
-    }
-
     public int getConnectionStatus(BluetoothDevice device) {
         if (mService == null) {
             return BluetoothProfile.STATE_DISCONNECTED;
@@ -106,16 +93,36 @@
         return mService.getConnectionState(device);
     }
 
-    public boolean isPreferred(BluetoothDevice device) {
+    @Override
+    public boolean isEnabled(BluetoothDevice device) {
         return true;
     }
 
-    public int getPreferred(BluetoothDevice device) {
+    @Override
+    public int getConnectionPolicy(BluetoothDevice device) {
         return -1;
     }
 
-    public void setPreferred(BluetoothDevice device, boolean preferred) {
-        // ignore: isPreferred is always true for PAN
+    @Override
+    public boolean setEnabled(BluetoothDevice device, boolean enabled) {
+        boolean isEnabled = false;
+        if (mService == null) {
+            return false;
+        }
+
+        if (enabled) {
+            final List<BluetoothDevice> sinks = mService.getConnectedDevices();
+            if (sinks != null) {
+                for (BluetoothDevice sink : sinks) {
+                    mService.setConnectionPolicy(sink, CONNECTION_POLICY_FORBIDDEN);
+                }
+            }
+            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+        } else {
+            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+        }
+
+        return isEnabled;
     }
 
     public String toString() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
index 56267fc..0d11293 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
@@ -126,23 +126,6 @@
                          BluetoothProfile.STATE_DISCONNECTING});
     }
 
-    public boolean connect(BluetoothDevice device) {
-        Log.d(TAG,"PBAPClientProfile got connect request");
-        if (mService == null) {
-            return false;
-        }
-        Log.d(TAG,"PBAPClientProfile attempting to connect to " + device.getAddress());
-        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
-    }
-
-    public boolean disconnect(BluetoothDevice device) {
-        Log.d(TAG,"PBAPClientProfile got disconnect request");
-        if (mService == null) {
-            return false;
-        }
-        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
-    }
-
     public int getConnectionStatus(BluetoothDevice device) {
         if (mService == null) {
             return BluetoothProfile.STATE_DISCONNECTED;
@@ -150,31 +133,37 @@
         return mService.getConnectionState(device);
     }
 
-    public boolean isPreferred(BluetoothDevice device) {
+    @Override
+    public boolean isEnabled(BluetoothDevice device) {
         if (mService == null) {
             return false;
         }
         return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
-    public int getPreferred(BluetoothDevice device) {
+    @Override
+    public int getConnectionPolicy(BluetoothDevice device) {
         if (mService == null) {
             return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
 
-    public void setPreferred(BluetoothDevice device, boolean preferred) {
+    @Override
+    public boolean setEnabled(BluetoothDevice device, boolean enabled) {
+        boolean isEnabled = false;
         if (mService == null) {
-            return;
+            return false;
         }
-        if (preferred) {
+        if (enabled) {
             if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+                isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
+
+        return isEnabled;
     }
 
     public String toString() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
index f7c0bf5..9e2e4a1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
@@ -91,34 +91,33 @@
         return false;
     }
 
-    public boolean connect(BluetoothDevice device) {
-        /*Can't connect from server */
-        return false;
-
-    }
-
-    public boolean disconnect(BluetoothDevice device) {
-        if (mService == null) {
-            return false;
-        }
-        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
-    }
-
     public int getConnectionStatus(BluetoothDevice device) {
         if (mService == null) return BluetoothProfile.STATE_DISCONNECTED;
         return mService.getConnectionState(device);
     }
 
-    public boolean isPreferred(BluetoothDevice device) {
+    @Override
+    public boolean isEnabled(BluetoothDevice device) {
         return false;
     }
 
-    public int getPreferred(BluetoothDevice device) {
+    @Override
+    public int getConnectionPolicy(BluetoothDevice device) {
         return -1;
     }
 
-    public void setPreferred(BluetoothDevice device, boolean preferred) {
-        // ignore: isPreferred is always true for PBAP
+    @Override
+    public boolean setEnabled(BluetoothDevice device, boolean enabled) {
+        boolean isEnabled = false;
+        if (mService == null) {
+            return false;
+        }
+
+        if (!enabled) {
+            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+        }
+
+        return isEnabled;
     }
 
     public String toString() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java
index 3022c5b..104f1d7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java
@@ -111,21 +111,6 @@
         return true;
     }
 
-    public boolean connect(BluetoothDevice device) {
-        if (mService == null) {
-            return false;
-        }
-        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
-    }
-
-    public boolean disconnect(BluetoothDevice device) {
-        if (mService == null) {
-            return false;
-        }
-
-        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
-    }
-
     public int getConnectionStatus(BluetoothDevice device) {
         if (mService == null) {
             return BluetoothProfile.STATE_DISCONNECTED;
@@ -133,31 +118,37 @@
         return mService.getConnectionState(device);
     }
 
-    public boolean isPreferred(BluetoothDevice device) {
+    @Override
+    public boolean isEnabled(BluetoothDevice device) {
         if (mService == null) {
             return false;
         }
         return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
-    public int getPreferred(BluetoothDevice device) {
+    @Override
+    public int getConnectionPolicy(BluetoothDevice device) {
         if (mService == null) {
             return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
 
-    public void setPreferred(BluetoothDevice device, boolean preferred) {
+    @Override
+    public boolean setEnabled(BluetoothDevice device, boolean enabled) {
+        boolean isEnabled = false;
         if (mService == null) {
-            return;
+            return false;
         }
-        if (preferred) {
+        if (enabled) {
             if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+                isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
+
+        return isEnabled;
     }
 
     public List<BluetoothDevice> getConnectedDevices() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
new file mode 100644
index 0000000..bc40903
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
@@ -0,0 +1,147 @@
+/*
+ * 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.settingslib.fuelgauge;
+
+import static android.os.BatteryManager.BATTERY_HEALTH_UNKNOWN;
+import static android.os.BatteryManager.BATTERY_STATUS_FULL;
+import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN;
+import static android.os.BatteryManager.EXTRA_HEALTH;
+import static android.os.BatteryManager.EXTRA_LEVEL;
+import static android.os.BatteryManager.EXTRA_MAX_CHARGING_CURRENT;
+import static android.os.BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE;
+import static android.os.BatteryManager.EXTRA_PLUGGED;
+import static android.os.BatteryManager.EXTRA_STATUS;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.BatteryManager;
+
+import com.android.settingslib.R;
+
+/**
+ * Stores and computes some battery information.
+ */
+public class BatteryStatus {
+    private static final int LOW_BATTERY_THRESHOLD = 20;
+    private static final int DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT = 5000000;
+
+    public static final int CHARGING_UNKNOWN = -1;
+    public static final int CHARGING_SLOWLY = 0;
+    public static final int CHARGING_REGULAR = 1;
+    public static final int CHARGING_FAST = 2;
+
+    public final int status;
+    public final int level;
+    public final int plugged;
+    public final int health;
+    public final int maxChargingWattage;
+
+    public BatteryStatus(int status, int level, int plugged, int health,
+            int maxChargingWattage) {
+        this.status = status;
+        this.level = level;
+        this.plugged = plugged;
+        this.health = health;
+        this.maxChargingWattage = maxChargingWattage;
+    }
+
+    public BatteryStatus(Intent batteryChangedIntent) {
+        status = batteryChangedIntent.getIntExtra(EXTRA_STATUS, BATTERY_STATUS_UNKNOWN);
+        plugged = batteryChangedIntent.getIntExtra(EXTRA_PLUGGED, 0);
+        level = batteryChangedIntent.getIntExtra(EXTRA_LEVEL, 0);
+        health = batteryChangedIntent.getIntExtra(EXTRA_HEALTH, BATTERY_HEALTH_UNKNOWN);
+
+        final int maxChargingMicroAmp = batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_CURRENT,
+                -1);
+        int maxChargingMicroVolt = batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_VOLTAGE, -1);
+
+        if (maxChargingMicroVolt <= 0) {
+            maxChargingMicroVolt = DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT;
+        }
+        if (maxChargingMicroAmp > 0) {
+            // Calculating muW = muA * muV / (10^6 mu^2 / mu); splitting up the divisor
+            // to maintain precision equally on both factors.
+            maxChargingWattage = (maxChargingMicroAmp / 1000)
+                    * (maxChargingMicroVolt / 1000);
+        } else {
+            maxChargingWattage = -1;
+        }
+    }
+
+    /**
+     * Determine whether the device is plugged in (USB, power, or wireless).
+     *
+     * @return true if the device is plugged in.
+     */
+    public boolean isPluggedIn() {
+        return plugged == BatteryManager.BATTERY_PLUGGED_AC
+                || plugged == BatteryManager.BATTERY_PLUGGED_USB
+                || plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS;
+    }
+
+    /**
+     * Determine whether the device is plugged in (USB, power).
+     *
+     * @return true if the device is plugged in wired (as opposed to wireless)
+     */
+    public boolean isPluggedInWired() {
+        return plugged == BatteryManager.BATTERY_PLUGGED_AC
+                || plugged == BatteryManager.BATTERY_PLUGGED_USB;
+    }
+
+    /**
+     * Whether or not the device is charged. Note that some devices never return 100% for
+     * battery level, so this allows either battery level or status to determine if the
+     * battery is charged.
+     *
+     * @return true if the device is charged
+     */
+    public boolean isCharged() {
+        return status == BATTERY_STATUS_FULL || level >= 100;
+    }
+
+    /**
+     * Whether battery is low and needs to be charged.
+     *
+     * @return true if battery is low
+     */
+    public boolean isBatteryLow() {
+        return level < LOW_BATTERY_THRESHOLD;
+    }
+
+    /**
+     * Return current chargin speed is fast, slow or normal.
+     *
+     * @return the charing speed
+     */
+    public final int getChargingSpeed(Context context) {
+        final int slowThreshold = context.getResources().getInteger(
+                R.integer.config_chargingSlowlyThreshold);
+        final int fastThreshold = context.getResources().getInteger(
+                R.integer.config_chargingFastThreshold);
+        return maxChargingWattage <= 0 ? CHARGING_UNKNOWN :
+                maxChargingWattage < slowThreshold ? CHARGING_SLOWLY :
+                        maxChargingWattage > fastThreshold ? CHARGING_FAST :
+                                CHARGING_REGULAR;
+    }
+
+    @Override
+    public String toString() {
+        return "BatteryStatus{status=" + status + ",level=" + level + ",plugged=" + plugged
+                + ",health=" + health + ",maxChargingWattage=" + maxChargingWattage + "}";
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaManager.java
index 12d054e..3a807c9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaManager.java
@@ -108,9 +108,9 @@
 
             Log.d(TAG, "addConnectableA2dpDevices() device : " + cachedDevice.getName()
                     + ", is connected : " + cachedDevice.isConnected()
-                    + ", is preferred : " + a2dpProfile.isPreferred(device));
+                    + ", is enabled : " + a2dpProfile.isEnabled(device));
 
-            if (a2dpProfile.isPreferred(device)
+            if (a2dpProfile.isEnabled(device)
                     && BluetoothDevice.BOND_BONDED == cachedDevice.getBondState()) {
                 addMediaDevice(cachedDevice);
             }
@@ -143,9 +143,9 @@
 
             Log.d(TAG, "addConnectableHearingAidDevices() device : " + cachedDevice.getName()
                     + ", is connected : " + cachedDevice.isConnected()
-                    + ", is preferred : " + hapProfile.isPreferred(device));
+                    + ", is enabled : " + hapProfile.isEnabled(device));
 
-            if (hapProfile.isPreferred(device)
+            if (hapProfile.isEnabled(device)
                     && BluetoothDevice.BOND_BONDED == cachedDevice.getBondState()) {
                 addMediaDevice(cachedDevice);
             }
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
index d287f95..ba74139 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
@@ -16,9 +16,12 @@
 package com.android.settingslib.media;
 
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.graphics.drawable.Drawable;
 import android.media.MediaRoute2Info;
 import android.media.MediaRouter2Manager;
+import android.text.TextUtils;
+import android.util.Log;
 
 import com.android.settingslib.R;
 import com.android.settingslib.bluetooth.BluetoothUtils;
@@ -89,6 +92,31 @@
     }
 
     @Override
+    public String getClientPackageName() {
+        return mRouteInfo.getClientPackageName();
+    }
+
+    @Override
+    public String getClientAppLabel() {
+        final String packageName = mRouteInfo.getClientPackageName();
+        if (TextUtils.isEmpty(packageName)) {
+            Log.d(TAG, "Client package name is empty");
+            return mContext.getResources().getString(R.string.unknown);
+        }
+        try {
+            final PackageManager packageManager = mContext.getPackageManager();
+            final String appLabel = packageManager.getApplicationLabel(
+                    packageManager.getApplicationInfo(packageName, 0)).toString();
+            if (!TextUtils.isEmpty(appLabel)) {
+                return appLabel;
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.e(TAG, "unable to find " + packageName);
+        }
+        return mContext.getResources().getString(R.string.unknown);
+    }
+
+    @Override
     public void disconnect() {
         //TODO(b/144535188): disconnected last select device
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 50196d2..96d72e7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -235,6 +235,22 @@
         return mCurrentConnectedDevice;
     }
 
+    /**
+     * Find the active MediaDevice.
+     *
+     * @param type the media device type.
+     * @return MediaDevice list
+     */
+    public List<MediaDevice> getActiveMediaDevice(@MediaDevice.MediaDeviceType int type) {
+        final List<MediaDevice> devices = new ArrayList<>();
+        for (MediaDevice device : mMediaDevices) {
+            if (type == device.mType && device.getClientPackageName() != null) {
+                devices.add(device);
+            }
+        }
+        return devices;
+    }
+
     private MediaDevice updateCurrentConnectedDevice() {
         for (MediaDevice device : mMediaDevices) {
             if (device instanceof  BluetoothMediaDevice) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index 44e70f4..bfb79c0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.wifi;
 
+import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLED;
+import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_PERMANENTLY_DISABLED;
+
 import android.annotation.IntDef;
 import android.annotation.MainThread;
 import android.annotation.Nullable;
@@ -1144,11 +1147,15 @@
                     mInfo != null ? mInfo.getRequestingPackageName() : null));
         } else { // not active
             if (mConfig != null && mConfig.hasNoInternetAccess()) {
-                int messageID = mConfig.getNetworkSelectionStatus().isNetworkPermanentlyDisabled()
+                int messageID =
+                        mConfig.getNetworkSelectionStatus().getNetworkSelectionStatus()
+                                == NETWORK_SELECTION_PERMANENTLY_DISABLED
                         ? R.string.wifi_no_internet_no_reconnect
                         : R.string.wifi_no_internet;
                 summary.append(mContext.getString(messageID));
-            } else if (mConfig != null && !mConfig.getNetworkSelectionStatus().isNetworkEnabled()) {
+            } else if (mConfig != null
+                    && (mConfig.getNetworkSelectionStatus().getNetworkSelectionStatus()
+                            != NETWORK_SELECTION_ENABLED)) {
                 WifiConfiguration.NetworkSelectionStatus networkStatus =
                         mConfig.getNetworkSelectionStatus();
                 switch (networkStatus.getNetworkSelectionDisableReason()) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
index d4e0510..d233649 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
@@ -16,9 +16,13 @@
 
 package com.android.settingslib.wifi;
 
+import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLED;
+import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.getMaxNetworkSelectionDisableReason;
+
 import android.content.Context;
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
 import android.net.wifi.WifiInfo;
 import android.os.SystemClock;
 
@@ -41,7 +45,9 @@
             summary.append(" f=" + Integer.toString(info.getFrequency()));
         }
         summary.append(" " + getVisibilityStatus(accessPoint));
-        if (config != null && !config.getNetworkSelectionStatus().isNetworkEnabled()) {
+        if (config != null
+                && (config.getNetworkSelectionStatus().getNetworkSelectionStatus()
+                        != NETWORK_SELECTION_ENABLED)) {
             summary.append(" (" + config.getNetworkSelectionStatus().getNetworkStatusString());
             if (config.getNetworkSelectionStatus().getDisableTime() > 0) {
                 long now = System.currentTimeMillis();
@@ -58,15 +64,13 @@
         }
 
         if (config != null) {
-            WifiConfiguration.NetworkSelectionStatus networkStatus =
-                    config.getNetworkSelectionStatus();
-            for (int index = WifiConfiguration.NetworkSelectionStatus.DISABLED_NONE;
-                    index < WifiConfiguration.NetworkSelectionStatus
-                            .NETWORK_SELECTION_DISABLED_MAX; index++) {
-                if (networkStatus.getDisableReasonCounter(index) != 0) {
-                    summary.append(" " + WifiConfiguration.NetworkSelectionStatus
-                            .getNetworkDisableReasonString(index) + "="
-                            + networkStatus.getDisableReasonCounter(index));
+            NetworkSelectionStatus networkStatus = config.getNetworkSelectionStatus();
+            for (int reason = 0; reason <= getMaxNetworkSelectionDisableReason(); reason++) {
+                if (networkStatus.getDisableReasonCounter(reason) != 0) {
+                    summary.append(" ")
+                            .append(NetworkSelectionStatus.getNetworkDisableReasonString(reason))
+                            .append("=")
+                            .append(networkStatus.getDisableReasonCounter(reason));
                 }
             }
         }
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
index 42f3cbb..bcabec8 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
@@ -465,6 +465,8 @@
         WifiConfiguration.NetworkSelectionStatus status =
                 mock(WifiConfiguration.NetworkSelectionStatus.class);
         when(configuration.getNetworkSelectionStatus()).thenReturn(status);
+        when(status.getNetworkSelectionStatus()).thenReturn(
+                WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_TEMPORARY_DISABLED);
         when(status.getNetworkSelectionDisableReason()).thenReturn(
                 WifiConfiguration.NetworkSelectionStatus.DISABLED_BY_WRONG_PASSWORD);
         AccessPoint ap = new AccessPoint(mContext, configuration);
@@ -1370,13 +1372,13 @@
     public void testOsuAccessPointSummary_showsProvisioningUpdates() {
         OsuProvider provider = createOsuProvider();
         Context spyContext = spy(new ContextWrapper(mContext));
-        AccessPoint osuAccessPoint = new AccessPoint(spyContext, provider,
-                mScanResults);
+        when(spyContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mMockWifiManager);
         Map<OsuProvider, PasspointConfiguration> osuProviderConfigMap = new HashMap<>();
         osuProviderConfigMap.put(provider, null);
-        when(spyContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mMockWifiManager);
         when(mMockWifiManager.getMatchingPasspointConfigsForOsuProviders(
                 Collections.singleton(provider))).thenReturn(osuProviderConfigMap);
+        AccessPoint osuAccessPoint = new AccessPoint(spyContext, provider,
+                mScanResults);
 
         osuAccessPoint.setListener(mMockAccessPointListener);
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
index 1182945..6307caf 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
@@ -31,6 +31,7 @@
 import android.content.res.Resources;
 import android.location.LocationManager;
 import android.media.AudioManager;
+import android.os.BatteryManager;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -122,12 +123,12 @@
     public void testGetDefaultStorageManagerDaysToRetain_storageManagerDaysToRetainUsesResources() {
         Resources resources = mock(Resources.class);
         when(resources.getInteger(
-                        eq(
-                                com.android
-                                        .internal
-                                        .R
-                                        .integer
-                                        .config_storageManagerDaystoRetainDefault)))
+                eq(
+                        com.android
+                                .internal
+                                .R
+                                .integer
+                                .config_storageManagerDaystoRetainDefault)))
                 .thenReturn(60);
         assertThat(Utils.getDefaultStorageManagerDaysToRetain(resources)).isEqualTo(60);
     }
@@ -147,7 +148,8 @@
         private static Map<String, Integer> map = new HashMap<>();
 
         @Implementation
-        public static boolean putIntForUser(ContentResolver cr, String name, int value, int userHandle) {
+        public static boolean putIntForUser(ContentResolver cr, String name, int value,
+                int userHandle) {
             map.put(name, value);
             return true;
         }
@@ -312,4 +314,33 @@
         assertThat(Utils.getCombinedServiceState(mServiceState)).isEqualTo(
                 ServiceState.STATE_OUT_OF_SERVICE);
     }
+
+    @Test
+    public void getBatteryStatus_statusIsFull_returnFullString() {
+        final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_LEVEL, 100);
+        final Resources resources = mContext.getResources();
+
+        assertThat(Utils.getBatteryStatus(mContext, intent)).isEqualTo(
+                resources.getString(R.string.battery_info_status_full));
+    }
+
+    @Test
+    public void getBatteryStatus_batteryLevelIs100_returnFullString() {
+        final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_STATUS,
+                BatteryManager.BATTERY_STATUS_FULL);
+        final Resources resources = mContext.getResources();
+
+        assertThat(Utils.getBatteryStatus(mContext, intent)).isEqualTo(
+                resources.getString(R.string.battery_info_status_full));
+    }
+
+    @Test
+    public void getBatteryStatus_batteryLevel99_returnChargingString() {
+        final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_STATUS,
+                BatteryManager.BATTERY_STATUS_CHARGING);
+        final Resources resources = mContext.getResources();
+
+        assertThat(Utils.getBatteryStatus(mContext, intent)).isEqualTo(
+                resources.getString(R.string.battery_info_status_charging));
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java
index ccb6646..9bb2f22d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java
@@ -16,12 +16,8 @@
 
 package com.android.settingslib.bluetooth;
 
-import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
-import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
-
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.bluetooth.BluetoothA2dpSink;
@@ -68,18 +64,6 @@
     }
 
     @Test
-    public void connect_shouldConnectBluetoothA2dpSink() {
-        mProfile.connect(mBluetoothDevice);
-        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED);
-    }
-
-    @Test
-    public void disconnect_shouldDisconnectBluetoothA2dpSink() {
-        mProfile.disconnect(mBluetoothDevice);
-        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN);
-    }
-
-    @Test
     public void getConnectionStatus_shouldReturnConnectionState() {
         when(mService.getConnectionState(mBluetoothDevice)).
                 thenReturn(BluetoothProfile.STATE_CONNECTED);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java
index 9180760..d121e0b 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java
@@ -16,12 +16,8 @@
 
 package com.android.settingslib.bluetooth;
 
-import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
-import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
-
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.bluetooth.BluetoothAdapter;
@@ -68,18 +64,6 @@
     }
 
     @Test
-    public void connect_shouldConnectBluetoothHeadsetClient() {
-        mProfile.connect(mBluetoothDevice);
-        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED);
-    }
-
-    @Test
-    public void disconnect_shouldDisconnectBluetoothHeadsetClient() {
-        mProfile.disconnect(mBluetoothDevice);
-        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN);
-    }
-
-    @Test
     public void getConnectionStatus_shouldReturnConnectionState() {
         when(mService.getConnectionState(mBluetoothDevice)).
                 thenReturn(BluetoothProfile.STATE_CONNECTED);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HidDeviceProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HidDeviceProfileTest.java
index f38af70..3665d9c 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HidDeviceProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HidDeviceProfileTest.java
@@ -18,7 +18,6 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.bluetooth.BluetoothAdapter;
@@ -65,17 +64,6 @@
     }
 
     @Test
-    public void connect_shouldReturnFalse() {
-        assertThat(mProfile.connect(mBluetoothDevice)).isFalse();
-    }
-
-    @Test
-    public void disconnect_shouldDisconnectBluetoothHidDevice() {
-        mProfile.disconnect(mBluetoothDevice);
-        verify(mService).disconnect(mBluetoothDevice);
-    }
-
-    @Test
     public void getConnectionStatus_shouldReturnConnectionState() {
         when(mService.getConnectionState(mBluetoothDevice)).
                 thenReturn(BluetoothProfile.STATE_CONNECTED);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java
index 1425c38..25031a6 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java
@@ -16,12 +16,8 @@
 
 package com.android.settingslib.bluetooth;
 
-import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
-import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
-
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.bluetooth.BluetoothAdapter;
@@ -68,18 +64,6 @@
     }
 
     @Test
-    public void connect_shouldConnectBluetoothMapClient() {
-        mProfile.connect(mBluetoothDevice);
-        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED);
-    }
-
-    @Test
-    public void disconnect_shouldDisconnectBluetoothMapClient() {
-        mProfile.disconnect(mBluetoothDevice);
-        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN);
-    }
-
-    @Test
     public void getConnectionStatus_shouldReturnConnectionState() {
         when(mService.getConnectionState(mBluetoothDevice)).
                 thenReturn(BluetoothProfile.STATE_CONNECTED);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java
index 15f560b..4305a3b 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java
@@ -16,12 +16,8 @@
 
 package com.android.settingslib.bluetooth;
 
-import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
-import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
-
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.bluetooth.BluetoothAdapter;
@@ -68,18 +64,6 @@
     }
 
     @Test
-    public void connect_shouldConnectBluetoothPbapClient() {
-        mProfile.connect(mBluetoothDevice);
-        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED);
-    }
-
-    @Test
-    public void disconnect_shouldDisconnectBluetoothPbapClient() {
-        mProfile.disconnect(mBluetoothDevice);
-        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN);
-    }
-
-    @Test
     public void getConnectionStatus_shouldReturnConnectionState() {
         when(mService.getConnectionState(mBluetoothDevice)).
                 thenReturn(BluetoothProfile.STATE_CONNECTED);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java
index 4f978a8..e460eaf 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java
@@ -16,12 +16,8 @@
 
 package com.android.settingslib.bluetooth;
 
-import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
-import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
-
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.bluetooth.BluetoothAdapter;
@@ -67,18 +63,6 @@
     }
 
     @Test
-    public void connect_shouldConnectBluetoothSap() {
-        mProfile.connect(mBluetoothDevice);
-        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED);
-    }
-
-    @Test
-    public void disconnect_shouldDisconnectBluetoothSap() {
-        mProfile.disconnect(mBluetoothDevice);
-        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN);
-    }
-
-    @Test
     public void getConnectionStatus_shouldReturnConnectionState() {
         when(mService.getConnectionState(mBluetoothDevice)).
                 thenReturn(BluetoothProfile.STATE_CONNECTED);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaManagerTest.java
index f27cef9..0ee5ea8 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaManagerTest.java
@@ -96,7 +96,7 @@
         when(mA2dpProfile.getConnectableDevices()).thenReturn(devices);
         when(mCachedDeviceManager.findDevice(bluetoothDevice)).thenReturn(cachedDevice);
         when(cachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
-        when(mA2dpProfile.isPreferred(bluetoothDevice)).thenReturn(true);
+        when(mA2dpProfile.isEnabled(bluetoothDevice)).thenReturn(true);
 
         assertThat(mMediaManager.mMediaDevices).isEmpty();
         mMediaManager.startScan();
@@ -113,7 +113,7 @@
         when(mA2dpProfile.getConnectableDevices()).thenReturn(devices);
         when(mCachedDeviceManager.findDevice(bluetoothDevice)).thenReturn(cachedDevice);
         when(cachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
-        when(mA2dpProfile.isPreferred(bluetoothDevice)).thenReturn(true);
+        when(mA2dpProfile.isEnabled(bluetoothDevice)).thenReturn(true);
 
         assertThat(mMediaManager.mMediaDevices).isEmpty();
         mMediaManager.startScan();
@@ -141,7 +141,7 @@
         when(mHapProfile.getConnectableDevices()).thenReturn(devices);
         when(mCachedDeviceManager.findDevice(bluetoothDevice)).thenReturn(cachedDevice);
         when(cachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
-        when(mHapProfile.isPreferred(bluetoothDevice)).thenReturn(true);
+        when(mHapProfile.isEnabled(bluetoothDevice)).thenReturn(true);
 
         assertThat(mMediaManager.mMediaDevices).isEmpty();
         mMediaManager.startScan();
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaDeviceTest.java
index c9db0d1..2e304dc 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaDeviceTest.java
@@ -22,6 +22,9 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageStats;
 import android.media.MediaRoute2Info;
 import android.media.MediaRouter2Manager;
 
@@ -34,11 +37,14 @@
 import org.mockito.MockitoAnnotations;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.shadows.ShadowPackageManager;
 
 @RunWith(RobolectricTestRunner.class)
 public class InfoMediaDeviceTest {
 
     private static final String TEST_PACKAGE_NAME = "com.test.packagename";
+    private static final String TEST_PACKAGE_NAME2 = "com.test.packagename2";
     private static final String TEST_ID = "test_id";
     private static final String TEST_NAME = "test_name";
 
@@ -50,11 +56,24 @@
 
     private Context mContext;
     private InfoMediaDevice mInfoMediaDevice;
+    private ShadowPackageManager mShadowPackageManager;
+    private ApplicationInfo mAppInfo;
+    private PackageInfo mPackageInfo;
+    private PackageStats mPackageStats;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mContext = RuntimeEnvironment.application;
+        mShadowPackageManager = Shadows.shadowOf(mContext.getPackageManager());
+        mAppInfo = new ApplicationInfo();
+        mAppInfo.flags = ApplicationInfo.FLAG_INSTALLED;
+        mAppInfo.packageName = TEST_PACKAGE_NAME;
+        mAppInfo.name = TEST_NAME;
+        mPackageInfo = new PackageInfo();
+        mPackageInfo.packageName = TEST_PACKAGE_NAME;
+        mPackageInfo.applicationInfo = mAppInfo;
+        mPackageStats = new PackageStats(TEST_PACKAGE_NAME);
 
         mInfoMediaDevice = new InfoMediaDevice(mContext, mRouterManager, mRouteInfo,
                 TEST_PACKAGE_NAME);
@@ -95,4 +114,32 @@
 
         verify(mRouterManager).selectRoute(TEST_PACKAGE_NAME, mRouteInfo);
     }
+
+    @Test
+    public void getClientPackageName_returnPackageName() {
+        when(mRouteInfo.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+
+        assertThat(mInfoMediaDevice.getClientPackageName()).isEqualTo(TEST_PACKAGE_NAME);
+    }
+
+    @Test
+    public void getClientAppLabel_matchedPackageName_returnLabel() {
+        when(mRouteInfo.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+
+        assertThat(mInfoMediaDevice.getClientAppLabel()).isEqualTo(
+                mContext.getResources().getString(R.string.unknown));
+
+        mShadowPackageManager.addPackage(mPackageInfo, mPackageStats);
+
+        assertThat(mInfoMediaDevice.getClientAppLabel()).isEqualTo(TEST_NAME);
+    }
+
+    @Test
+    public void getClientAppLabel_noMatchedPackageName_returnDefault() {
+        mShadowPackageManager.addPackage(mPackageInfo, mPackageStats);
+        when(mRouteInfo.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME2);
+
+        assertThat(mInfoMediaDevice.getClientAppLabel()).isEqualTo(
+                mContext.getResources().getString(R.string.unknown));
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
index c780a64..15aaa82 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
@@ -384,4 +384,38 @@
 
         verify(mCallback).onDeviceAttributesChanged();
     }
+
+    @Test
+    public void getActiveMediaDevice_checkList() {
+        final List<MediaDevice> devices = new ArrayList<>();
+        final MediaDevice device1 = mock(MediaDevice.class);
+        final MediaDevice device2 = mock(MediaDevice.class);
+        final MediaDevice device3 = mock(MediaDevice.class);
+        device1.mType = MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE;
+        device2.mType = MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE;
+        device3.mType = MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE;
+        when(device1.getClientPackageName()).thenReturn(TEST_DEVICE_ID_1);
+        when(device2.getClientPackageName()).thenReturn(TEST_DEVICE_ID_2);
+        when(device3.getClientPackageName()).thenReturn(TEST_DEVICE_ID_3);
+        when(device1.getId()).thenReturn(TEST_DEVICE_ID_1);
+        when(device2.getId()).thenReturn(TEST_DEVICE_ID_2);
+        when(device3.getId()).thenReturn(TEST_DEVICE_ID_3);
+        devices.add(device1);
+        devices.add(device2);
+        devices.add(device3);
+        mLocalMediaManager.registerCallback(mCallback);
+        mLocalMediaManager.mMediaDeviceCallback.onDeviceListAdded(devices);
+
+        List<MediaDevice> activeDevices = mLocalMediaManager.getActiveMediaDevice(
+                MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE);
+        assertThat(activeDevices).containsExactly(device1);
+
+        activeDevices = mLocalMediaManager.getActiveMediaDevice(
+                MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE);
+        assertThat(activeDevices).containsExactly(device2);
+
+        activeDevices = mLocalMediaManager.getActiveMediaDevice(
+                MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE);
+        assertThat(activeDevices).containsExactly(device3);
+    }
 }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 9934e59..cd62420 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -425,7 +425,8 @@
             }
             newState = oldState;
         } else {
-            newState = new Setting(name, value, makeDefault, packageName, tag);
+            newState = new Setting(name, value, makeDefault, packageName, tag,
+                    forceNonSystemPackage);
             mSettings.put(name, newState);
         }
 
@@ -1173,11 +1174,15 @@
 
         public Setting(String name, String value, boolean makeDefault, String packageName,
                 String tag) {
+            this(name, value, makeDefault, packageName, tag, false);
+        }
+
+        Setting(String name, String value, boolean makeDefault, String packageName,
+                String tag, boolean forceNonSystemPackage) {
             this.name = name;
             // overrideableByRestore = true as the first initialization isn't considered a
             // modification.
-            update(value, makeDefault, packageName, tag, false,
-                    /* overrideableByRestore */ true);
+            update(value, makeDefault, packageName, tag, forceNonSystemPackage, true);
         }
 
         public Setting(String name, String value, String defaultValue,
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java
index d67a9bc..8ff595b 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java
@@ -33,7 +33,6 @@
 import android.provider.Settings;
 import android.util.Log;
 
-import org.junit.Ignore;
 import org.junit.Test;
 
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -692,125 +691,4 @@
             cursor.close();
         }
     }
-
-    @Test
-    @Ignore("b/140250974")
-    public void testLocationModeChanges_viaFrontEndApi() throws Exception {
-        setStringViaFrontEndApiSetting(
-                SETTING_TYPE_SECURE,
-                Settings.Secure.LOCATION_MODE,
-                String.valueOf(Settings.Secure.LOCATION_MODE_OFF),
-                UserHandle.USER_SYSTEM);
-        assertEquals(
-                "Wrong location providers",
-                "",
-                getStringViaFrontEndApiSetting(
-                        SETTING_TYPE_SECURE,
-                        Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
-                        UserHandle.USER_SYSTEM));
-
-        setStringViaFrontEndApiSetting(
-                SETTING_TYPE_SECURE,
-                Settings.Secure.LOCATION_MODE,
-                String.valueOf(Settings.Secure.LOCATION_MODE_BATTERY_SAVING),
-                UserHandle.USER_SYSTEM);
-        assertEquals(
-                "Wrong location providers",
-                "network",
-                getStringViaFrontEndApiSetting(
-                        SETTING_TYPE_SECURE,
-                        Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
-                        UserHandle.USER_SYSTEM));
-
-        setStringViaFrontEndApiSetting(
-                SETTING_TYPE_SECURE,
-                Settings.Secure.LOCATION_MODE,
-                String.valueOf(Settings.Secure.LOCATION_MODE_HIGH_ACCURACY),
-                UserHandle.USER_SYSTEM);
-        assertEquals(
-                "Wrong location providers",
-                "gps,network",
-                getStringViaFrontEndApiSetting(
-                        SETTING_TYPE_SECURE,
-                        Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
-                        UserHandle.USER_SYSTEM));
-    }
-
-    @Test
-    @Ignore("b/140250974")
-    public void testLocationProvidersAllowed_disableProviders() throws Exception {
-        setStringViaFrontEndApiSetting(
-                SETTING_TYPE_SECURE,
-                Settings.Secure.LOCATION_MODE,
-                String.valueOf(Settings.Secure.LOCATION_MODE_HIGH_ACCURACY),
-                UserHandle.USER_SYSTEM);
-
-        // Disable providers that were enabled
-        updateStringViaProviderApiSetting(
-                SETTING_TYPE_SECURE,
-                Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
-                "-gps,-network");
-        assertEquals(
-                "Wrong location providers",
-                "",
-                queryStringViaProviderApi(
-                        SETTING_TYPE_SECURE, Settings.Secure.LOCATION_PROVIDERS_ALLOWED));
-
-        // Disable a provider that was not enabled
-        updateStringViaProviderApiSetting(
-                SETTING_TYPE_SECURE,
-                Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
-                "-test");
-        assertEquals(
-                "Wrong location providers",
-                "",
-                queryStringViaProviderApi(
-                        SETTING_TYPE_SECURE, Settings.Secure.LOCATION_PROVIDERS_ALLOWED));
-    }
-
-    @Test
-    @Ignore("b/140250974")
-    public void testLocationProvidersAllowed_enableAndDisable() throws Exception {
-        setStringViaFrontEndApiSetting(
-                SETTING_TYPE_SECURE,
-                Settings.Secure.LOCATION_MODE,
-                String.valueOf(Settings.Secure.LOCATION_MODE_OFF),
-                UserHandle.USER_SYSTEM);
-
-        updateStringViaProviderApiSetting(
-                SETTING_TYPE_SECURE,
-                Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
-                "+gps,+network,+test");
-        updateStringViaProviderApiSetting(
-                SETTING_TYPE_SECURE, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "-test");
-
-        assertEquals(
-                "Wrong location providers",
-                "gps,network",
-                queryStringViaProviderApi(
-                        SETTING_TYPE_SECURE, Settings.Secure.LOCATION_PROVIDERS_ALLOWED));
-    }
-
-    @Test
-    @Ignore("b/140250974")
-    public void testLocationProvidersAllowedLocked_invalidInput() throws Exception {
-        setStringViaFrontEndApiSetting(
-                SETTING_TYPE_SECURE,
-                Settings.Secure.LOCATION_MODE,
-                String.valueOf(Settings.Secure.LOCATION_MODE_OFF),
-                UserHandle.USER_SYSTEM);
-
-        // update providers with a invalid string
-        updateStringViaProviderApiSetting(
-                SETTING_TYPE_SECURE,
-                Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
-                "+gps, invalid-string");
-
-        // Verifies providers list does not change
-        assertEquals(
-                "Wrong location providers",
-                "",
-                queryStringViaProviderApi(
-                        SETTING_TYPE_SECURE, Settings.Secure.LOCATION_PROVIDERS_ALLOWED));
-    }
 }
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 6a89b71..1d679c7 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -223,6 +223,11 @@
     <!-- permissions required for CTS test - PhoneStateListenerTest -->
     <uses-permission android:name="android.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH" />
 
+    <!-- Permissions required for ganting and logging -->
+    <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE"/>
+    <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG"/>
+    <uses-permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG"/>
+
     <!-- Permission required for CTS test - UiModeManagerTest -->
     <uses-permission android:name="android.permission.ENTER_CAR_MODE_PRIORITIZED"/>
 
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 139a8c3..1fe967b 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -645,7 +645,6 @@
         <activity android:name=".controls.management.ControlsProviderSelectorActivity"
                   android:label="Controls Providers"
                   android:theme="@style/Theme.ControlsManagement"
-                  android:exported="true"
                   android:showForAllUsers="true"
                   android:excludeFromRecents="true"
                   android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
diff --git a/packages/SystemUI/res-keyguard/values/config.xml b/packages/SystemUI/res-keyguard/values/config.xml
index bde6ed5..8d9d6ee 100644
--- a/packages/SystemUI/res-keyguard/values/config.xml
+++ b/packages/SystemUI/res-keyguard/values/config.xml
@@ -22,10 +22,4 @@
 
     <!-- Allow the menu hard key to be disabled in LockScreen on some devices [DO NOT TRANSLATE] -->
     <bool name="config_disableMenuKeyInLockScreen">false</bool>
-
-    <!-- Threshold in micro watts below which a charger is rated as "slow"; 1A @ 5V -->
-    <integer name="config_chargingSlowlyThreshold">5000000</integer>
-
-    <!-- Threshold in micro watts above which a charger is rated as "fast"; 1.5A @ 5V  -->
-    <integer name="config_chargingFastThreshold">7500000</integer>
 </resources>
diff --git a/packages/SystemUI/res/drawable/ic_device_unknown_gm2_24px.xml b/packages/SystemUI/res/drawable/ic_device_unknown_gm2_24px.xml
new file mode 100644
index 0000000..24e0635
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_device_unknown_gm2_24px.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M12,2l-5.5,9h11L12,2zM12,5.84L13.93,9h-3.87L12,5.84zM17.5,13c-2.49,0 -4.5,2.01 -4.5,4.5s2.01,4.5 4.5,4.5 4.5,-2.01 4.5,-4.5 -2.01,-4.5 -4.5,-4.5zM17.5,20c-1.38,0 -2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5 2.5,1.12 2.5,2.5 -1.12,2.5 -2.5,2.5zM3,21.5h8v-8L3,13.5v8zM5,15.5h4v4L5,19.5v-4z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_touch.xml b/packages/SystemUI/res/drawable/ic_touch.xml
new file mode 100644
index 0000000..4f6698d
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_touch.xml
@@ -0,0 +1,26 @@
+<!--
+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.
+-->
+<!-- maybe need android:fillType="evenOdd" -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M9,7.5V11.24C7.79,10.43 7,9.06 7,7.5C7,5.01 9.01,3 11.5,3C13.99,3 16,5.01 16,7.5C16,9.06 15.21,10.43 14,11.24V7.5C14,6.12 12.88,5 11.5,5C10.12,5 9,6.12 9,7.5ZM14.3,13.61L18.84,15.87C19.37,16.09 19.75,16.63 19.75,17.25C19.75,17.31 19.74,17.38 19.73,17.45L18.98,22.72C18.87,23.45 18.29,24 17.54,24H10.75C10.34,24 9.96,23.83 9.69,23.56L4.75,18.62L5.54,17.82C5.74,17.62 6.02,17.49 6.33,17.49C6.39,17.49 6.4411,17.4989 6.4922,17.5078C6.5178,17.5122 6.5433,17.5167 6.57,17.52L10,18.24V7.5C10,6.67 10.67,6 11.5,6C12.33,6 13,6.67 13,7.5V13.5H13.76C13.95,13.5 14.13,13.54 14.3,13.61Z"
+    />
+</vector>
diff --git a/packages/SystemUI/res/layout/auth_credential_password_view.xml b/packages/SystemUI/res/layout/auth_credential_password_view.xml
index a1c593f..b14bc7d 100644
--- a/packages/SystemUI/res/layout/auth_credential_password_view.xml
+++ b/packages/SystemUI/res/layout/auth_credential_password_view.xml
@@ -73,6 +73,7 @@
         android:layout_width="208dp"
         android:layout_height="wrap_content"
         android:layout_gravity="center_horizontal"
+        android:minHeight="48dp"
         android:gravity="center"
         android:inputType="textPassword"
         android:maxLength="500"
diff --git a/packages/SystemUI/res/layout/screen_record_dialog.xml b/packages/SystemUI/res/layout/screen_record_dialog.xml
new file mode 100644
index 0000000..df576d8
--- /dev/null
+++ b/packages/SystemUI/res/layout/screen_record_dialog.xml
@@ -0,0 +1,142 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2020 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:background="@drawable/rounded_bg_full">
+
+    <!-- Header -->
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical"
+        android:gravity="center"
+        android:padding="@dimen/screenrecord_dialog_padding">
+        <ImageView
+            android:layout_width="@dimen/screenrecord_logo_size"
+            android:layout_height="@dimen/screenrecord_logo_size"
+            android:src="@drawable/ic_screenrecord"
+            android:tint="@color/GM2_red_500"
+            android:layout_marginBottom="@dimen/screenrecord_dialog_padding"/>
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textAppearance="?android:attr/textAppearanceLarge"
+            android:text="@string/screenrecord_start_label"/>
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/screenrecord_description"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            android:paddingTop="@dimen/screenrecord_dialog_padding"
+            android:paddingBottom="@dimen/screenrecord_dialog_padding"/>
+
+        <!-- Options -->
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+            <ImageView
+                android:layout_width="@dimen/screenrecord_logo_size"
+                android:layout_height="@dimen/screenrecord_logo_size"
+                android:src="@drawable/ic_mic_26dp"
+                android:tint="@color/GM2_grey_700"
+                android:layout_gravity="center"
+                android:layout_weight="0"
+                android:layout_marginRight="@dimen/screenrecord_dialog_padding"/>
+            <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:orientation="vertical"
+                android:layout_weight="1">
+                <TextView
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    android:layout_gravity="center_vertical"
+                    android:text="@string/screenrecord_audio_label"
+                    android:textColor="?android:attr/textColorPrimary"
+                    android:textAppearance="?android:attr/textAppearanceMedium"/>
+                <TextView
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    android:id="@+id/audio_type"
+                    android:text="@string/screenrecord_mic_label"
+                    android:textAppearance="?android:attr/textAppearanceSmall"/>
+            </LinearLayout>
+            <Switch
+                android:layout_width="wrap_content"
+                android:layout_height="48dp"
+                android:layout_weight="0"
+                android:id="@+id/screenrecord_audio_switch"/>
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+            <ImageView
+                android:layout_width="@dimen/screenrecord_logo_size"
+                android:layout_height="@dimen/screenrecord_logo_size"
+                android:src="@drawable/ic_touch"
+                android:tint="@color/GM2_grey_700"
+                android:layout_gravity="center"
+                android:layout_marginRight="@dimen/screenrecord_dialog_padding"/>
+            <Switch
+                android:layout_width="match_parent"
+                android:layout_height="48dp"
+                android:id="@+id/screenrecord_taps_switch"
+                android:text="@string/screenrecord_taps_label"
+                android:textColor="?android:attr/textColorPrimary"
+                android:textAppearance="?android:attr/textAppearanceMedium"/>
+        </LinearLayout>
+    </LinearLayout>
+
+    <!-- hr -->
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="1dp"
+        android:background="@color/GM2_grey_300"/>
+
+    <!-- Buttons -->
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:padding="@dimen/screenrecord_dialog_padding">
+        <Button
+            android:id="@+id/button_cancel"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_weight="0"
+            android:layout_gravity="start"
+            android:text="@string/cancel"
+            style="@android:style/Widget.DeviceDefault.Button.Borderless.Colored"/>
+        <Space
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"/>
+        <Button
+            android:id="@+id/button_start"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_weight="0"
+            android:layout_gravity="end"
+            android:text="@string/screenrecord_start"
+            style="@android:style/Widget.DeviceDefault.Button.Colored"/>
+    </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 9c997e8..c4fa4e5 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1090,7 +1090,7 @@
 
     <!--  Blur radius on status bar window and power menu  -->
     <dimen name="min_window_blur_radius">1px</dimen>
-    <dimen name="max_window_blur_radius">100px</dimen>
+    <dimen name="max_window_blur_radius">250px</dimen>
 
     <!-- How much into a DisplayCutout's bounds we can go, on each side -->
     <dimen name="display_cutout_margin_consumption">0px</dimen>
@@ -1209,5 +1209,7 @@
 
     <dimen name="controls_card_margin">2dp</dimen>
 
-
+    <!-- Screen Record -->
+    <dimen name="screenrecord_dialog_padding">18dp</dimen>
+    <dimen name="screenrecord_logo_size">24dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 82cd5f7..b85b51e 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -217,15 +217,31 @@
         your organization</string>
 
     <!-- Notification title displayed for screen recording [CHAR LIMIT=50]-->
-    <string name="screenrecord_name">Screen Recording</string>
+    <string name="screenrecord_name">Screen Recorder</string>
     <!-- Description of the screen recording notification channel [CHAR LIMIT=NONE]-->
     <string name="screenrecord_channel_description">Ongoing notification for a screen record session</string>
-    <!-- Label for the button to begin screen recording [CHAR LIMIT=NONE]-->
-    <string name="screenrecord_start_label">Start Recording</string>
-    <!-- Label for the checkbox to enable microphone input during screen recording [CHAR LIMIT=NONE]-->
-    <string name="screenrecord_mic_label">Record voiceover</string>
+    <!-- Title for the screen prompting the user to begin recording their screen [CHAR LIMIT=NONE]-->
+    <string name="screenrecord_start_label">Start Recording?</string>
+    <!-- Message reminding the user that sensitive information may be captured during a screen recording [CHAR_LIMIT=NONE]-->
+    <string name="screenrecord_description">While recording, Android System can capture any sensitive information that\u2019s visible on your screen or played on your device. This includes passwords, payment info, photos, messages, and audio.</string>
+    <!-- Label for a switch to enable recording audio [CHAR LIMIT=NONE]-->
+    <string name="screenrecord_audio_label">Record audio</string>
+    <!-- Label for the option to record audio from the device [CHAR LIMIT=NONE]-->
+    <string name="screenrecord_device_audio_label">Device audio</string>
+    <!-- Description of what audio may be captured from the device [CHAR LIMIT=NONE]-->
+    <string name="screenrecord_device_audio_description">Sound from your device, like music, calls, and ringtones</string>
+    <!-- Label for the option to enable microphone input during screen recording [CHAR LIMIT=NONE]-->
+    <string name="screenrecord_mic_label">Microphone</string>
+    <!-- Label for an option to record audio from both device and microphone [CHAR LIMIT=NONE]-->
+    <string name="screenrecord_device_audio_and_mic_label">Device audio and microphone</string>
+    <!-- Button to start a screen recording [CHAR LIMIT=50]-->
+    <string name="screenrecord_start">Start</string>
+    <!-- Notification text displayed when we are recording the screen [CHAR LIMIT=100]-->
+    <string name="screenrecord_ongoing_screen_only">Recording screen</string>
+    <!-- Notification text displayed when we are recording both the screen and audio [CHAR LIMIT=100]-->
+    <string name="screenrecord_ongoing_screen_and_audio">Recording screen and audio</string>
     <!-- Label for the checkbox to enable showing location of touches during screen recording [CHAR LIMIT=NONE]-->
-    <string name="screenrecord_taps_label">Show taps</string>
+    <string name="screenrecord_taps_label">Show touches on screen</string>
     <!-- Label for notification that the user can tap to stop and save the screen recording [CHAR LIMIT=NONE] -->
     <string name="screenrecord_stop_text">Tap to stop</string>
     <!-- Label for notification action to stop and save the screen recording [CHAR LIMIT=35] -->
@@ -250,6 +266,8 @@
     <string name="screenrecord_delete_error">Error deleting screen recording</string>
     <!-- A toast message shown when the screen recording cannot be started due to insufficient permissions [CHAR LIMIT=NONE] -->
     <string name="screenrecord_permission_error">Failed to get permissions</string>
+    <!-- A toast message shown when the screen recording cannot be started due to a generic error [CHAR LIMIT=NONE] -->
+    <string name="screenrecord_start_error">Error starting screen recording</string>
 
     <!-- Title for the USB function chooser in UsbPreferenceActivity. [CHAR LIMIT=30] -->
     <string name="usb_preference_title">USB file transfer options</string>
@@ -1812,16 +1830,16 @@
     <string name="demote">Mark this notification as not a conversation</string>
 
     <!-- [CHAR LIMIT=100] Mark this conversation as a favorite -->
-    <string name="notification_conversation_favorite">Favorite</string>
+    <string name="notification_conversation_favorite">Mark as important</string>
 
     <!-- [CHAR LIMIT=100] Unmark this conversation as a favorite -->
-    <string name="notification_conversation_unfavorite">Unfavorite</string>
+    <string name="notification_conversation_unfavorite">Mark as unimportant</string>
 
     <!-- [CHAR LIMIT=100] Mute this conversation -->
-    <string name="notification_conversation_mute">Mute</string>
+    <string name="notification_conversation_mute">Silence</string>
 
     <!-- [CHAR LIMIT=100] Umute this conversation -->
-    <string name="notification_conversation_unmute">Unmute</string>
+    <string name="notification_conversation_unmute">Alerting</string>
 
     <!-- [CHAR LIMIT=100] Show notification as bubble -->
     <string name="notification_conversation_bubble">Show as bubble</string>
diff --git a/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java b/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java
index b2423b9..85724a9 100644
--- a/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java
+++ b/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java
@@ -27,7 +27,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.Log;
-import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
 import android.view.SurfaceHolder;
 import android.view.SurfaceView;
 import android.view.ViewGroup;
@@ -47,7 +47,6 @@
     private Handler mHandler;
     private IKeyguardClient mClient;
     private KeyguardSecurityCallback mKeyguardCallback;
-    private SurfaceControl.Transaction mTransaction;
 
     private final ServiceConnection mConnection = new ServiceConnection() {
         @Override
@@ -84,13 +83,13 @@
         }
 
         @Override
-        public void onSurfaceControlCreated(@Nullable SurfaceControl remoteSurfaceControl) {
+        public void onRemoteContentReady(
+                @Nullable SurfaceControlViewHost.SurfacePackage surfacePackage) {
             if (mHandler != null) {
                 mHandler.removeCallbacksAndMessages(null);
             }
-            if (remoteSurfaceControl != null) {
-                mTransaction.reparent(remoteSurfaceControl, mView.getSurfaceControl())
-                    .apply();
+            if (surfacePackage != null) {
+                mView.setChildSurfacePackage(surfacePackage);
             } else {
                 dismiss(KeyguardUpdateMonitor.getCurrentUser());
             }
@@ -138,11 +137,10 @@
 
     public AdminSecondaryLockScreenController(Context context, ViewGroup parent,
             KeyguardUpdateMonitor updateMonitor, KeyguardSecurityCallback callback,
-            Handler handler, SurfaceControl.Transaction transaction) {
+            Handler handler) {
         mContext = context;
         mHandler = handler;
         mParent = parent;
-        mTransaction = transaction;
         mUpdateMonitor = updateMonitor;
         mKeyguardCallback = callback;
         mView = new AdminSecurityView(mContext, mSurfaceHolderCallback);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 29c67ae..ba8a1a9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -39,7 +39,6 @@
 import android.util.TypedValue;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
-import android.view.SurfaceControl;
 import android.view.VelocityTracker;
 import android.view.View;
 import android.view.ViewConfiguration;
@@ -149,8 +148,7 @@
         mViewConfiguration = ViewConfiguration.get(context);
         mKeyguardStateController = Dependency.get(KeyguardStateController.class);
         mSecondaryLockScreenController = new AdminSecondaryLockScreenController(context, this,
-                mUpdateMonitor, mCallback, new Handler(Looper.myLooper()),
-                new SurfaceControl.Transaction());
+                mUpdateMonitor, mCallback, new Handler(Looper.myLooper()));
     }
 
     public void setSecurityCallback(SecurityCallback callback) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 6a04583..e5f6d3c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -21,15 +21,7 @@
 import static android.content.Intent.ACTION_USER_REMOVED;
 import static android.content.Intent.ACTION_USER_STOPPED;
 import static android.content.Intent.ACTION_USER_UNLOCKED;
-import static android.os.BatteryManager.BATTERY_HEALTH_UNKNOWN;
-import static android.os.BatteryManager.BATTERY_STATUS_FULL;
 import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN;
-import static android.os.BatteryManager.EXTRA_HEALTH;
-import static android.os.BatteryManager.EXTRA_LEVEL;
-import static android.os.BatteryManager.EXTRA_MAX_CHARGING_CURRENT;
-import static android.os.BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE;
-import static android.os.BatteryManager.EXTRA_PLUGGED;
-import static android.os.BatteryManager.EXTRA_STATUS;
 import static android.telephony.PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE;
 import static android.telephony.TelephonyManager.MODEM_COUNT_DUAL_MODEM;
 
@@ -67,7 +59,6 @@
 import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback;
 import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
 import android.media.AudioManager;
-import android.os.BatteryManager;
 import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.IRemoteCallback;
@@ -94,6 +85,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.settingslib.WirelessUtils;
+import com.android.settingslib.fuelgauge.BatteryStatus;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.DumpController;
 import com.android.systemui.Dumpable;
@@ -1059,29 +1051,9 @@
                         MSG_TIMEZONE_UPDATE, intent.getStringExtra("time-zone"));
                 mHandler.sendMessage(msg);
             } else if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
-                final int status = intent.getIntExtra(EXTRA_STATUS, BATTERY_STATUS_UNKNOWN);
-                final int plugged = intent.getIntExtra(EXTRA_PLUGGED, 0);
-                final int level = intent.getIntExtra(EXTRA_LEVEL, 0);
-                final int health = intent.getIntExtra(EXTRA_HEALTH, BATTERY_HEALTH_UNKNOWN);
 
-                final int maxChargingMicroAmp = intent.getIntExtra(EXTRA_MAX_CHARGING_CURRENT, -1);
-                int maxChargingMicroVolt = intent.getIntExtra(EXTRA_MAX_CHARGING_VOLTAGE, -1);
-                final int maxChargingMicroWatt;
-
-                if (maxChargingMicroVolt <= 0) {
-                    maxChargingMicroVolt = DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT;
-                }
-                if (maxChargingMicroAmp > 0) {
-                    // Calculating muW = muA * muV / (10^6 mu^2 / mu); splitting up the divisor
-                    // to maintain precision equally on both factors.
-                    maxChargingMicroWatt = (maxChargingMicroAmp / 1000)
-                            * (maxChargingMicroVolt / 1000);
-                } else {
-                    maxChargingMicroWatt = -1;
-                }
                 final Message msg = mHandler.obtainMessage(
-                        MSG_BATTERY_UPDATE, new BatteryStatus(status, level, plugged, health,
-                                maxChargingMicroWatt));
+                        MSG_BATTERY_UPDATE, new BatteryStatus(intent));
                 mHandler.sendMessage(msg);
             } else if (Intent.ACTION_SIM_STATE_CHANGED.equals(action)) {
                 SimData args = SimData.fromIntent(intent);
@@ -1323,82 +1295,6 @@
         }
     }
 
-    public static class BatteryStatus {
-        public static final int CHARGING_UNKNOWN = -1;
-        public static final int CHARGING_SLOWLY = 0;
-        public static final int CHARGING_REGULAR = 1;
-        public static final int CHARGING_FAST = 2;
-
-        public final int status;
-        public final int level;
-        public final int plugged;
-        public final int health;
-        public final int maxChargingWattage;
-
-        public BatteryStatus(int status, int level, int plugged, int health,
-                int maxChargingWattage) {
-            this.status = status;
-            this.level = level;
-            this.plugged = plugged;
-            this.health = health;
-            this.maxChargingWattage = maxChargingWattage;
-        }
-
-        /**
-         * Determine whether the device is plugged in (USB, power, or wireless).
-         *
-         * @return true if the device is plugged in.
-         */
-        public boolean isPluggedIn() {
-            return plugged == BatteryManager.BATTERY_PLUGGED_AC
-                    || plugged == BatteryManager.BATTERY_PLUGGED_USB
-                    || plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS;
-        }
-
-        /**
-         * Determine whether the device is plugged in (USB, power).
-         *
-         * @return true if the device is plugged in wired (as opposed to wireless)
-         */
-        public boolean isPluggedInWired() {
-            return plugged == BatteryManager.BATTERY_PLUGGED_AC
-                    || plugged == BatteryManager.BATTERY_PLUGGED_USB;
-        }
-
-        /**
-         * Whether or not the device is charged. Note that some devices never return 100% for
-         * battery level, so this allows either battery level or status to determine if the
-         * battery is charged.
-         *
-         * @return true if the device is charged
-         */
-        public boolean isCharged() {
-            return status == BATTERY_STATUS_FULL || level >= 100;
-        }
-
-        /**
-         * Whether battery is low and needs to be charged.
-         *
-         * @return true if battery is low
-         */
-        public boolean isBatteryLow() {
-            return level < LOW_BATTERY_THRESHOLD;
-        }
-
-        public final int getChargingSpeed(int slowThreshold, int fastThreshold) {
-            return maxChargingWattage <= 0 ? CHARGING_UNKNOWN :
-                    maxChargingWattage < slowThreshold ? CHARGING_SLOWLY :
-                            maxChargingWattage > fastThreshold ? CHARGING_FAST :
-                                    CHARGING_REGULAR;
-        }
-
-        @Override
-        public String toString() {
-            return "BatteryStatus{status=" + status + ",level=" + level + ",plugged=" + plugged
-                    + ",health=" + health + ",maxChargingWattage=" + maxChargingWattage + "}";
-        }
-    }
-
     public static class StrongAuthTracker extends LockPatternUtils.StrongAuthTracker {
         private final Consumer<Integer> mStrongAuthRequiredChangedCallback;
 
@@ -2640,9 +2536,9 @@
      */
     private boolean refreshSimState(int subId, int slotId) {
         final TelephonyManager tele =
-            (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+                (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
         int state = (tele != null) ?
-            tele.getSimState(slotId) : TelephonyManager.SIM_STATE_UNKNOWN;
+                tele.getSimState(slotId) : TelephonyManager.SIM_STATE_UNKNOWN;
         SimData data = mSimDatas.get(subId);
         final boolean changed;
         if (data == null) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 8e87b7a..49f72a9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -23,6 +23,7 @@
 import android.telephony.TelephonyManager;
 import android.view.WindowManagerPolicyConstants;
 
+import com.android.settingslib.fuelgauge.BatteryStatus;
 import com.android.systemui.statusbar.KeyguardIndicationController;
 
 import java.util.TimeZone;
@@ -42,7 +43,7 @@
      *
      * @param status current battery status
      */
-    public void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) { }
+    public void onRefreshBatteryInfo(BatteryStatus status) { }
 
     /**
      * Called once per minute or when the time changes.
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index b8d32ae..8a492a8 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -87,7 +87,7 @@
     @VisibleForTesting @Nullable AuthBiometricView mBiometricView;
     @VisibleForTesting @Nullable AuthCredentialView mCredentialView;
 
-    private final ImageView mBackgroundView;
+    @VisibleForTesting final ImageView mBackgroundView;
     @VisibleForTesting final ScrollView mBiometricScrollView;
     private final View mPanelView;
 
@@ -333,6 +333,12 @@
                 throw new IllegalStateException("Unknown credential type: " + credentialType);
         }
 
+        // The background is used for detecting taps / cancelling authentication. Since the
+        // credential view is full-screen and should not be canceled from background taps,
+        // disable it.
+        mBackgroundView.setOnClickListener(null);
+        mBackgroundView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
+
         mCredentialView.setContainerView(this);
         mCredentialView.setEffectiveUserId(mEffectiveUserId);
         mCredentialView.setCredentialType(credentialType);
@@ -583,11 +589,13 @@
      * @return
      */
     public static WindowManager.LayoutParams getLayoutParams(IBinder windowToken) {
+        final int windowFlags = WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
+                | WindowManager.LayoutParams.FLAG_SECURE;
         final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
                 ViewGroup.LayoutParams.MATCH_PARENT,
                 ViewGroup.LayoutParams.MATCH_PARENT,
                 WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
-                WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
+                windowFlags,
                 PixelFormat.TRANSLUCENT);
         lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
         lp.setTitle("BiometricPrompt");
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 7c07c9d..2f1e4b4 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -970,7 +970,7 @@
      * The cancellation of summaries with children associated with bubbles are also handled in this
      * method. User-cancelled summaries are tracked by {@link BubbleData#addSummaryToSuppress}.
      *
-     * @return true if we want to intercept the dismissal of the entry, else false
+     * @return true if we want to intercept the dismissal of the entry, else false.
      */
     public boolean shouldInterceptDismissal(NotificationEntry entry, int dismissReason) {
         if (entry == null) {
@@ -1010,7 +1010,8 @@
         // The bubble notification sticks around in the data as long as the bubble is
         // not dismissed and the app hasn't cancelled the notification.
         Bubble bubble = mBubbleData.getBubbleWithKey(key);
-        boolean bubbleExtended = entry != null && entry.isBubble() && userRemovedNotif;
+        boolean bubbleExtended = entry != null && entry.isBubble()
+                && (userRemovedNotif || isUserCreatedBubble(bubble.getKey()));
         if (bubbleExtended) {
             bubble.setSuppressNotification(true);
             bubble.setShowDot(false /* show */, true /* animate */);
@@ -1019,8 +1020,7 @@
                         + ".shouldInterceptDismissal");
             }
             return true;
-        } else if (!userRemovedNotif && entry != null
-                && !isUserCreatedBubble(bubble.getKey())) {
+        } else if (!userRemovedNotif && entry != null) {
             // This wasn't a user removal so we should remove the bubble as well
             mBubbleData.notificationEntryRemoved(entry, DISMISS_NOTIF_CANCEL);
             return false;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 6062a3d..20d19ec 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -533,10 +533,6 @@
         mBubbleContainer.addView(mOverflowBtn, 0,
                 new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
 
-        mOverflowBtn.setOnClickListener(v -> {
-            setSelectedBubble(null);
-        });
-
         TypedArray ta = mContext.obtainStyledAttributes(
                 new int[]{android.R.attr.colorBackgroundFloating});
         int bgColor = ta.getColor(0, Color.WHITE /* default */);
@@ -856,6 +852,10 @@
         updateBubbleZOrdersAndDotPosition(false /* animate */);
     }
 
+    void showOverflow() {
+        setSelectedBubble(null);
+    }
+
     /**
      * Changes the currently selected bubble. If the stack is already expanded, the newly selected
      * bubble will be shown immediately. This does not change the expanded state or change the
@@ -950,6 +950,10 @@
         }
         if (mIsExpanded) {
             if (isIntersecting(mBubbleContainer, x, y)) {
+                if (BubbleExperimentConfig.allowBubbleOverflow(mContext)
+                        && isIntersecting(mOverflowBtn, x, y)) {
+                    return mOverflowBtn;
+                }
                 // Could be tapping or dragging a bubble while expanded
                 for (int i = 0; i < getBubbleCount(); i++) {
                     BadgedImageView view = (BadgedImageView) mBubbleContainer.getChildAt(i);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
index fdeaf1f..5a9d44b 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
@@ -24,6 +24,7 @@
 import android.view.ViewConfiguration;
 
 import com.android.systemui.Dependency;
+import com.android.systemui.R;
 
 /**
  * Handles interpreting touches on a {@link BubbleStackView}. This includes expanding, collapsing,
@@ -109,6 +110,10 @@
         if (!(mTouchedView instanceof BadgedImageView)
                 && !(mTouchedView instanceof BubbleStackView)
                 && !(mTouchedView instanceof BubbleFlyoutView)) {
+
+            if (mTouchedView.getId() == R.id.bubble_overflow_button) {
+                mStack.showOverflow();
+            }
             // Not touching anything touchable, but we shouldn't collapse (e.g. touching edge
             // of expanded view).
             mStack.hideBubbleMenu();
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 a6f1d84..7de1557 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -19,9 +19,12 @@
 import android.app.PendingIntent
 import android.content.BroadcastReceiver
 import android.content.ComponentName
+import android.content.ContentResolver
 import android.content.Context
 import android.content.Intent
 import android.content.IntentFilter
+import android.database.ContentObserver
+import android.net.Uri
 import android.os.Environment
 import android.os.UserHandle
 import android.provider.Settings
@@ -30,6 +33,7 @@
 import android.util.ArrayMap
 import android.util.Log
 import com.android.internal.annotations.GuardedBy
+import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.DumpController
 import com.android.systemui.Dumpable
 import com.android.systemui.broadcast.BroadcastDispatcher
@@ -53,15 +57,16 @@
     private val uiController: ControlsUiController,
     private val bindingController: ControlsBindingController,
     private val listingController: ControlsListingController,
-    broadcastDispatcher: BroadcastDispatcher,
+    private val 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
+        internal const val CONTROLS_AVAILABLE = "systemui.controls_available"
+        internal val URI = Settings.Secure.getUriFor(CONTROLS_AVAILABLE)
+        private const val USER_CHANGE_RETRY_DELAY = 500L // ms
     }
 
     // Map of map: ComponentName -> (String -> ControlInfo).
@@ -69,9 +74,11 @@
     @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
+    private var userChanging: Boolean = true
+
+    private val contentResolver: ContentResolver
+        get() = context.contentResolver
+    override var available = Settings.Secure.getInt(contentResolver, CONTROLS_AVAILABLE, 0) != 0
         private set
 
     private var currentUser = context.user
@@ -95,8 +102,8 @@
         val fileName = Environment.buildPath(
                 userContext.filesDir, ControlsFavoritePersistenceWrapper.FILE_NAME)
         persistenceWrapper.changeFile(fileName)
-        available = Settings.Secure.getIntForUser(
-                context.contentResolver, CONTROLS_AVAILABLE, 0) != 0
+        available = Settings.Secure.getIntForUser(contentResolver, CONTROLS_AVAILABLE,
+                /* default */ 0, newUser.identifier) != 0
         synchronized(currentFavorites) {
             currentFavorites.clear()
         }
@@ -123,6 +130,25 @@
         }
     }
 
+    @VisibleForTesting
+    internal val settingObserver = object : ContentObserver(null) {
+        override fun onChange(selfChange: Boolean, uri: Uri, userId: Int) {
+            // Do not listen to changes in the middle of user change, those will be read by the
+            // user-switch receiver.
+            if (userChanging || userId != currentUserId) {
+                return
+            }
+            available = Settings.Secure.getIntForUser(contentResolver, CONTROLS_AVAILABLE,
+                    /* default */ 0, currentUserId) != 0
+            synchronized(currentFavorites) {
+                currentFavorites.clear()
+            }
+            if (available) {
+                loadFavorites()
+            }
+        }
+    }
+
     init {
         dumpController.registerDumpable(this)
         if (available) {
@@ -135,6 +161,7 @@
                 executor,
                 UserHandle.ALL
         )
+        contentResolver.registerContentObserver(URI, false, settingObserver, UserHandle.USER_ALL)
     }
 
     private fun confirmAvailability(): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
index fad2d94..88b19b5 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
@@ -34,14 +34,17 @@
 import android.widget.TextView
 
 import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.R
 
 const val MIN_LEVEL = 0
 const val MAX_LEVEL = 10000
+private const val UPDATE_DELAY_IN_MILLIS = 2000L
 
 class ControlViewHolder(
     val layout: ViewGroup,
-    val controlsController: ControlsController
+    val controlsController: ControlsController,
+    val uiExecutor: DelayableExecutor
 ) {
     val icon: ImageView = layout.requireViewById(R.id.icon)
     val status: TextView = layout.requireViewById(R.id.status)
@@ -52,6 +55,7 @@
     val clipLayer: ClipDrawable
     val gd: GradientDrawable
     lateinit var cws: ControlWithState
+    var cancelUpdate: Runnable? = null
 
     init {
         val ld = layout.getBackground() as LayerDrawable
@@ -63,6 +67,8 @@
     fun bindData(cws: ControlWithState) {
         this.cws = cws
 
+        cancelUpdate?.run()
+
         val (status, template) = cws.control?.let {
             title.setText(it.getTitle())
             subtitle.setText(it.getSubtitle())
@@ -86,6 +92,27 @@
         findBehavior(status, template).apply(this, cws)
     }
 
+    fun actionResponse(@ControlAction.ResponseResult response: Int) {
+        val text = when (response) {
+            ControlAction.RESPONSE_OK -> "Success"
+            ControlAction.RESPONSE_FAIL -> "Error"
+            else -> ""
+        }
+
+        if (!text.isEmpty()) {
+            val previousText = status.getText()
+            val previousTextExtra = statusExtra.getText()
+
+            cancelUpdate = uiExecutor.executeDelayed({
+                    status.setText(previousText)
+                    statusExtra.setText(previousTextExtra)
+                }, UPDATE_DELAY_IN_MILLIS)
+
+            status.setText(text)
+            statusExtra.setText("")
+        }
+    }
+
     fun action(action: ControlAction) {
         controlsController.action(cws.ci, action)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
index b07a75d..d70c86f 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
@@ -22,6 +22,8 @@
 import android.view.ViewGroup
 
 interface ControlsUiController {
+    val available: Boolean
+
     fun show(parent: ViewGroup)
     fun hide()
     fun onRefreshState(componentName: ComponentName, controls: List<Control>)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index a777faf..ed521e3 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -38,10 +38,10 @@
 import com.android.systemui.controls.management.ControlsProviderSelectorActivity
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.R
+import com.android.systemui.util.concurrency.DelayableExecutor
 
 import dagger.Lazy
 
-import java.util.concurrent.Executor
 import javax.inject.Inject
 import javax.inject.Singleton
 
@@ -104,18 +104,23 @@
     }
 }
 
+private data class ControlKey(val componentName: ComponentName, val controlId: String)
+
 @Singleton
 class ControlsUiControllerImpl @Inject constructor (
     val controlsController: Lazy<ControlsController>,
     val context: Context,
-    @Main val uiExecutor: Executor
+    @Main val uiExecutor: DelayableExecutor
 ) : ControlsUiController {
 
     private lateinit var controlInfos: List<ControlInfo>
-    private val controlsById = mutableMapOf<Pair<ComponentName, String>, ControlWithState>()
-    private val controlViewsById = mutableMapOf<String, ControlViewHolder>()
+    private val controlsById = mutableMapOf<ControlKey, ControlWithState>()
+    private val controlViewsById = mutableMapOf<ControlKey, ControlViewHolder>()
     private lateinit var parent: ViewGroup
 
+    override val available: Boolean
+        get() = controlsController.get().available
+
     override fun show(parent: ViewGroup) {
         Log.d(TAG, "show()")
 
@@ -125,7 +130,7 @@
 
         controlInfos.map {
             ControlWithState(it, null)
-        }.associateByTo(controlsById) { Pair(it.ci.component, it.ci.controlId) }
+        }.associateByTo(controlsById) { ControlKey(it.ci.component, it.ci.controlId) }
 
         if (controlInfos.isEmpty()) {
             showInitialSetupView()
@@ -178,9 +183,10 @@
             val item = inflater.inflate(
                 R.layout.controls_base_item, lastRow, false) as ViewGroup
             lastRow.addView(item)
-            val cvh = ControlViewHolder(item, controlsController.get())
-            cvh.bindData(controlsById.get(Pair(it.component, it.controlId))!!)
-            controlViewsById.put(it.controlId, cvh)
+            val cvh = ControlViewHolder(item, controlsController.get(), uiExecutor)
+            val key = ControlKey(it.component, it.controlId)
+            cvh.bindData(controlsById.getValue(key))
+            controlViewsById.put(key, cvh)
         }
 
         if ((controlInfos.size % 2) == 1) {
@@ -205,21 +211,24 @@
     override fun onRefreshState(componentName: ComponentName, controls: List<Control>) {
         Log.d(TAG, "onRefreshState()")
         controls.forEach { c ->
-            controlsById.get(Pair(componentName, c.getControlId()))?.let {
+            controlsById.get(ControlKey(componentName, c.getControlId()))?.let {
                 Log.d(TAG, "onRefreshState() for id: " + c.getControlId())
                 val cws = ControlWithState(it.ci, c)
-                controlsById.put(Pair(componentName, c.getControlId()), cws)
+                val key = ControlKey(componentName, c.getControlId())
+                controlsById.put(key, cws)
 
                 uiExecutor.execute {
-                    controlViewsById.get(c.getControlId())?.bindData(cws)
+                    controlViewsById.get(key)?.bindData(cws)
                 }
             }
         }
     }
 
     override fun onActionResponse(componentName: ComponentName, controlId: String, response: Int) {
-        Log.d(TAG, "onActionResponse()")
-        TODO("not implemented")
+        val key = ControlKey(componentName, controlId)
+        uiExecutor.execute {
+            controlViewsById.get(key)?.actionResponse(response)
+        }
     }
 
     private fun createRow(inflater: LayoutInflater, parent: ViewGroup): ViewGroup {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt
index 093c99f..da52c6f 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt
@@ -108,10 +108,18 @@
     DeviceTypes.TYPE_OUTLET to IconState(
         R.drawable.ic_power_off_gm2_24px,
         R.drawable.ic_power_gm2_24px
+    ),
+    DeviceTypes.TYPE_VACUUM to IconState(
+        R.drawable.ic_vacuum_gm2_24px,
+        R.drawable.ic_vacuum_gm2_24px
+    ),
+    DeviceTypes.TYPE_MOP to IconState(
+        R.drawable.ic_vacuum_gm2_24px,
+        R.drawable.ic_vacuum_gm2_24px
     )
 ).withDefault {
     IconState(
-        R.drawable.ic_light_off_gm2_24px,
-        R.drawable.ic_lightbulb_outline_gm2_24px
+        R.drawable.ic_device_unknown_gm2_24px,
+        R.drawable.ic_device_unknown_gm2_24px
     )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index 45c07a3..b3fc027 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -1725,7 +1725,8 @@
             if (!(mBackgroundDrawable instanceof ScrimDrawable)) {
                 return;
             }
-            ((ScrimDrawable) mBackgroundDrawable).setColor(Color.BLACK, animate);
+            ((ScrimDrawable) mBackgroundDrawable).setColor(colors.supportsDarkText() ? Color.WHITE
+                    : Color.BLACK, animate);
             View decorView = getWindow().getDecorView();
             if (colors.supportsDarkText()) {
                 decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR |
@@ -1899,9 +1900,7 @@
     }
 
     private boolean shouldShowControls() {
-        return isCurrentUserOwner()
-                && !mKeyguardManager.isDeviceLocked()
-                && Settings.Secure.getInt(mContext.getContentResolver(),
-                        "systemui.controls_available", 0) == 1;
+        return !mKeyguardManager.isDeviceLocked()
+                && mControlsUiController.getAvailable();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/log/Event.java b/packages/SystemUI/src/com/android/systemui/log/Event.java
deleted file mode 100644
index 7bc1abf..0000000
--- a/packages/SystemUI/src/com/android/systemui/log/Event.java
+++ /dev/null
@@ -1,84 +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.log;
-
-import android.annotation.IntDef;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Stores information about an event that occurred in SystemUI to be used for debugging and triage.
- * Every event has a time stamp, log level and message.
- * Events are stored in {@link SysuiLog} and can be printed in a dumpsys.
- */
-public class Event {
-    public static final int UNINITIALIZED = -1;
-
-    @IntDef({ERROR, WARN, INFO, DEBUG, VERBOSE})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface Level {}
-    public static final int VERBOSE = 2;
-    public static final int DEBUG = 3;
-    public static final int INFO = 4;
-    public static final int WARN = 5;
-    public static final int ERROR = 6;
-    public static final @Level int DEFAULT_LOG_LEVEL = DEBUG;
-
-    private long mTimestamp;
-    private @Level int mLogLevel = DEFAULT_LOG_LEVEL;
-    private String mMessage = "";
-
-    /**
-     * initialize an event with a message
-     */
-    public Event init(String message) {
-        init(DEFAULT_LOG_LEVEL, message);
-        return this;
-    }
-
-    /**
-     * initialize an event with a logLevel and message
-     */
-    public Event init(@Level int logLevel, String message) {
-        mTimestamp = System.currentTimeMillis();
-        mLogLevel = logLevel;
-        mMessage = message;
-        return this;
-    }
-
-    public String getMessage() {
-        return mMessage;
-    }
-
-    public long getTimestamp() {
-        return mTimestamp;
-    }
-
-    public @Level int getLogLevel() {
-        return mLogLevel;
-    }
-
-    /**
-     * Recycle this event
-     */
-    void recycle() {
-        mTimestamp = -1;
-        mLogLevel = DEFAULT_LOG_LEVEL;
-        mMessage = "";
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/log/RichEvent.java b/packages/SystemUI/src/com/android/systemui/log/RichEvent.java
deleted file mode 100644
index 470f2b0..0000000
--- a/packages/SystemUI/src/com/android/systemui/log/RichEvent.java
+++ /dev/null
@@ -1,123 +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.log;
-
-/**
- * Stores information about an event that occurred in SystemUI to be used for debugging and triage.
- * Every rich event has a time stamp, event type, and log level, with the option to provide the
- * reason this event was triggered.
- * Events are stored in {@link SysuiLog} and can be printed in a dumpsys.
- */
-public abstract class RichEvent extends Event {
-    private int mType;
-
-    /**
-     * Initializes a rich event that includes an event type that matches with an index in the array
-     * getEventLabels().
-     */
-    public RichEvent init(@Event.Level int logLevel, int type, String reason) {
-        final int numEvents = getEventLabels().length;
-        if (type < 0 || type >= numEvents) {
-            throw new IllegalArgumentException("Unsupported event type. Events only supported"
-                    + " from 0 to " + (numEvents - 1) + ", but given type=" + type);
-        }
-        mType = type;
-        super.init(logLevel, getEventLabels()[mType] + " " + reason);
-        return this;
-    }
-
-    /**
-     * Returns an array of the event labels.  The index represents the event type and the
-     * corresponding String stored at that index is the user-readable representation of that event.
-     * @return array of user readable events, where the index represents its event type constant
-     */
-    public abstract String[] getEventLabels();
-
-    @Override
-    public void recycle() {
-        super.recycle();
-        mType = -1;
-    }
-
-    public int getType() {
-        return mType;
-    }
-
-    /**
-     * Builder to build a RichEvent.
-     * @param <B> Log specific builder that is extending this builder
-     * @param <E> Type of event we'll be building
-     */
-    public abstract static class Builder<B extends Builder<B, E>, E extends RichEvent> {
-        public static final int UNINITIALIZED = -1;
-
-        public final SysuiLog mLog;
-        private B mBuilder = getBuilder();
-        protected int mType;
-        protected String mReason;
-        protected @Level int mLogLevel;
-
-        public Builder(SysuiLog sysuiLog) {
-            mLog = sysuiLog;
-            reset();
-        }
-
-        /**
-         * Reset this builder's parameters so it can be reused to build another RichEvent.
-         */
-        public void reset() {
-            mType = UNINITIALIZED;
-            mReason = null;
-            mLogLevel = VERBOSE;
-        }
-
-        /**
-         * Get the log-specific builder.
-         */
-        public abstract B getBuilder();
-
-        /**
-         * Build the log-specific event given an event to populate.
-         */
-        public abstract E build(E e);
-
-        /**
-         * Optional - set the log level. Defaults to DEBUG.
-         */
-        public B setLogLevel(@Level int logLevel) {
-            mLogLevel = logLevel;
-            return mBuilder;
-        }
-
-        /**
-         * Required - set the event type.  These events must correspond with the events from
-         * getEventLabels().
-         */
-        public B setType(int type) {
-            mType = type;
-            return mBuilder;
-        }
-
-        /**
-         * Optional - set the reason why this event was triggered.
-         */
-        public B setReason(String reason) {
-            mReason = reason;
-            return mBuilder;
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/log/SysuiLog.java b/packages/SystemUI/src/com/android/systemui/log/SysuiLog.java
deleted file mode 100644
index 9ee3e67..0000000
--- a/packages/SystemUI/src/com/android/systemui/log/SysuiLog.java
+++ /dev/null
@@ -1,180 +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.log;
-
-import android.os.Build;
-import android.os.SystemProperties;
-import android.util.Log;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.DumpController;
-import com.android.systemui.Dumpable;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.text.SimpleDateFormat;
-import java.util.ArrayDeque;
-import java.util.Locale;
-
-/**
- * Thread-safe logger in SystemUI which prints logs to logcat and stores logs to be
- * printed by the DumpController. This is an alternative to printing directly
- * to avoid logs being deleted by chatty. The number of logs retained is varied based on
- * whether the build is {@link Build.IS_DEBUGGABLE}.
- *
- * To manually view the logs via adb:
- *      adb shell dumpsys activity service com.android.systemui/.SystemUIService \
- *      dependency DumpController <SysuiLogId>
- *
- * Logs can be disabled by setting the following SystemProperty and then restarting the device:
- *      adb shell setprop persist.sysui.log.enabled.<id> true/false && adb reboot
- *
- * @param <E> Type of event we'll be logging
- */
-public class SysuiLog<E extends Event> implements Dumpable {
-    public static final SimpleDateFormat DATE_FORMAT =
-            new SimpleDateFormat("MM-dd HH:mm:ss.S", Locale.US);
-
-    protected final Object mDataLock = new Object();
-    private final String mId;
-    private final int mMaxLogs;
-    protected boolean mEnabled;
-    protected boolean mLogToLogcatEnabled;
-
-    @VisibleForTesting protected ArrayDeque<E> mTimeline;
-
-    /**
-     * Creates a SysuiLog
-     * @param dumpController where to register this logger's dumpsys
-     * @param id user-readable tag for this logger
-     * @param maxDebugLogs maximum number of logs to retain when {@link sDebuggable} is true
-     * @param maxLogs maximum number of logs to retain when {@link sDebuggable} is false
-     */
-    public SysuiLog(DumpController dumpController, String id, int maxDebugLogs, int maxLogs) {
-        this(dumpController, id, sDebuggable ? maxDebugLogs : maxLogs,
-                SystemProperties.getBoolean(SYSPROP_ENABLED_PREFIX + id, DEFAULT_ENABLED),
-                SystemProperties.getBoolean(SYSPROP_LOGCAT_ENABLED_PREFIX + id,
-                        DEFAULT_LOGCAT_ENABLED));
-    }
-
-    @VisibleForTesting
-    protected SysuiLog(DumpController dumpController, String id, int maxLogs, boolean enabled,
-            boolean logcatEnabled) {
-        mId = id;
-        mMaxLogs = maxLogs;
-        mEnabled = enabled;
-        mLogToLogcatEnabled = logcatEnabled;
-        mTimeline = mEnabled ? new ArrayDeque<>(mMaxLogs) : null;
-        dumpController.registerDumpable(mId, this);
-    }
-
-    /**
-     * Logs an event to the timeline which can be printed by the dumpsys.
-     * May also log to logcat if enabled.
-     * @return the last event that was discarded from the Timeline (can be recycled)
-     */
-    public E log(E event) {
-        if (!mEnabled) {
-            return null;
-        }
-
-        E recycledEvent = null;
-        synchronized (mDataLock) {
-            if (mTimeline.size() >= mMaxLogs) {
-                recycledEvent = mTimeline.removeFirst();
-            }
-
-            mTimeline.add(event);
-        }
-
-        if (mLogToLogcatEnabled) {
-            final String strEvent = eventToString(event);
-            switch (event.getLogLevel()) {
-                case Event.VERBOSE:
-                    Log.v(mId, strEvent);
-                    break;
-                case Event.DEBUG:
-                    Log.d(mId, strEvent);
-                    break;
-                case Event.ERROR:
-                    Log.e(mId, strEvent);
-                    break;
-                case Event.INFO:
-                    Log.i(mId, strEvent);
-                    break;
-                case Event.WARN:
-                    Log.w(mId, strEvent);
-                    break;
-            }
-        }
-
-        if (recycledEvent != null) {
-            recycledEvent.recycle();
-        }
-
-        return recycledEvent;
-    }
-
-    /**
-     * @return user-readable string of the given event with timestamp
-     */
-    private String eventToTimestampedString(Event event) {
-        StringBuilder sb = new StringBuilder();
-        sb.append(SysuiLog.DATE_FORMAT.format(event.getTimestamp()));
-        sb.append(" ");
-        sb.append(event.getMessage());
-        return sb.toString();
-    }
-
-    /**
-     * @return user-readable string of the given event without a timestamp
-     */
-    public String eventToString(Event event) {
-        return event.getMessage();
-    }
-
-    @GuardedBy("mDataLock")
-    private void dumpTimelineLocked(PrintWriter pw) {
-        pw.println("\tTimeline:");
-
-        for (Event event : mTimeline) {
-            pw.println("\t" + eventToTimestampedString(event));
-        }
-    }
-
-    @Override
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println(mId + ":");
-
-        if (mEnabled) {
-            synchronized (mDataLock) {
-                dumpTimelineLocked(pw);
-            }
-        } else {
-            pw.print(" - Logging disabled.");
-        }
-    }
-
-    private static boolean sDebuggable = Build.IS_DEBUGGABLE;
-    private static final String SYSPROP_ENABLED_PREFIX = "persist.sysui.log.enabled.";
-    private static final String SYSPROP_LOGCAT_ENABLED_PREFIX = "persist.sysui.log.enabled.logcat.";
-    private static final boolean DEFAULT_ENABLED = sDebuggable;
-    private static final boolean DEFAULT_LOGCAT_ENABLED = false;
-    private static final int DEFAULT_MAX_DEBUG_LOGS = 100;
-    private static final int DEFAULT_MAX_LOGS = 50;
-}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
index eba2e78..3ae627d 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
@@ -19,10 +19,6 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 
-import android.animation.AnimationHandler;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.TimeAnimator;
 import android.annotation.Nullable;
 import android.app.ActivityManager.StackInfo;
 import android.app.IActivityManager;
@@ -36,6 +32,7 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.util.Log;
+import android.view.Choreographer;
 
 import androidx.dynamicanimation.animation.SpringForce;
 
@@ -88,9 +85,11 @@
     /** PIP's current bounds on the screen. */
     private final Rect mBounds = new Rect();
 
+    private final SfVsyncFrameCallbackProvider mSfVsyncFrameProvider =
+            new SfVsyncFrameCallbackProvider();
+
     /**
-     * Bounds that are animated using the physics animator. PIP is moved to these bounds whenever
-     * the {@link #mVsyncTimeAnimator} ticks.
+     * Bounds that are animated using the physics animator.
      */
     private final Rect mAnimatedBounds = new Rect();
 
@@ -100,12 +99,16 @@
     private PhysicsAnimator<Rect> mAnimatedBoundsPhysicsAnimator = PhysicsAnimator.getInstance(
             mAnimatedBounds);
 
+    /** Callback that re-sizes PIP to the animated bounds. */
+    private final Choreographer.FrameCallback mResizePipVsyncCallback =
+            l -> resizePipUnchecked(mAnimatedBounds);
+
     /**
-     * Time animator whose frame timing comes from the SurfaceFlinger vsync frame provider. At each
-     * frame, PIP is moved to {@link #mAnimatedBounds}, which are animated asynchronously using
-     * physics animations.
+     * Update listener that posts a vsync frame callback to resize PIP to {@link #mAnimatedBounds}.
      */
-    private TimeAnimator mVsyncTimeAnimator;
+    private final PhysicsAnimator.UpdateListener<Rect> mResizePipVsyncUpdateListener =
+            (target, values) ->
+                    mSfVsyncFrameProvider.postFrameCallback(mResizePipVsyncCallback);
 
     /** FlingConfig instances provided to PhysicsAnimator for fling gestures. */
     private PhysicsAnimator.FlingConfig mFlingConfigX;
@@ -126,39 +129,7 @@
         mMenuController = menuController;
         mSnapAlgorithm = snapAlgorithm;
         mFlingAnimationUtils = flingAnimationUtils;
-        final AnimationHandler vsyncFrameCallbackProvider = new AnimationHandler();
-        vsyncFrameCallbackProvider.setProvider(new SfVsyncFrameCallbackProvider());
-
         onConfigurationChanged();
-
-        // Construct a time animator that uses the vsync frame provider. Physics animations can't
-        // use custom frame providers, since they rely on constant time between frames to run the
-        // physics simulations. To work around this, we physically-animate a second set of bounds,
-        // and apply those animating bounds to the PIP in-sync via this TimeAnimator.
-        mVsyncTimeAnimator = new TimeAnimator() {
-            @Override
-            public AnimationHandler getAnimationHandler() {
-                return vsyncFrameCallbackProvider;
-            }
-        };
-
-        // When the time animator ticks, move PIP to the animated bounds.
-        mVsyncTimeAnimator.setTimeListener(
-                (animation, totalTime, deltaTime) ->
-                        resizePipUnchecked(mAnimatedBounds));
-
-        // Add a listener for cancel/end events that moves PIP to the final animated bounds.
-        mVsyncTimeAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationCancel(Animator animation) {
-                resizePipUnchecked(mAnimatedBounds);
-            }
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                resizePipUnchecked(mAnimatedBounds);
-            }
-        });
     }
 
     /**
@@ -429,7 +400,6 @@
      */
     private void cancelAnimations() {
         mAnimatedBoundsPhysicsAnimator.cancel();
-        mVsyncTimeAnimator.cancel();
     }
 
     /**
@@ -457,10 +427,8 @@
         cancelAnimations();
 
         mAnimatedBoundsPhysicsAnimator
-                .withEndActions(
-                        mVsyncTimeAnimator::cancel)
+                .addUpdateListener(mResizePipVsyncUpdateListener)
                 .start();
-        mVsyncTimeAnimator.start();
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
index 0134aa3..5de6d1c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
@@ -169,7 +169,7 @@
         if (DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(mContext)) {
             v.setText(mContext.getString(
                     com.android.internal.R.string.bugreport_status,
-                    Build.VERSION.RELEASE,
+                    Build.VERSION.RELEASE_OR_CODENAME,
                     Build.ID));
             v.setVisibility(View.VISIBLE);
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
index 9e3e94c..6c69718 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
@@ -36,6 +36,7 @@
 import android.media.session.MediaController;
 import android.media.session.MediaSession;
 import android.media.session.PlaybackState;
+import android.os.Handler;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
@@ -370,6 +371,13 @@
         if (mSeamless == null) {
             return;
         }
+        Handler handler = mSeamless.getHandler();
+        handler.post(() -> {
+            updateChipInternal(device);
+        });
+    }
+
+    private void updateChipInternal(MediaDevice device) {
         ColorStateList fgTintList = ColorStateList.valueOf(mForegroundColor);
 
         // Update the outline color
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index b091ad8..626f298 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.screenrecord;
 
-import android.app.Activity;
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
@@ -26,16 +25,21 @@
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
+import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.drawable.Icon;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.VirtualDisplay;
 import android.media.MediaRecorder;
+import android.media.projection.IMediaProjection;
+import android.media.projection.IMediaProjectionManager;
 import android.media.projection.MediaProjection;
 import android.media.projection.MediaProjectionManager;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.provider.MediaStore;
 import android.provider.Settings;
 import android.util.DisplayMetrics;
@@ -83,7 +87,6 @@
     private static final int AUDIO_SAMPLE_RATE = 44100;
 
     private final RecordingController mController;
-    private MediaProjectionManager mMediaProjectionManager;
     private MediaProjection mMediaProjection;
     private Surface mInputSurface;
     private VirtualDisplay mVirtualDisplay;
@@ -134,13 +137,30 @@
 
         switch (action) {
             case ACTION_START:
-                int resultCode = intent.getIntExtra(EXTRA_RESULT_CODE, Activity.RESULT_CANCELED);
                 mUseAudio = intent.getBooleanExtra(EXTRA_USE_AUDIO, false);
                 mShowTaps = intent.getBooleanExtra(EXTRA_SHOW_TAPS, false);
-                Intent data = intent.getParcelableExtra(EXTRA_DATA);
-                if (data != null) {
-                    mMediaProjection = mMediaProjectionManager.getMediaProjection(resultCode, data);
+                try {
+                    IBinder b = ServiceManager.getService(MEDIA_PROJECTION_SERVICE);
+                    IMediaProjectionManager mediaService =
+                            IMediaProjectionManager.Stub.asInterface(b);
+                    IMediaProjection proj = mediaService.createProjection(getUserId(),
+                            getPackageName(),
+                            MediaProjectionManager.TYPE_SCREEN_CAPTURE, false);
+                    IBinder projection = proj.asBinder();
+                    if (projection == null) {
+                        Log.e(TAG, "Projection was null");
+                        Toast.makeText(this, R.string.screenrecord_start_error, Toast.LENGTH_LONG)
+                                .show();
+                        return Service.START_NOT_STICKY;
+                    }
+                    mMediaProjection = new MediaProjection(getApplicationContext(),
+                            IMediaProjection.Stub.asInterface(projection));
                     startRecording();
+                } catch (RemoteException e) {
+                    e.printStackTrace();
+                    Toast.makeText(this, R.string.screenrecord_start_error, Toast.LENGTH_LONG)
+                            .show();
+                    return Service.START_NOT_STICKY;
                 }
                 break;
 
@@ -195,9 +215,6 @@
     @Override
     public void onCreate() {
         super.onCreate();
-
-        mMediaProjectionManager =
-                (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
     }
 
     /**
@@ -269,6 +286,7 @@
     }
 
     private void createRecordingNotification() {
+        Resources res = getResources();
         NotificationChannel channel = new NotificationChannel(
                 CHANNEL_ID,
                 getString(R.string.screenrecord_name),
@@ -281,11 +299,15 @@
 
         Bundle extras = new Bundle();
         extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
-                getResources().getString(R.string.screenrecord_name));
+                res.getString(R.string.screenrecord_name));
+
+        String notificationTitle = mUseAudio
+                ? res.getString(R.string.screenrecord_ongoing_screen_and_audio)
+                : res.getString(R.string.screenrecord_ongoing_screen_only);
 
         mRecordingNotificationBuilder = new Notification.Builder(this, CHANNEL_ID)
                 .setSmallIcon(R.drawable.ic_screenrecord)
-                .setContentTitle(getResources().getString(R.string.screenrecord_name))
+                .setContentTitle(notificationTitle)
                 .setContentText(getResources().getString(R.string.screenrecord_stop_text))
                 .setUsesChronometer(true)
                 .setColorized(true)
@@ -332,8 +354,7 @@
 
         Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID)
                 .setSmallIcon(R.drawable.ic_screenrecord)
-                .setContentTitle(getResources().getString(R.string.screenrecord_name))
-                .setContentText(getResources().getString(R.string.screenrecord_save_message))
+                .setContentTitle(getResources().getString(R.string.screenrecord_save_message))
                 .setContentIntent(PendingIntent.getActivity(
                         this,
                         REQUEST_CODE,
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
index 8324986..566f12b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
@@ -16,15 +16,14 @@
 
 package com.android.systemui.screenrecord;
 
-import android.Manifest;
 import android.app.Activity;
 import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.media.projection.MediaProjectionManager;
 import android.os.Bundle;
-import android.widget.Toast;
+import android.view.Gravity;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.widget.Button;
+import android.widget.Switch;
 
 import com.android.systemui.R;
 
@@ -34,15 +33,11 @@
  * Activity to select screen recording options
  */
 public class ScreenRecordDialog extends Activity {
-    private static final int REQUEST_CODE_VIDEO_ONLY = 200;
-    private static final int REQUEST_CODE_VIDEO_TAPS = 201;
-    private static final int REQUEST_CODE_PERMISSIONS = 299;
-    private static final int REQUEST_CODE_VIDEO_AUDIO = 300;
-    private static final int REQUEST_CODE_VIDEO_AUDIO_TAPS = 301;
-    private static final int REQUEST_CODE_PERMISSIONS_AUDIO = 399;
     private static final long DELAY_MS = 3000;
 
     private final RecordingController mController;
+    private Switch mAudioSwitch;
+    private Switch mTapsSwitch;
 
     @Inject
     public ScreenRecordDialog(RecordingController controller) {
@@ -52,81 +47,42 @@
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        requestScreenCapture();
+
+        Window window = getWindow();
+        // Inflate the decor view, so the attributes below are not overwritten by the theme.
+        window.getDecorView();
+        window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+        window.setGravity(Gravity.TOP);
+
+        setContentView(R.layout.screen_record_dialog);
+
+        Button cancelBtn = findViewById(R.id.button_cancel);
+        cancelBtn.setOnClickListener(v -> {
+            finish();
+        });
+
+        Button startBtn = findViewById(R.id.button_start);
+        startBtn.setOnClickListener(v -> {
+            requestScreenCapture();
+            finish();
+        });
+
+        mAudioSwitch = findViewById(R.id.screenrecord_audio_switch);
+        mTapsSwitch = findViewById(R.id.screenrecord_taps_switch);
     }
 
     private void requestScreenCapture() {
-        MediaProjectionManager mediaProjectionManager = (MediaProjectionManager) getSystemService(
-                Context.MEDIA_PROJECTION_SERVICE);
-        Intent permissionIntent = mediaProjectionManager.createScreenCaptureIntent();
-
-        // TODO get saved settings
-        boolean useAudio = false;
-        boolean showTaps = false;
-        if (useAudio) {
-            startActivityForResult(permissionIntent,
-                    showTaps ? REQUEST_CODE_VIDEO_AUDIO_TAPS : REQUEST_CODE_VIDEO_AUDIO);
-        } else {
-            startActivityForResult(permissionIntent,
-                    showTaps ? REQUEST_CODE_VIDEO_TAPS : REQUEST_CODE_VIDEO_ONLY);
-        }
-    }
-
-    @Override
-    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
-        boolean showTaps = (requestCode == REQUEST_CODE_VIDEO_TAPS
-                || requestCode == REQUEST_CODE_VIDEO_AUDIO_TAPS);
-        boolean useAudio = (requestCode == REQUEST_CODE_VIDEO_AUDIO
-                || requestCode == REQUEST_CODE_VIDEO_AUDIO_TAPS);
-        switch (requestCode) {
-            case REQUEST_CODE_VIDEO_TAPS:
-            case REQUEST_CODE_VIDEO_AUDIO_TAPS:
-            case REQUEST_CODE_VIDEO_ONLY:
-            case REQUEST_CODE_VIDEO_AUDIO:
-                if (resultCode == RESULT_OK) {
-                    PendingIntent startIntent = PendingIntent.getForegroundService(
-                            this, RecordingService.REQUEST_CODE, RecordingService.getStartIntent(
-                                    ScreenRecordDialog.this, resultCode, data, useAudio,
-                                    showTaps),
-                            PendingIntent.FLAG_UPDATE_CURRENT
-                            );
-                    PendingIntent stopIntent = PendingIntent.getService(
-                            this, RecordingService.REQUEST_CODE,
-                            RecordingService.getStopIntent(this),
-                            PendingIntent.FLAG_UPDATE_CURRENT);
-                    mController.startCountdown(DELAY_MS, startIntent, stopIntent);
-                } else {
-                    Toast.makeText(this,
-                            getResources().getString(R.string.screenrecord_permission_error),
-                            Toast.LENGTH_SHORT).show();
-                }
-                finish();
-                break;
-            case REQUEST_CODE_PERMISSIONS:
-                int permission = checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE);
-                if (permission != PackageManager.PERMISSION_GRANTED) {
-                    Toast.makeText(this,
-                            getResources().getString(R.string.screenrecord_permission_error),
-                            Toast.LENGTH_SHORT).show();
-                    finish();
-                } else {
-                    requestScreenCapture();
-                }
-                break;
-            case REQUEST_CODE_PERMISSIONS_AUDIO:
-                int videoPermission = checkSelfPermission(
-                        Manifest.permission.WRITE_EXTERNAL_STORAGE);
-                int audioPermission = checkSelfPermission(Manifest.permission.RECORD_AUDIO);
-                if (videoPermission != PackageManager.PERMISSION_GRANTED
-                        || audioPermission != PackageManager.PERMISSION_GRANTED) {
-                    Toast.makeText(this,
-                            getResources().getString(R.string.screenrecord_permission_error),
-                            Toast.LENGTH_SHORT).show();
-                    finish();
-                } else {
-                    requestScreenCapture();
-                }
-                break;
-        }
+        boolean useAudio = mAudioSwitch.isChecked();
+        boolean showTaps = mTapsSwitch.isChecked();
+        PendingIntent startIntent = PendingIntent.getForegroundService(this,
+                RecordingService.REQUEST_CODE,
+                RecordingService.getStartIntent(
+                        ScreenRecordDialog.this, RESULT_OK, null, useAudio, showTaps),
+                PendingIntent.FLAG_UPDATE_CURRENT);
+        PendingIntent stopIntent = PendingIntent.getService(this,
+                RecordingService.REQUEST_CODE,
+                RecordingService.getStopIntent(this),
+                PendingIntent.FLAG_UPDATE_CURRENT);
+        mController.startCountdown(DELAY_MS, startIntent, stopIntent);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/InflationTask.java b/packages/SystemUI/src/com/android/systemui/statusbar/InflationTask.java
index 22fd37c..eb580c4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/InflationTask.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/InflationTask.java
@@ -22,11 +22,4 @@
  */
 public interface InflationTask {
     void abort();
-
-    /**
-     * Supersedes an existing task. i.e another task was superceeded by this.
-     *
-     * @param task the task that was previously running
-     */
-    default void supersedeTask(InflationTask task) {}
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 7ad07c2..7d3d406 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -20,7 +20,6 @@
 import android.animation.AnimatorListenerAdapter;
 import android.content.Context;
 import android.content.res.ColorStateList;
-import android.content.res.Resources;
 import android.graphics.Color;
 import android.hardware.biometrics.BiometricSourceType;
 import android.hardware.face.FaceManager;
@@ -46,6 +45,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.settingslib.Utils;
+import com.android.settingslib.fuelgauge.BatteryStatus;
 import com.android.systemui.Dependency;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
@@ -97,8 +97,6 @@
     private final LockPatternUtils mLockPatternUtils;
     private final DockManager mDockManager;
 
-    private final int mSlowThreshold;
-    private final int mFastThreshold;
     private final LockIcon mLockIcon;
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
@@ -178,10 +176,6 @@
         mWakeLock = new SettableWakeLock(wakeLock, TAG);
         mLockPatternUtils = lockPatternUtils;
 
-        Resources res = context.getResources();
-        mSlowThreshold = res.getInteger(R.integer.config_chargingSlowlyThreshold);
-        mFastThreshold = res.getInteger(R.integer.config_chargingFastThreshold);
-
         mUserManager = context.getSystemService(UserManager.class);
         mBatteryInfo = iBatteryStats;
 
@@ -484,12 +478,12 @@
         int chargingId;
         if (mPowerPluggedInWired) {
             switch (mChargingSpeed) {
-                case KeyguardUpdateMonitor.BatteryStatus.CHARGING_FAST:
+                case BatteryStatus.CHARGING_FAST:
                     chargingId = hasChargingTime
                             ? R.string.keyguard_indication_charging_time_fast
                             : R.string.keyguard_plugged_in_charging_fast;
                     break;
-                case KeyguardUpdateMonitor.BatteryStatus.CHARGING_SLOWLY:
+                case BatteryStatus.CHARGING_SLOWLY:
                     chargingId = hasChargingTime
                             ? R.string.keyguard_indication_charging_time_slowly
                             : R.string.keyguard_plugged_in_charging_slowly;
@@ -620,7 +614,7 @@
         public static final int HIDE_DELAY_MS = 5000;
 
         @Override
-        public void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) {
+        public void onRefreshBatteryInfo(BatteryStatus status) {
             boolean isChargingOrFull = status.status == BatteryManager.BATTERY_STATUS_CHARGING
                     || status.status == BatteryManager.BATTERY_STATUS_FULL;
             boolean wasPluggedIn = mPowerPluggedIn;
@@ -628,7 +622,7 @@
             mPowerPluggedIn = status.isPluggedIn() && isChargingOrFull;
             mPowerCharged = status.isCharged();
             mChargingWattage = status.maxChargingWattage;
-            mChargingSpeed = status.getChargingSpeed(mSlowThreshold, mFastThreshold);
+            mChargingSpeed = status.getChargingSpeed(mContext);
             mBatteryLevel = status.level;
             try {
                 mChargingTimeRemaining = mPowerPluggedIn
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java
index 81833a4..d0e238a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java
@@ -28,7 +28,6 @@
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
 import javax.inject.Inject;
@@ -64,8 +63,8 @@
 
         notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
             @Override
-            public void onEntryInflated(NotificationEntry entry, int inflatedFlags) {
-                showAlertingView(entry, inflatedFlags);
+            public void onEntryInflated(NotificationEntry entry) {
+                showAlertingView(entry);
             }
 
             @Override
@@ -90,12 +89,11 @@
     /**
      * Adds the entry to the respective alerting manager if the content view was inflated and
      * the entry should still alert.
-     *
-     * @param entry         entry to add
-     * @param inflatedFlags flags representing content views that were inflated
      */
-    private void showAlertingView(NotificationEntry entry, @InflationFlag int inflatedFlags) {
-        if ((inflatedFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) {
+    private void showAlertingView(NotificationEntry entry) {
+        // TODO: Instead of this back and forth, we should listen to changes in heads up and
+        // cancel on-going heads up view inflation using the bind pipeline.
+        if (entry.getRow().getPrivateLayout().getHeadsUpChild() != null) {
             // Possible for shouldHeadsUp to change between the inflation starting and ending.
             // If it does and we no longer need to heads up, we should free the view.
             if (mNotificationInterruptionStateProvider.shouldHeadsUp(entry)) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java
index f6b5583..25253a1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java
@@ -24,7 +24,6 @@
 
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 
 /**
  * Listener interface for changes sent by NotificationEntryManager.
@@ -62,7 +61,7 @@
     /**
      * Called when a notification's views are inflated for the first time.
      */
-    default void onEntryInflated(NotificationEntry entry, @InflationFlag int inflatedFlags) {
+    default void onEntryInflated(NotificationEntry entry) {
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index 6bb377e8..916da6e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -18,6 +18,7 @@
 import static android.service.notification.NotificationListenerService.REASON_CANCEL;
 import static android.service.notification.NotificationListenerService.REASON_ERROR;
 
+import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_UNKNOWN;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback;
 
 import android.annotation.NonNull;
@@ -44,10 +45,9 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
-import com.android.systemui.statusbar.notification.logging.NotifEvent;
-import com.android.systemui.statusbar.notification.logging.NotifLog;
+import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
-import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -96,6 +96,7 @@
  */
 @Singleton
 public class NotificationEntryManager implements
+        CommonNotifCollection,
         Dumpable,
         InflationCallback,
         VisualStabilityManager.Callback {
@@ -126,10 +127,13 @@
     private final Map<NotificationEntry, NotificationLifetimeExtender> mRetainedNotifications =
             new ArrayMap<>();
 
+    private final NotificationEntryManagerLogger mLogger;
+
     // Lazily retrieved dependencies
     private final Lazy<NotificationRowBinder> mNotificationRowBinderLazy;
     private final Lazy<NotificationRemoteInputManager> mRemoteInputManagerLazy;
     private final LeakDetector mLeakDetector;
+    private final List<NotifCollectionListener> mNotifCollectionListeners = new ArrayList<>();
 
     private final KeyguardEnvironment mKeyguardEnvironment;
     private final NotificationGroupManager mGroupManager;
@@ -139,7 +143,6 @@
 
     private NotificationPresenter mPresenter;
     private RankingMap mLatestRankingMap;
-    private NotifLog mNotifLog;
 
     @VisibleForTesting
     final ArrayList<NotificationLifetimeExtender> mNotificationLifetimeExtenders
@@ -180,7 +183,7 @@
 
     @Inject
     public NotificationEntryManager(
-            NotifLog notifLog,
+            NotificationEntryManagerLogger logger,
             NotificationGroupManager groupManager,
             NotificationRankingManager rankingManager,
             KeyguardEnvironment keyguardEnvironment,
@@ -189,7 +192,7 @@
             Lazy<NotificationRemoteInputManager> notificationRemoteInputManagerLazy,
             LeakDetector leakDetector,
             ForegroundServiceDismissalFeatureController fgsFeatureController) {
-        mNotifLog = notifLog;
+        mLogger = logger;
         mGroupManager = groupManager;
         mRankingManager = rankingManager;
         mKeyguardEnvironment = keyguardEnvironment;
@@ -287,13 +290,12 @@
             NotificationEntry entry = mPendingNotifications.get(key);
             entry.abortTask();
             mPendingNotifications.remove(key);
-            mNotifLog.log(NotifEvent.INFLATION_ABORTED, entry, "PendingNotification aborted"
-                    + " reason=" + reason);
+            mLogger.logInflationAborted(key, "pending", reason);
         }
         NotificationEntry addedEntry = getActiveNotificationUnfiltered(key);
         if (addedEntry != null) {
             addedEntry.abortTask();
-            mNotifLog.log(NotifEvent.INFLATION_ABORTED, addedEntry.getKey() + " " + reason);
+            mLogger.logInflationAborted(key, "active", reason);
         }
     }
 
@@ -318,17 +320,16 @@
     }
 
     @Override
-    public void onAsyncInflationFinished(NotificationEntry entry,
-            @InflationFlag int inflatedFlags) {
+    public void onAsyncInflationFinished(NotificationEntry entry) {
         mPendingNotifications.remove(entry.getKey());
         // If there was an async task started after the removal, we don't want to add it back to
         // the list, otherwise we might get leaks.
         if (!entry.isRowRemoved()) {
             boolean isNew = getActiveNotificationUnfiltered(entry.getKey()) == null;
+            mLogger.logNotifInflated(entry.getKey(), isNew);
             if (isNew) {
                 for (NotificationEntryListener listener : mNotificationEntryListeners) {
-                    mNotifLog.log(NotifEvent.INFLATED, entry);
-                    listener.onEntryInflated(entry, inflatedFlags);
+                    listener.onEntryInflated(entry);
                 }
                 addActiveNotification(entry);
                 updateNotifications("onAsyncInflationFinished");
@@ -337,7 +338,6 @@
                 }
             } else {
                 for (NotificationEntryListener listener : mNotificationEntryListeners) {
-                    mNotifLog.log(NotifEvent.INFLATED, entry);
                     listener.onEntryReinflated(entry);
                 }
             }
@@ -419,7 +419,7 @@
         for (NotificationRemoveInterceptor interceptor : mRemoveInterceptors) {
             if (interceptor.onNotificationRemoveRequested(key, entry, reason)) {
                 // Remove intercepted; log and skip
-                mNotifLog.log(NotifEvent.REMOVE_INTERCEPTED);
+                mLogger.logRemovalIntercepted(key);
                 return;
             }
         }
@@ -434,10 +434,7 @@
                     if (extender.shouldExtendLifetimeForPendingNotification(pendingEntry)) {
                         extendLifetime(pendingEntry, extender);
                         lifetimeExtended = true;
-                        mNotifLog.log(
-                                NotifEvent.LIFETIME_EXTENDED,
-                                pendingEntry.getSbn(),
-                                "pendingEntry extendedBy=" + extender.toString());
+                        mLogger.logLifetimeExtended(key, extender.getClass().getName(), "pending");
                     }
                 }
             }
@@ -457,10 +454,7 @@
                         mLatestRankingMap = ranking;
                         extendLifetime(entry, extender);
                         lifetimeExtended = true;
-                        mNotifLog.log(
-                                NotifEvent.LIFETIME_EXTENDED,
-                                entry.getSbn(),
-                                "entry extendedBy=" + extender.toString());
+                        mLogger.logLifetimeExtended(key, extender.getClass().getName(), "active");
                         break;
                     }
                 }
@@ -483,11 +477,17 @@
                 mLeakDetector.trackGarbage(entry);
                 removedByUser |= entryDismissed;
 
-                mNotifLog.log(NotifEvent.NOTIF_REMOVED, entry.getSbn(),
-                        "removedByUser=" + removedByUser);
+                mLogger.logNotifRemoved(entry.getKey(), removedByUser);
                 for (NotificationEntryListener listener : mNotificationEntryListeners) {
                     listener.onEntryRemoved(entry, visibility, removedByUser);
                 }
+                for (NotifCollectionListener listener : mNotifCollectionListeners) {
+                    // NEM doesn't have a good knowledge of reasons so defaulting to unknown.
+                    listener.onEntryRemoved(entry, REASON_UNKNOWN);
+                }
+                for (NotifCollectionListener listener : mNotifCollectionListeners) {
+                    listener.onEntryCleanUp(entry);
+                }
             }
         }
     }
@@ -553,6 +553,10 @@
 
         mLeakDetector.trackInstance(entry);
 
+        for (NotifCollectionListener listener : mNotifCollectionListeners) {
+            listener.onEntryInit(entry);
+        }
+
         // Construct the expanded view.
         if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
             mNotificationRowBinderLazy.get()
@@ -562,10 +566,13 @@
 
         abortExistingInflation(key, "addNotification");
         mPendingNotifications.put(key, entry);
-        mNotifLog.log(NotifEvent.NOTIF_ADDED, entry);
+        mLogger.logNotifAdded(entry.getKey());
         for (NotificationEntryListener listener : mNotificationEntryListeners) {
             listener.onPendingEntryAdded(entry);
         }
+        for (NotifCollectionListener listener : mNotifCollectionListeners) {
+            listener.onEntryAdded(entry);
+        }
     }
 
     public void addNotification(StatusBarNotification notification, RankingMap ranking) {
@@ -596,10 +603,13 @@
         entry.setSbn(notification);
         mGroupManager.onEntryUpdated(entry, oldSbn);
 
-        mNotifLog.log(NotifEvent.NOTIF_UPDATED, entry);
+        mLogger.logNotifUpdated(entry.getKey());
         for (NotificationEntryListener listener : mNotificationEntryListeners) {
             listener.onPreEntryUpdated(entry);
         }
+        for (NotifCollectionListener listener : mNotifCollectionListeners) {
+            listener.onEntryUpdated(entry);
+        }
 
         if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
             mNotificationRowBinderLazy.get()
@@ -674,6 +684,9 @@
         for (NotificationEntryListener listener : mNotificationEntryListeners) {
             listener.onNotificationRankingUpdated(rankingMap);
         }
+        for (NotifCollectionListener listener : mNotifCollectionListeners) {
+            listener.onRankingUpdate(rankingMap);
+        }
     }
 
     private void updateRankingOfPendingNotifications(@Nullable RankingMap rankingMap) {
@@ -785,7 +798,7 @@
     //TODO: Get rid of this in favor of NotificationUpdateHandler#updateNotificationRanking
     /**
      * @param rankingMap the {@link RankingMap} to apply to the current notification list
-     * @param reason the reason for calling this method, for {@link NotifLog}
+     * @param reason the reason for calling this method, which will be logged
      */
     public void updateRanking(RankingMap rankingMap, String reason) {
         updateRankingAndSort(rankingMap, reason);
@@ -862,6 +875,11 @@
         return mReadOnlyNotifications.size() != 0;
     }
 
+    @Override
+    public void addCollectionListener(NotifCollectionListener listener) {
+        mNotifCollectionListeners.add(listener);
+    }
+
     /*
      * End annexation
      * -----
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManagerLogger.kt
new file mode 100644
index 0000000..4382ab5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManagerLogger.kt
@@ -0,0 +1,100 @@
+/*
+ * 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
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.dagger.NotificationLog
+import javax.inject.Inject
+
+/** Logger for [NotificationEntryManager]. */
+class NotificationEntryManagerLogger @Inject constructor(
+    @NotificationLog private val buffer: LogBuffer
+) {
+    fun logNotifAdded(key: String) {
+        buffer.log(TAG, INFO, {
+            str1 = key
+        }, {
+            "NOTIF ADDED $str1"
+        })
+    }
+
+    fun logNotifUpdated(key: String) {
+        buffer.log(TAG, INFO, {
+            str1 = key
+        }, {
+            "NOTIF UPDATED $str1"
+        })
+    }
+
+    fun logInflationAborted(key: String, status: String, reason: String) {
+        buffer.log(TAG, DEBUG, {
+            str1 = key
+            str2 = status
+            str3 = reason
+        }, {
+            "NOTIF INFLATION ABORTED $str1 notifStatus=$str2 reason=$str3"
+        })
+    }
+
+    fun logNotifInflated(key: String, isNew: Boolean) {
+        buffer.log(TAG, DEBUG, {
+            str1 = key
+            bool1 = isNew
+        }, {
+            "NOTIF INFLATED $str1 isNew=$bool1}"
+        })
+    }
+
+    fun logRemovalIntercepted(key: String) {
+        buffer.log(TAG, INFO, {
+            str1 = key
+        }, {
+            "NOTIF REMOVE INTERCEPTED for $str1"
+        })
+    }
+
+    fun logLifetimeExtended(key: String, extenderName: String, status: String) {
+        buffer.log(TAG, INFO, {
+            str1 = key
+            str2 = extenderName
+            str3 = status
+        }, {
+            "NOTIF LIFETIME EXTENDED $str1 extender=$str2 status=$str3"
+        })
+    }
+
+    fun logNotifRemoved(key: String, removedByUser: Boolean) {
+        buffer.log(TAG, INFO, {
+            str1 = key
+            bool1 = removedByUser
+        }, {
+            "NOTIF REMOVED $str1 removedByUser=$bool1"
+        })
+    }
+
+    fun logFilterAndSort(reason: String) {
+        buffer.log(TAG, INFO, {
+            str1 = reason
+        }, {
+            "FILTER AND SORT reason=$str1"
+        })
+    }
+}
+
+private const val TAG = "NotificationEntryMgr"
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
index 7a6d4f1..9272e51b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
@@ -149,9 +149,7 @@
                 }
 
                 @Override
-                public void onAsyncInflationFinished(
-                        NotificationEntry entry,
-                        int inflatedFlags) {
+                public void onAsyncInflationFinished(NotificationEntry entry) {
                     if (mExternalInflationCallback != null) {
                         mExternalInflationCallback.onInflationFinished(entry);
                     }
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 9142388..5767ad9 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
@@ -23,6 +23,7 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
+import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
 
@@ -66,7 +67,7 @@
  *  9. The list is handed off to the view layer to be rendered
  */
 @Singleton
-public class NotifPipeline {
+public class NotifPipeline implements CommonNotifCollection {
     private final NotifCollection mNotifCollection;
     private final ShadeListBuilder mShadeListBuilder;
 
@@ -89,10 +90,7 @@
         return mNotifCollection.getActiveNotifs();
     }
 
-    /**
-     * Registers a listener to be informed when there is a notification entry event such as an add,
-     * update, or remove.
-     */
+    @Override
     public void addCollectionListener(NotifCollectionListener listener) {
         mNotifCollection.addCollectionListener(listener);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index df65dac..5dbf47e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -275,7 +275,12 @@
         return mHasInflationError;
     }
 
-    void setHasInflationError(boolean hasError) {
+    /**
+     * Set whether the notification has an error while inflating.
+     *
+     * TODO: Move this into an inflation error manager class.
+     */
+    public void setHasInflationError(boolean hasError) {
         mHasInflationError = hasError;
     }
 
@@ -595,12 +600,8 @@
 
     public void setInflationTask(InflationTask abortableTask) {
         // abort any existing inflation
-        InflationTask existing = mRunningTask;
         abortTask();
         mRunningTask = abortableTask;
-        if (existing != null && mRunningTask != null) {
-            mRunningTask.supersedeTask(existing);
-        }
     }
 
     public void onInflationTaskFinished() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
index 1eeeab3..2981252 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
@@ -22,11 +22,10 @@
 import android.service.notification.NotificationListenerService.RankingMap
 import android.service.notification.StatusBarNotification
 import com.android.systemui.statusbar.NotificationMediaManager
+import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger
 import com.android.systemui.statusbar.notification.NotificationFilter
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
-import com.android.systemui.statusbar.notification.logging.NotifEvent
-import com.android.systemui.statusbar.notification.logging.NotifLog
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
 import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING
 import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_PEOPLE
@@ -53,7 +52,7 @@
     private val groupManager: NotificationGroupManager,
     private val headsUpManager: HeadsUpManager,
     private val notifFilter: NotificationFilter,
-    private val notifLog: NotifLog,
+    private val logger: NotificationEntryManagerLogger,
     sectionsFeatureManager: NotificationSectionsFeatureManager,
     private val peopleNotificationIdentifier: PeopleNotificationIdentifier,
     private val highPriorityProvider: HighPriorityProvider
@@ -134,7 +133,7 @@
         entries: Sequence<NotificationEntry>,
         reason: String
     ): Sequence<NotificationEntry> {
-        notifLog.log(NotifEvent.FILTER_AND_SORT, reason)
+        logger.logFilterAndSort(reason)
 
         return entries.filter { !notifFilter.shouldFilterOut(it) }
                 .sortedWith(rankingComparator)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
index 41314b8..1e5946a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
@@ -22,8 +22,6 @@
 import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
-import com.android.systemui.statusbar.notification.logging.NotifEvent;
-import com.android.systemui.statusbar.notification.logging.NotifLog;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -42,13 +40,15 @@
 public class PreparationCoordinator implements Coordinator {
     private static final String TAG = "PreparationCoordinator";
 
-    private final NotifLog mNotifLog;
+    private final PreparationCoordinatorLogger mLogger;
     private final NotifInflater mNotifInflater;
     private final List<NotificationEntry> mPendingNotifications = new ArrayList<>();
 
     @Inject
-    public PreparationCoordinator(NotifLog notifLog, NotifInflaterImpl notifInflater) {
-        mNotifLog = notifLog;
+    public PreparationCoordinator(
+            PreparationCoordinatorLogger logger,
+            NotifInflaterImpl notifInflater) {
+        mLogger = logger;
         mNotifInflater = notifInflater;
         mNotifInflater.setInflationCallback(mInflationCallback);
     }
@@ -106,7 +106,7 @@
             new NotifInflater.InflationCallback() {
         @Override
         public void onInflationFinished(NotificationEntry entry) {
-            mNotifLog.log(NotifEvent.INFLATED, entry);
+            mLogger.logNotifInflated(entry.getKey());
             mPendingNotifications.remove(entry);
             mNotifInflatingFilter.invalidateList();
         }
@@ -123,7 +123,7 @@
     }
 
     private void abortInflation(NotificationEntry entry, String reason) {
-        mNotifLog.log(NotifEvent.INFLATION_ABORTED, reason);
+        mLogger.logInflationAborted(entry.getKey(), reason);
         entry.abortTask();
         mPendingNotifications.remove(entry);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
new file mode 100644
index 0000000..75e7bc9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.collection.coordinator
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.dagger.NotificationLog
+import javax.inject.Inject
+
+class PreparationCoordinatorLogger @Inject constructor(
+    @NotificationLog private val buffer: LogBuffer
+) {
+    fun logNotifInflated(key: String) {
+        buffer.log(TAG, LogLevel.DEBUG, {
+            str1 = key
+        }, {
+            "NOTIF INFLATED $str1"
+        })
+    }
+
+    fun logInflationAborted(key: String, reason: String) {
+        buffer.log(TAG, LogLevel.DEBUG, {
+            str1 = key
+            str2 = reason
+        }, {
+            "NOTIF INFLATION ABORTED $str1 reason=$str2"
+        })
+    }
+}
+
+private const val TAG = "PreparationCoordinator"
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
index 2a7683a..ecf62db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
@@ -42,8 +42,11 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.NotifBindPipeline;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder;
+import com.android.systemui.statusbar.notification.row.RowContentBindParams;
+import com.android.systemui.statusbar.notification.row.RowContentBindStage;
 import com.android.systemui.statusbar.notification.row.RowInflaterTask;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -67,8 +70,10 @@
     private final NotificationGroupManager mGroupManager;
     private final NotificationGutsManager mGutsManager;
     private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
+
     private final Context mContext;
-    private final NotificationRowContentBinder mRowContentBinder;
+    private final NotifBindPipeline mNotifBindPipeline;
+    private final RowContentBindStage mRowContentBindStage;
     private final NotificationMessagingUtil mMessagingUtil;
     private final ExpandableNotificationRow.ExpansionLogger mExpansionLogger =
             this::logNotificationExpansion;
@@ -93,7 +98,8 @@
             Context context,
             NotificationRemoteInputManager notificationRemoteInputManager,
             NotificationLockscreenUserManager notificationLockscreenUserManager,
-            NotificationRowContentBinder rowContentBinder,
+            NotifBindPipeline notifBindPipeline,
+            RowContentBindStage rowContentBindStage,
             @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
             KeyguardBypassController keyguardBypassController,
             StatusBarStateController statusBarStateController,
@@ -103,7 +109,8 @@
             Provider<RowInflaterTask> rowInflaterTaskProvider,
             NotificationLogger logger) {
         mContext = context;
-        mRowContentBinder = rowContentBinder;
+        mNotifBindPipeline = notifBindPipeline;
+        mRowContentBindStage = rowContentBindStage;
         mMessagingUtil = new NotificationMessagingUtil(context);
         mNotificationRemoteInputManager = notificationRemoteInputManager;
         mNotificationLockscreenUserManager = notificationLockscreenUserManager;
@@ -167,6 +174,7 @@
         }
     }
 
+    //TODO: This method associates a row with an entry, but eventually needs to not do that
     private void bindRow(NotificationEntry entry, PackageManager pmUser,
             StatusBarNotification sbn, ExpandableNotificationRow row,
             Runnable onDismissRunnable) {
@@ -195,12 +203,11 @@
                 mKeyguardBypassController,
                 mGroupManager,
                 mHeadsUpManager,
-                mRowContentBinder,
+                mRowContentBindStage,
                 mPresenter);
 
         // TODO: Either move these into ExpandableNotificationRow#initialize or out of row entirely
         row.setStatusBarStateController(mStatusBarStateController);
-        row.setInflationCallback(mInflationCallback);
         row.setAppOpsOnClickListener(mOnAppOpsClickListener);
         if (mAllowLongPress) {
             row.setLongPressListener(mGutsManager::openGuts);
@@ -214,6 +221,10 @@
             row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
         }
 
+        entry.setRow(row);
+        row.setEntry(entry);
+        mNotifBindPipeline.manageRow(entry, row);
+
         mBindRowCallback.onBindRow(entry, pmUser, sbn, row);
     }
 
@@ -247,13 +258,11 @@
         }
     }
 
-    //TODO: This method associates a row with an entry, but eventually needs to not do that
     private void updateNotification(
             NotificationEntry entry,
             PackageManager pmUser,
             StatusBarNotification sbn,
             ExpandableNotificationRow row) {
-        row.setIsLowPriority(entry.isAmbient());
 
         // Extract target SDK version.
         try {
@@ -268,22 +277,31 @@
         // TODO: should updates to the entry be happening somewhere else?
         entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
 
-        entry.setRow(row);
         row.setOnActivatedListener(mPresenter);
 
-        boolean useIncreasedCollapsedHeight =
+        final boolean useIncreasedCollapsedHeight =
                 mMessagingUtil.isImportantMessaging(sbn, entry.getImportance());
-        boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight
+        final boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight
                 && !mPresenter.isPresenterFullyCollapsed();
-        row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
-        row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp);
-        row.setEntry(entry);
+        final boolean isLowPriority = entry.isAmbient();
+
+        RowContentBindParams params = mRowContentBindStage.getStageParams(entry);
+        params.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
+        params.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp);
+        params.setUseLowPriority(entry.isAmbient());
 
         if (mNotificationInterruptionStateProvider.shouldHeadsUp(entry)) {
-            row.setInflationFlags(FLAG_CONTENT_VIEW_HEADS_UP);
+            params.requireContentViews(FLAG_CONTENT_VIEW_HEADS_UP);
         }
+        //TODO: Replace this API with RowContentBindParams directly
         row.setNeedsRedaction(mNotificationLockscreenUserManager.needsRedaction(entry));
-        row.inflateViews();
+        params.rebindAllContentViews();
+        mRowContentBindStage.requestRebind(entry, en -> {
+            row.setUsesIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
+            row.setUsesIncreasedHeadsUpHeight(useIncreasedHeadsUp);
+            row.setIsLowPriority(isLowPriority);
+            mInflationCallback.onAsyncInflationFinished(en);
+        });
 
         // bind the click event to the content area
         Objects.requireNonNull(mNotificationClicker).register(row, sbn);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollection.java
new file mode 100644
index 0000000..171816f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollection.java
@@ -0,0 +1,37 @@
+/*
+ * 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.collection.notifcollection;
+
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+
+/**
+ * A notification collection that manages the list of {@link NotificationEntry}s that will be
+ * rendered.
+ *
+ * TODO: (b/145659174) Once we fully switch off {@link NotificationEntryManager} to
+ * {@link NotifPipeline}, we probably won't need this, but having it for now makes it easy to
+ * switch between the two.
+ */
+public interface CommonNotifCollection {
+    /**
+     * Registers a listener to be informed when notifications are created, added, updated, removed,
+     * or deleted.
+     */
+    void addCollectionListener(NotifCollectionListener listener);
+}
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
index c7666e4..39f4dfa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -19,6 +19,10 @@
 import android.content.Context;
 
 import com.android.systemui.R;
+import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.init.NotificationsControllerImpl;
 import com.android.systemui.statusbar.notification.init.NotificationsControllerStub;
@@ -45,4 +49,16 @@
             return stubController.get();
         }
     }
+
+    /**
+     * Provide the active notification collection managing the notifications to render.
+     */
+    @Provides
+    @Singleton
+    public CommonNotifCollection provideCommonNotifCollection(
+            FeatureFlags featureFlags,
+            Lazy<NotifPipeline> pipeline,
+            NotificationEntryManager entryManager) {
+        return featureFlags.isNewNotifPipelineRenderingEnabled() ? pipeline.get() : entryManager;
+    }
 }
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
index 61e3192..254b64f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
@@ -28,6 +28,7 @@
 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.row.NotifBindPipelineInitializer
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
 import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper
 import com.android.systemui.statusbar.phone.NotificationGroupManager
@@ -55,6 +56,7 @@
     private val notificationListener: NotificationListener,
     private val entryManager: NotificationEntryManager,
     private val newNotifPipeline: Lazy<NotifPipelineInitializer>,
+    private val notifBindPipelineInitializer: NotifBindPipelineInitializer,
     private val deviceProvisionedController: DeviceProvisionedController,
     private val notificationRowBinder: NotificationRowBinderImpl,
     private val remoteInputUriController: RemoteInputUriController,
@@ -98,6 +100,7 @@
         if (featureFlags.isNewNotifPipelineRenderingEnabled) {
             // TODO
         } else {
+            notifBindPipelineInitializer.initialize()
             notificationRowBinder.setInflationCallback(entryManager)
 
             remoteInputUriController.attach(entryManager)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java
deleted file mode 100644
index 9adceb7..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java
+++ /dev/null
@@ -1,212 +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.statusbar.notification.logging;
-
-import android.annotation.IntDef;
-import android.service.notification.NotificationListenerService;
-import android.service.notification.StatusBarNotification;
-
-import com.android.systemui.log.RichEvent;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.collection.ShadeListBuilder;
-import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * An event related to notifications. {@link NotifLog} stores and prints these events for debugging
- * and triaging purposes. We do not store a copy of the status bar notification nor ranking
- * here to mitigate memory usage.
- */
-public class NotifEvent extends RichEvent {
-    /**
-     * Initializes a rich event that includes an event type that matches with an index in the array
-     * getEventLabels().
-     */
-    public NotifEvent init(@EventType int type, StatusBarNotification sbn,
-            NotificationListenerService.Ranking ranking, String reason) {
-        StringBuilder extraInfo = new StringBuilder(reason);
-        if (sbn != null) {
-            extraInfo.append(" " + sbn.getKey());
-        }
-
-        if (ranking != null) {
-            extraInfo.append(" Ranking=");
-            extraInfo.append(ranking.getRank());
-        }
-        super.init(INFO, type, extraInfo.toString());
-        return this;
-    }
-
-    /**
-     * Event labels for ListBuilderEvents
-     * Index corresponds to an # in {@link EventType}
-     */
-    @Override
-    public String[] getEventLabels() {
-        assert (TOTAL_EVENT_LABELS
-                == (TOTAL_NEM_EVENT_TYPES
-                        + TOTAL_LIST_BUILDER_EVENT_TYPES
-                        + TOTAL_COALESCER_EVENT_TYPES));
-        return EVENT_LABELS;
-    }
-
-    /**
-     * @return if this event occurred in {@link ShadeListBuilder}
-     */
-    static boolean isListBuilderEvent(@EventType int type) {
-        return isBetweenInclusive(type, 0, TOTAL_LIST_BUILDER_EVENT_TYPES);
-    }
-
-    /**
-     * @return if this event occurred in {@link NotificationEntryManager}
-     */
-    static boolean isNemEvent(@EventType int type) {
-        return isBetweenInclusive(type, TOTAL_LIST_BUILDER_EVENT_TYPES,
-                TOTAL_LIST_BUILDER_EVENT_TYPES + TOTAL_NEM_EVENT_TYPES);
-    }
-
-    private static boolean isBetweenInclusive(int x, int a, int b) {
-        return x >= a && x <= b;
-    }
-
-    @IntDef({
-            // NotifListBuilder events:
-            WARN,
-            ON_BUILD_LIST,
-            START_BUILD_LIST,
-            DISPATCH_FINAL_LIST,
-            LIST_BUILD_COMPLETE,
-            PRE_GROUP_FILTER_INVALIDATED,
-            PROMOTER_INVALIDATED,
-            SECTION_INVALIDATED,
-            COMPARATOR_INVALIDATED,
-            PARENT_CHANGED,
-            FILTER_CHANGED,
-            PROMOTER_CHANGED,
-            PRE_RENDER_FILTER_INVALIDATED,
-
-            // NotificationEntryManager events:
-            NOTIF_ADDED,
-            NOTIF_REMOVED,
-            NOTIF_UPDATED,
-            FILTER,
-            SORT,
-            FILTER_AND_SORT,
-            NOTIF_VISIBILITY_CHANGED,
-            LIFETIME_EXTENDED,
-            REMOVE_INTERCEPTED,
-            INFLATION_ABORTED,
-            INFLATED,
-
-            // GroupCoalescer
-            COALESCED_EVENT,
-            EARLY_BATCH_EMIT,
-            EMIT_EVENT_BATCH
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface EventType {}
-
-    private static final String[] EVENT_LABELS =
-            new String[]{
-                    // NotifListBuilder labels:
-                    "Warning",
-                    "OnBuildList",
-                    "StartBuildList",
-                    "DispatchFinalList",
-                    "ListBuildComplete",
-                    "FilterInvalidated",
-                    "PromoterInvalidated",
-                    "SectionInvalidated",
-                    "ComparatorInvalidated",
-                    "ParentChanged",
-                    "FilterChanged",
-                    "PromoterChanged",
-                    "FinalFilterInvalidated",
-                    "SectionerChanged",
-
-                    // NEM event labels:
-                    "NotifAdded",
-                    "NotifRemoved",
-                    "NotifUpdated",
-                    "Filter",
-                    "Sort",
-                    "FilterAndSort",
-                    "NotifVisibilityChanged",
-                    "LifetimeExtended",
-                    "RemoveIntercepted",
-                    "InflationAborted",
-                    "Inflated",
-
-                    // GroupCoalescer labels:
-                    "CoalescedEvent",
-                    "EarlyBatchEmit",
-                    "EmitEventBatch",
-                    "BatchMaxTimeout"
-            };
-
-    private static final int TOTAL_EVENT_LABELS = EVENT_LABELS.length;
-
-    /**
-     * Events related to {@link ShadeListBuilder}
-     */
-    public static final int WARN = 0;
-    public static final int ON_BUILD_LIST = 1;
-    public static final int START_BUILD_LIST = 2;
-    public static final int DISPATCH_FINAL_LIST = 3;
-    public static final int LIST_BUILD_COMPLETE = 4;
-    public static final int PRE_GROUP_FILTER_INVALIDATED = 5;
-    public static final int PROMOTER_INVALIDATED = 6;
-    public static final int SECTION_INVALIDATED = 7;
-    public static final int COMPARATOR_INVALIDATED = 8;
-    public static final int PARENT_CHANGED = 9;
-    public static final int FILTER_CHANGED = 10;
-    public static final int PROMOTER_CHANGED = 11;
-    public static final int PRE_RENDER_FILTER_INVALIDATED = 12;
-    public static final int SECTION_CHANGED = 13;
-    private static final int TOTAL_LIST_BUILDER_EVENT_TYPES = 14;
-
-    /**
-     * Events related to {@link NotificationEntryManager}
-     */
-    private static final int NEM_EVENT_START_INDEX = TOTAL_LIST_BUILDER_EVENT_TYPES;
-    public static final int NOTIF_ADDED = NEM_EVENT_START_INDEX;
-    public static final int NOTIF_REMOVED = NEM_EVENT_START_INDEX + 1;
-    public static final int NOTIF_UPDATED = NEM_EVENT_START_INDEX + 2;
-    public static final int FILTER = NEM_EVENT_START_INDEX + 3;
-    public static final int SORT = NEM_EVENT_START_INDEX + 4;
-    public static final int FILTER_AND_SORT = NEM_EVENT_START_INDEX + 5;
-    public static final int NOTIF_VISIBILITY_CHANGED = NEM_EVENT_START_INDEX + 6;
-    public static final int LIFETIME_EXTENDED = NEM_EVENT_START_INDEX + 7;
-    // unable to remove notif - removal intercepted by {@link NotificationRemoveInterceptor}
-    public static final int REMOVE_INTERCEPTED = NEM_EVENT_START_INDEX + 8;
-    public static final int INFLATION_ABORTED = NEM_EVENT_START_INDEX + 9;
-    public static final int INFLATED = NEM_EVENT_START_INDEX + 10;
-    private static final int TOTAL_NEM_EVENT_TYPES = 11;
-
-    /**
-     * Events related to {@link GroupCoalescer}
-     */
-    private static final int COALESCER_EVENT_START_INDEX = NEM_EVENT_START_INDEX
-            + TOTAL_NEM_EVENT_TYPES;
-    public static final int COALESCED_EVENT = COALESCER_EVENT_START_INDEX;
-    public static final int EARLY_BATCH_EMIT = COALESCER_EVENT_START_INDEX + 1;
-    public static final int EMIT_EVENT_BATCH = COALESCER_EVENT_START_INDEX + 2;
-    public static final int BATCH_MAX_TIMEOUT = COALESCER_EVENT_START_INDEX + 3;
-    private static final int TOTAL_COALESCER_EVENT_TYPES = 3;
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifLog.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifLog.java
deleted file mode 100644
index 299d628..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifLog.java
+++ /dev/null
@@ -1,115 +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.statusbar.notification.logging;
-
-import android.os.SystemProperties;
-import android.service.notification.NotificationListenerService.Ranking;
-import android.service.notification.StatusBarNotification;
-
-import com.android.systemui.DumpController;
-import com.android.systemui.log.SysuiLog;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-/**
- * Logs systemui notification events for debugging and triaging purposes. Logs are dumped in
- * bugreports or on demand:
- *      adb shell dumpsys activity service com.android.systemui/.SystemUIService \
- *      dependency DumpController NotifLog
- */
-@Singleton
-public class NotifLog extends SysuiLog<NotifEvent> {
-    private static final String TAG = "NotifLog";
-    private static final boolean SHOW_NEM_LOGS =
-            SystemProperties.getBoolean("persist.sysui.log.notif.nem", true);
-    private static final boolean SHOW_LIST_BUILDER_LOGS =
-            SystemProperties.getBoolean("persist.sysui.log.notif.listbuilder", true);
-
-    private static final int MAX_DOZE_DEBUG_LOGS = 400;
-    private static final int MAX_DOZE_LOGS = 50;
-
-    private NotifEvent mRecycledEvent;
-
-    @Inject
-    public NotifLog(DumpController dumpController) {
-        super(dumpController, TAG, MAX_DOZE_DEBUG_LOGS, MAX_DOZE_LOGS);
-    }
-
-    /**
-     * Logs a {@link NotifEvent} with a notification, ranking and message.
-     * Uses the last recycled event if available.
-     * @return true if successfully logged, else false
-     */
-    public void log(@NotifEvent.EventType int eventType,
-            StatusBarNotification sbn, Ranking ranking, String msg) {
-        if (!mEnabled
-                || (NotifEvent.isListBuilderEvent(eventType) && !SHOW_LIST_BUILDER_LOGS)
-                || (NotifEvent.isNemEvent(eventType) && !SHOW_NEM_LOGS)) {
-            return;
-        }
-
-        if (mRecycledEvent != null) {
-            mRecycledEvent = log(mRecycledEvent.init(eventType, sbn, ranking, msg));
-        } else {
-            mRecycledEvent = log(new NotifEvent().init(eventType, sbn, ranking, msg));
-        }
-    }
-
-    /**
-     * Logs a {@link NotifEvent} with no extra information aside from the event type
-     */
-    public void log(@NotifEvent.EventType int eventType) {
-        log(eventType, null, null, "");
-    }
-
-    /**
-     * Logs a {@link NotifEvent} with a message
-     */
-    public void log(@NotifEvent.EventType int eventType, String msg) {
-        log(eventType, null, null, msg);
-    }
-
-    /**
-     * Logs a {@link NotifEvent} with a entry
-     */
-    public void log(@NotifEvent.EventType int eventType, NotificationEntry entry) {
-        log(eventType, entry.getSbn(), entry.getRanking(), "");
-    }
-
-    /**
-     * Logs a {@link NotifEvent} with a NotificationEntry and message
-     */
-    public void log(@NotifEvent.EventType int eventType, NotificationEntry entry, String msg) {
-        log(eventType, entry.getSbn(), entry.getRanking(), msg);
-    }
-
-    /**
-     * Logs a {@link NotifEvent} with a notification and message
-     */
-    public void log(@NotifEvent.EventType int eventType, StatusBarNotification sbn, String msg) {
-        log(eventType, sbn, null, msg);
-    }
-
-    /**
-     * Logs a {@link NotifEvent} with a ranking and message
-     */
-    public void log(@NotifEvent.EventType int eventType, Ranking ranking, String msg) {
-        log(eventType, null, ranking, msg);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
index 88b4147..fc221d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
@@ -93,8 +93,7 @@
     private val peopleHubManagerForUser = SparseArray<PeopleHubManager>()
 
     private val notificationEntryListener = object : NotificationEntryListener {
-        override fun onEntryInflated(entry: NotificationEntry, inflatedFlags: Int) =
-                addVisibleEntry(entry)
+        override fun onEntryInflated(entry: NotificationEntry) = addVisibleEntry(entry)
 
         override fun onEntryReinflated(entry: NotificationEntry) = addVisibleEntry(entry)
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindRequester.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindRequester.java
new file mode 100644
index 0000000..1cf6b4f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindRequester.java
@@ -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.row;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.os.CancellationSignal;
+
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback;
+
+/**
+ * A {@link BindRequester} is a general superclass for something that notifies
+ * {@link NotifBindPipeline} when it needs it to kick off a bind run.
+ */
+public abstract class BindRequester {
+    private @Nullable BindRequestListener mBindRequestListener;
+
+    /**
+     * Notifies the listener that some parameters/state has changed for some notification and that
+     * content needs to be bound again.
+     *
+     * The caller can also specify a callback for when the entire bind pipeline completes, i.e.
+     * when the change is fully propagated to the final view. The caller can cancel this
+     * callback with the returned cancellation signal.
+     *
+     * @param callback callback after bind completely finishes
+     * @return cancellation signal to cancel callback
+     */
+    public final CancellationSignal requestRebind(
+            @NonNull NotificationEntry entry,
+            @Nullable BindCallback callback) {
+        CancellationSignal signal = new CancellationSignal();
+        if (mBindRequestListener != null) {
+            mBindRequestListener.onBindRequest(entry, signal, callback);
+        }
+        return signal;
+    }
+
+    final void setBindRequestListener(BindRequestListener listener) {
+        mBindRequestListener = listener;
+    }
+
+    /**
+     * Listener interface for when content needs to be bound again.
+     */
+    public interface BindRequestListener {
+
+        /**
+         * Called when {@link #requestRebind} is called.
+         *
+         * @param entry notification that has outdated content
+         * @param signal cancellation signal to cancel callback
+         * @param callback callback after content is fully updated
+         */
+        void onBindRequest(
+                @NonNull NotificationEntry entry,
+                @NonNull CancellationSignal signal,
+                @Nullable BindCallback callback);
+
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java
new file mode 100644
index 0000000..29447ca
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java
@@ -0,0 +1,104 @@
+/*
+ * 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.row;
+
+import android.annotation.MainThread;
+import android.util.ArrayMap;
+
+import androidx.annotation.NonNull;
+
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+
+import java.util.Map;
+
+/**
+ * A {@link BindStage} is an abstraction for a unit of work in inflating/binding/unbinding
+ * views to a notification. Used by {@link NotifBindPipeline}.
+ *
+ * Clients may also use {@link #getStageParams} to provide parameters for this stage for a given
+ * notification and request a rebind.
+ *
+ * @param <Params> params to do this stage
+ */
+@MainThread
+public abstract class BindStage<Params> extends BindRequester {
+
+    private Map<NotificationEntry, Params> mContentParams = new ArrayMap<>();
+
+    /**
+     * Execute the stage asynchronously.
+     *
+     * @param row notification top-level view to bind views to
+     * @param callback callback after stage finishes
+     */
+    protected abstract void executeStage(
+            @NonNull NotificationEntry entry,
+            @NonNull ExpandableNotificationRow row,
+            @NonNull StageCallback callback);
+
+    /**
+     * Abort the stage if in progress.
+     *
+     * @param row notification top-level view to bind views to
+     */
+    protected abstract void abortStage(
+            @NonNull NotificationEntry entry,
+            @NonNull ExpandableNotificationRow row);
+
+    /**
+     * Get the stage parameters for the entry. Clients should use this to modify how the stage
+     * handles the notification content.
+     */
+    public final Params getStageParams(@NonNull NotificationEntry entry) {
+        Params params = mContentParams.get(entry);
+        if (params == null) {
+            throw new IllegalStateException(
+                    String.format("Entry does not have any stage parameters. key: %s",
+                            entry.getKey()));
+        }
+        return params;
+    }
+
+    /**
+     * Create a params entry for the notification for this stage.
+     */
+    final void createStageParams(@NonNull NotificationEntry entry) {
+        mContentParams.put(entry, newStageParams());
+    }
+
+    /**
+     * Delete params entry for notification.
+     */
+    final void deleteStageParams(@NonNull NotificationEntry entry) {
+        mContentParams.remove(entry);
+    }
+
+    /**
+     * Create a new, empty stage params object.
+     */
+    protected abstract Params newStageParams();
+
+    /**
+     * Interface for callback.
+     */
+    interface StageCallback {
+        /**
+         * Callback for when the stage is complete.
+         */
+        void onStageFinished(NotificationEntry entry);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 253be2fc..c34bba7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -19,8 +19,6 @@
 import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
 import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED;
 import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP;
-import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
-import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC;
 
@@ -88,8 +86,6 @@
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.logging.NotificationCounters;
-import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams;
-import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
 import com.android.systemui.statusbar.notification.stack.AmbientState;
@@ -127,14 +123,6 @@
     public static final float DEFAULT_HEADER_VISIBLE_AMOUNT = 1.0f;
     private static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30);
 
-    /**
-     * Content views that must be inflated at all times.
-     */
-    @InflationFlag
-    static final int REQUIRED_INFLATION_FLAGS =
-            FLAG_CONTENT_VIEW_CONTRACTED
-            | FLAG_CONTENT_VIEW_EXPANDED;
-
     private boolean mUpdateBackgroundOnUpdate;
     private boolean mNotificationTranslationFinished = false;
 
@@ -149,7 +137,7 @@
     private StatusBarStateController mStatusbarStateController;
     private KeyguardBypassController mBypassController;
     private LayoutListener mLayoutListener;
-    private NotificationRowContentBinder mNotificationContentBinder;
+    private RowContentBindStage mRowContentBindStage;
     private int mIconTransformContentShift;
     private int mIconTransformContentShiftNoIcon;
     private int mMaxHeadsUpHeightBeforeN;
@@ -244,10 +232,7 @@
     private ExpandableNotificationRow mNotificationParent;
     private OnExpandClickListener mOnExpandClickListener;
     private View.OnClickListener mOnAppOpsClickListener;
-    private InflationCallback mInflationCallback;
     private boolean mIsChildInGroup;
-    private @InflationFlag int mInflationFlags = REQUIRED_INFLATION_FLAGS;
-    private final BindParams mBindParams = new BindParams();
 
     // Listener will be called when receiving a long click event.
     // Use #setLongPressPosition to optionally assign positional data with the long press.
@@ -460,24 +445,25 @@
     }
 
     /**
-     * Inflate views based off the inflation flags set. Inflation happens asynchronously.
-     */
-    public void inflateViews() {
-        mNotificationContentBinder.bindContent(mEntry, this, mInflationFlags, mBindParams,
-                false /* forceInflate */, mInflationCallback);
-    }
-
-    /**
      * Marks a content view as freeable, setting it so that future inflations do not reinflate
      * and ensuring that the view is freed when it is safe to remove.
      *
+     * TODO: This should be moved to the respective coordinator and call
+     * {@link RowContentBindParams#freeContentViews} directly after disappear animation
+     * finishes instead of depending on binding API to know when it's "safe".
+     *
      * @param inflationFlag flag corresponding to the content view to be freed
      */
     public void freeContentViewWhenSafe(@InflationFlag int inflationFlag) {
         // View should not be reinflated in the future
-        clearInflationFlags(inflationFlag);
-        Runnable freeViewRunnable =
-                () -> mNotificationContentBinder.unbindContent(mEntry, this, inflationFlag);
+        Runnable freeViewRunnable = () -> {
+            // Possible for notification to be removed after free request.
+            if (!isRemoved()) {
+                RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);
+                params.freeContentViews(inflationFlag);
+                mRowContentBindStage.requestRebind(mEntry, null /* callback */);
+            }
+        };
         switch (inflationFlag) {
             case FLAG_CONTENT_VIEW_HEADS_UP:
                 getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_HEADSUP,
@@ -492,35 +478,6 @@
     }
 
     /**
-     * Set flags for content views that should be inflated
-     *
-     * @param flags flags to inflate
-     */
-    public void setInflationFlags(@InflationFlag int flags) {
-        mInflationFlags |= flags;
-    }
-
-    /**
-     * Clear flags for content views that should not be inflated
-     *
-     * @param flags flags that should not be inflated
-     */
-    public void clearInflationFlags(@InflationFlag int flags) {
-        mInflationFlags &= ~flags;
-        mInflationFlags |= REQUIRED_INFLATION_FLAGS;
-    }
-
-    /**
-     * Whether or not a content view should be inflated.
-     *
-     * @param flag the flag corresponding to the content view
-     * @return true if the flag is set, false otherwise
-     */
-    public boolean isInflationFlagSet(@InflationFlag int flag) {
-        return ((mInflationFlags & flag) != 0);
-    }
-
-    /**
      * Caches whether or not this row contains a system notification. Note, this is only cached
      * once per notification as the packageInfo can't technically change for a notification row.
      */
@@ -838,13 +795,13 @@
         }
         mNotificationParent = isChildInGroup ? parent : null;
         mPrivateLayout.setIsChildInGroup(isChildInGroup);
-        mBindParams.isChildInGroup = isChildInGroup;
+        // TODO: Move inflation logic out of this call
         if (mIsChildInGroup != isChildInGroup) {
             mIsChildInGroup = isChildInGroup;
             if (mIsLowPriority) {
-                int flags = FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED;
-                mNotificationContentBinder.bindContent(mEntry, this, flags, mBindParams,
-                        false /* forceInflate */, mInflationCallback);
+                RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);
+                params.setUseLowPriority(mIsLowPriority);
+                mRowContentBindStage.requestRebind(mEntry, null /* callback */);
             }
         }
         resetBackgroundAlpha();
@@ -1243,8 +1200,10 @@
             l.reInflateViews();
         }
         mEntry.getSbn().clearPackageContext();
-        mNotificationContentBinder.bindContent(mEntry, this, mInflationFlags, mBindParams,
-                true /* forceInflate */, mInflationCallback);
+        // TODO: Move content inflation logic out of this call
+        RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);
+        params.setNeedsReinflation(true);
+        mRowContentBindStage.requestRebind(mEntry, null /* callback */);
     }
 
     @Override
@@ -1598,7 +1557,6 @@
     public void setIsLowPriority(boolean isLowPriority) {
         mIsLowPriority = isLowPriority;
         mPrivateLayout.setIsLowPriority(isLowPriority);
-        mBindParams.isLowPriority = mIsLowPriority;
         if (mChildrenContainer != null) {
             mChildrenContainer.setIsLowPriority(isLowPriority);
         }
@@ -1608,36 +1566,25 @@
         return mIsLowPriority;
     }
 
-    public void setUseIncreasedCollapsedHeight(boolean use) {
+    public void setUsesIncreasedCollapsedHeight(boolean use) {
         mUseIncreasedCollapsedHeight = use;
-        mBindParams.usesIncreasedHeight = use;
     }
 
-    public void setUseIncreasedHeadsUpHeight(boolean use) {
+    public void setUsesIncreasedHeadsUpHeight(boolean use) {
         mUseIncreasedHeadsUpHeight = use;
-        mBindParams.usesIncreasedHeadsUpHeight = use;
-    }
-
-    /**
-     * Set callback for notification content inflation
-     *
-     * @param callback inflation callback
-     */
-    public void setInflationCallback(InflationCallback callback) {
-        mInflationCallback = callback;
     }
 
     public void setNeedsRedaction(boolean needsRedaction) {
+        // TODO: Move inflation logic out of this call and remove this method
         if (mNeedsRedaction != needsRedaction) {
             mNeedsRedaction = needsRedaction;
+            RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);
             if (needsRedaction) {
-                setInflationFlags(FLAG_CONTENT_VIEW_PUBLIC);
-                mNotificationContentBinder.bindContent(mEntry, this, FLAG_CONTENT_VIEW_PUBLIC,
-                        mBindParams, false /* forceInflate */, mInflationCallback);
+                params.requireContentViews(FLAG_CONTENT_VIEW_PUBLIC);
             } else {
-                clearInflationFlags(FLAG_CONTENT_VIEW_PUBLIC);
-                freeContentViewWhenSafe(FLAG_CONTENT_VIEW_PUBLIC);
+                params.freeContentViews(FLAG_CONTENT_VIEW_PUBLIC);
             }
+            mRowContentBindStage.requestRebind(mEntry, null /* callback */);
         }
     }
 
@@ -1664,7 +1611,7 @@
             KeyguardBypassController bypassController,
             NotificationGroupManager groupManager,
             HeadsUpManager headsUpManager,
-            NotificationRowContentBinder rowContentBinder,
+            RowContentBindStage rowContentBindStage,
             OnExpandClickListener onExpandClickListener) {
         mAppName = appName;
         if (mMenuRow != null && mMenuRow.getMenuView() != null) {
@@ -1676,7 +1623,7 @@
         mGroupManager = groupManager;
         mPrivateLayout.setGroupManager(groupManager);
         mHeadsUpManager = headsUpManager;
-        mNotificationContentBinder = rowContentBinder;
+        mRowContentBindStage = rowContentBindStage;
         mOnExpandClickListener = onExpandClickListener;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java
new file mode 100644
index 0000000..af2d084
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java
@@ -0,0 +1,207 @@
+/*
+ * 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.row;
+
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.widget.FrameLayout;
+
+import androidx.annotation.MainThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.os.CancellationSignal;
+
+import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.statusbar.notification.NotificationEntryListener;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
+
+import java.util.Map;
+import java.util.Set;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * {@link NotifBindPipeline} is responsible for converting notifications from their data form to
+ * their actual inflated views. It is essentially a control class that composes notification view
+ * binding logic (i.e. {@link BindStage}) in response to explicit bind requests. At the end of the
+ * pipeline, the notification's bound views are guaranteed to be correct and up-to-date, and any
+ * registered callbacks will be called.
+ *
+ * The pipeline ensures that a notification's top-level view and its content views are bound.
+ * Currently, a notification's top-level view, the {@link ExpandableNotificationRow} is essentially
+ * just a {@link FrameLayout} for various different content views that are switched in and out as
+ * appropriate. These include a contracted view, expanded view, heads up view, and sensitive view on
+ * keyguard. See {@link InflationFlag}. These content views themselves can have child views added
+ * on depending on different factors. For example, notification actions and smart replies are views
+ * that are dynamically added to these content views after they're inflated. Finally, aside from
+ * the app provided content views, System UI itself also provides some content views that are shown
+ * occasionally (e.g. {@link NotificationGuts}). Many of these are business logic specific views
+ * and the requirements surrounding them may change over time, so the pipeline must handle
+ * composing the logic as necessary.
+ *
+ * Note that bind requests do not only occur from add/updates from updates from the app. For
+ * example, the user may make changes to device settings (e.g. sensitive notifications on lock
+ * screen) or we may want to make certain optimizations for the sake of memory or performance (e.g
+ * freeing views when not visible). Oftentimes, we also need to wait for these changes to complete
+ * before doing something else (e.g. moving a notification to the top of the screen to heads up).
+ * The pipeline thus handles bind requests from across the system and provides a way for
+ * requesters to know when the change is propagated to the view.
+ *
+ * Right now, we only support one attached {@link BindStage} which just does all the binding but we
+ * should eventually support multiple stages once content inflation is made more modular.
+ * In particular, row inflation/binding, which is handled by {@link NotificationRowBinder} should
+ * probably be moved here in the future as a stage. Right now, the pipeline just manages content
+ * views and assumes that a row is given to it when it's inflated.
+ */
+@MainThread
+@Singleton
+public final class NotifBindPipeline {
+    private final Map<NotificationEntry, BindEntry> mBindEntries = new ArrayMap<>();
+    private BindStage mStage;
+
+    @Inject
+    NotifBindPipeline(NotificationEntryManager entryManager) {
+        entryManager.addNotificationEntryListener(mEntryListener);
+    }
+
+    /**
+     * Set the bind stage for binding notification row content.
+     */
+    public void setStage(
+            BindStage stage) {
+        mStage = stage;
+        mStage.setBindRequestListener(this::onBindRequested);
+    }
+
+    /**
+     * Start managing the row's content for a given notification.
+     */
+    public void manageRow(
+            @NonNull NotificationEntry entry,
+            @NonNull ExpandableNotificationRow row) {
+        final BindEntry bindEntry = getBindEntry(entry);
+        bindEntry.row = row;
+        if (bindEntry.invalidated) {
+            startPipeline(entry);
+        }
+    }
+
+    private void onBindRequested(
+            @NonNull NotificationEntry entry,
+            @NonNull CancellationSignal signal,
+            @Nullable BindCallback callback) {
+        final BindEntry bindEntry = getBindEntry(entry);
+        if (bindEntry == null) {
+            // Invalidating views for a notification that is not active.
+            return;
+        }
+
+        bindEntry.invalidated = true;
+
+        // Put in new callback.
+        if (callback != null) {
+            final Set<BindCallback> callbacks = bindEntry.callbacks;
+            callbacks.add(callback);
+            signal.setOnCancelListener(() -> callbacks.remove(callback));
+        }
+
+        startPipeline(entry);
+    }
+
+    /**
+     * Run the pipeline for the notification, ensuring all views are bound when finished. Call all
+     * callbacks when the run finishes. If a run is already in progress, it is restarted.
+     */
+    private void startPipeline(NotificationEntry entry) {
+        if (mStage == null) {
+            throw new IllegalStateException("No stage was ever set on the pipeline");
+        }
+
+        final BindEntry bindEntry = mBindEntries.get(entry);
+        final ExpandableNotificationRow row = bindEntry.row;
+        if (row == null) {
+            // Row is not managed yet but may be soon. Stop for now.
+            return;
+        }
+
+        mStage.abortStage(entry, row);
+        mStage.executeStage(entry, row, (en) -> onPipelineComplete(en));
+    }
+
+    private void onPipelineComplete(NotificationEntry entry) {
+        final BindEntry bindEntry = getBindEntry(entry);
+
+        bindEntry.invalidated = false;
+
+        final Set<BindCallback> callbacks = bindEntry.callbacks;
+        for (BindCallback cb : callbacks) {
+            cb.onBindFinished(entry);
+        }
+        callbacks.clear();
+    }
+
+    //TODO: Move this to onManageEntry hook when we split that from add/remove
+    private final NotificationEntryListener mEntryListener = new NotificationEntryListener() {
+        @Override
+        public void onPendingEntryAdded(NotificationEntry entry) {
+            mBindEntries.put(entry, new BindEntry());
+            mStage.createStageParams(entry);
+        }
+
+        @Override
+        public void onEntryRemoved(NotificationEntry entry,
+                @Nullable NotificationVisibility visibility,
+                boolean removedByUser) {
+            BindEntry bindEntry = mBindEntries.remove(entry);
+            ExpandableNotificationRow row = bindEntry.row;
+            if (row != null) {
+                mStage.abortStage(entry, row);
+            }
+            mStage.deleteStageParams(entry);
+        }
+    };
+
+    private @NonNull BindEntry getBindEntry(NotificationEntry entry) {
+        final BindEntry bindEntry = mBindEntries.get(entry);
+        if (bindEntry == null) {
+            throw new IllegalStateException(
+                    String.format("Attempting bind on an inactive notification. key: %s",
+                            entry.getKey()));
+        }
+        return bindEntry;
+    }
+
+    /**
+     * Interface for bind callback.
+     */
+    public interface BindCallback {
+        /**
+         * Called when all views are fully bound on the notification.
+         */
+        void onBindFinished(NotificationEntry entry);
+    }
+
+    private class BindEntry {
+        public ExpandableNotificationRow row;
+        public final Set<BindCallback> callbacks = new ArraySet<>();
+        public boolean invalidated;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineInitializer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineInitializer.java
new file mode 100644
index 0000000..7754991
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineInitializer.java
@@ -0,0 +1,47 @@
+/*
+ * 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.row;
+
+import javax.inject.Inject;
+
+/**
+ * Initialize {@link NotifBindPipeline} with all its mandatory stages and dynamically added stages.
+ *
+ * In the future, coordinators should be able to register their own {@link BindStage} to the
+ * {@link NotifBindPipeline}.
+ */
+public class NotifBindPipelineInitializer {
+    NotifBindPipeline mNotifBindPipeline;
+    RowContentBindStage mRowContentBindStage;
+
+    @Inject
+    NotifBindPipelineInitializer(
+            NotifBindPipeline pipeline,
+            RowContentBindStage stage) {
+        mNotifBindPipeline = pipeline;
+        mRowContentBindStage = stage;
+        // TODO: Inject coordinators and allow them to add BindStages in initialize
+    }
+
+    /**
+     * Hooks up stages to the pipeline.
+     */
+    public void initialize() {
+        // Mandatory bind stages
+        mNotifBindPipeline.setStage(mRowContentBindStage);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java
index a6e5c2b..b62dfa8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java
@@ -22,10 +22,9 @@
 
 import androidx.annotation.Nullable;
 
-import com.android.internal.statusbar.NotificationVisibility;
-import com.android.systemui.statusbar.notification.NotificationEntryListener;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 
 import java.util.Map;
@@ -40,8 +39,8 @@
             new ArrayMap<>();
 
     @Inject
-    NotifRemoteViewCacheImpl(NotificationEntryManager entryManager) {
-        entryManager.addNotificationEntryListener(mEntryListener);
+    NotifRemoteViewCacheImpl(CommonNotifCollection collection) {
+        collection.addCollectionListener(mCollectionListener);
     }
 
     @Override
@@ -93,17 +92,14 @@
         contentViews.clear();
     }
 
-    private final NotificationEntryListener mEntryListener = new NotificationEntryListener() {
+    private final NotifCollectionListener mCollectionListener = new NotifCollectionListener() {
         @Override
-        public void onPendingEntryAdded(NotificationEntry entry) {
+        public void onEntryInit(NotificationEntry entry) {
             mNotifCachedContentViews.put(entry, new SparseArray<>());
         }
 
         @Override
-        public void onEntryRemoved(
-                NotificationEntry entry,
-                @Nullable NotificationVisibility visibility,
-                boolean removedByUser) {
+        public void onEntryCleanUp(NotificationEntry entry) {
             mNotifCachedContentViews.remove(entry);
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index e1a6747..566da65 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -68,7 +68,7 @@
     private final NotifRemoteViewCache mRemoteViewCache;
 
     @Inject
-    public NotificationContentInflater(
+    NotificationContentInflater(
             NotifRemoteViewCache remoteViewCache,
             NotificationRemoteInputManager remoteInputManager) {
         mRemoteViewCache = remoteViewCache;
@@ -575,7 +575,7 @@
             entry.headsUpStatusBarText = result.headsUpStatusBarText;
             entry.headsUpStatusBarTextPublic = result.headsUpStatusBarTextPublic;
             if (endListener != null) {
-                endListener.onAsyncInflationFinished(entry, reInflateFlags);
+                endListener.onAsyncInflationFinished(entry);
             }
             return true;
         }
@@ -641,7 +641,7 @@
         private final boolean mUsesIncreasedHeight;
         private final InflationCallback mCallback;
         private final boolean mUsesIncreasedHeadsUpHeight;
-        private @InflationFlag int mReInflateFlags;
+        private final @InflationFlag int mReInflateFlags;
         private final NotifRemoteViewCache mRemoteViewCache;
         private ExpandableNotificationRow mRow;
         private Exception mError;
@@ -739,25 +739,16 @@
         }
 
         @Override
-        public void supersedeTask(InflationTask task) {
-            if (task instanceof AsyncInflationTask) {
-                // We want to inflate all flags of the previous task as well
-                mReInflateFlags |= ((AsyncInflationTask) task).mReInflateFlags;
-            }
-        }
-
-        @Override
         public void handleInflationException(NotificationEntry entry, Exception e) {
             handleError(e);
         }
 
         @Override
-        public void onAsyncInflationFinished(NotificationEntry entry,
-                @InflationFlag int inflatedFlags) {
+        public void onAsyncInflationFinished(NotificationEntry entry) {
             mEntry.onInflationTaskFinished();
             mRow.onNotificationUpdated();
             if (mCallback != null) {
-                mCallback.onAsyncInflationFinished(mEntry, inflatedFlags);
+                mCallback.onAsyncInflationFinished(mEntry);
             }
 
             // Notify the resolver that the inflation task has finished,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
index 6045524..bb0681c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -280,7 +280,7 @@
 
         Button favorite = findViewById(R.id.fave);
         favorite.setOnClickListener(mOnFavoriteClick);
-        if (mNotificationChannel.canBypassDnd()) {
+        if (mNotificationChannel.isImportantConversation()) {
             favorite.setText(R.string.notification_conversation_unfavorite);
             favorite.setCompoundDrawablesRelative(
                     mContext.getDrawable(R.drawable.ic_star), null, null, null);
@@ -621,8 +621,8 @@
                         }
                         break;
                     case ACTION_FAVORITE:
-                        // TODO: extend beyond DND
-                        mChannelToUpdate.setBypassDnd(!mChannelToUpdate.canBypassDnd());
+                        mChannelToUpdate.setImportantConversation(
+                                !mChannelToUpdate.isImportantConversation());
                         break;
                     case ACTION_MUTE:
                         if (mChannelToUpdate.getImportance() == IMPORTANCE_UNSPECIFIED
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
index 9b95bff..9bd8d47 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
@@ -101,7 +101,7 @@
      */
     int FLAG_CONTENT_VIEW_PUBLIC = 1 << 3;
 
-    int FLAG_CONTENT_VIEW_ALL = ~0;
+    int FLAG_CONTENT_VIEW_ALL = (1 << 4) - 1;
 
     /**
      * Parameters for content view binding
@@ -146,9 +146,7 @@
          * Callback for after the content views finish inflating.
          *
          * @param entry the entry with the content views set
-         * @param inflatedFlags the flags associated with the content views that were inflated
          */
-        void onAsyncInflationFinished(NotificationEntry entry,
-                @InflationFlag int inflatedFlags);
+        void onAsyncInflationFinished(NotificationEntry entry);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java
new file mode 100644
index 0000000..5170d0b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java
@@ -0,0 +1,165 @@
+/*
+ * 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.row;
+
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
+
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
+
+/**
+ * Parameters for {@link RowContentBindStage}.
+ */
+public final class RowContentBindParams {
+    private boolean mUseLowPriority;
+    private boolean mUseChildInGroup;
+    private boolean mUseIncreasedHeight;
+    private boolean mUseIncreasedHeadsUpHeight;
+    private boolean mViewsNeedReinflation;
+    private @InflationFlag int mContentViews = DEFAULT_INFLATION_FLAGS;
+
+    /**
+     * Content views that are out of date and need to be rebound.
+     *
+     * TODO: This should go away once {@link NotificationContentInflater} is broken down into
+     * smaller stages as then the stage itself would be invalidated.
+     */
+    private @InflationFlag int mDirtyContentViews = mContentViews;
+
+    /**
+     * Set whether content should use a low priority version of its content views.
+     */
+    public void setUseLowPriority(boolean useLowPriority) {
+        if (mUseLowPriority != useLowPriority) {
+            mDirtyContentViews |= (FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED);
+        }
+        mUseLowPriority = useLowPriority;
+    }
+
+    public boolean useLowPriority() {
+        return mUseLowPriority;
+    }
+
+    /**
+     * Set whether content should use group child version of its content views.
+     */
+    public void setUseChildInGroup(boolean useChildInGroup) {
+        if (mUseChildInGroup != useChildInGroup) {
+            mDirtyContentViews |= (FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED);
+        }
+        mUseChildInGroup = useChildInGroup;
+    }
+
+    public boolean useChildInGroup() {
+        return mUseChildInGroup;
+    }
+
+    /**
+     * Set whether content should use an increased height version of its contracted view.
+     */
+    public void setUseIncreasedCollapsedHeight(boolean useIncreasedHeight) {
+        if (mUseIncreasedHeight != useIncreasedHeight) {
+            mDirtyContentViews |= FLAG_CONTENT_VIEW_CONTRACTED;
+        }
+        mUseIncreasedHeight = useIncreasedHeight;
+    }
+
+    public boolean useIncreasedHeight() {
+        return mUseIncreasedHeight;
+    }
+
+    /**
+     * Set whether content should use an increased height version of its heads up view.
+     */
+    public void setUseIncreasedHeadsUpHeight(boolean useIncreasedHeadsUpHeight) {
+        if (mUseIncreasedHeadsUpHeight != useIncreasedHeadsUpHeight) {
+            mDirtyContentViews |= FLAG_CONTENT_VIEW_HEADS_UP;
+        }
+        mUseIncreasedHeadsUpHeight = useIncreasedHeadsUpHeight;
+    }
+
+    public boolean useIncreasedHeadsUpHeight() {
+        return mUseIncreasedHeadsUpHeight;
+    }
+
+    /**
+     * Require the specified content views to be bound after the rebind request.
+     *
+     * @see InflationFlag
+     */
+    public void requireContentViews(@InflationFlag int contentViews) {
+        @InflationFlag int newContentViews = contentViews &= ~mContentViews;
+        mContentViews |= contentViews;
+        mDirtyContentViews |= newContentViews;
+    }
+
+    /**
+     * Free the content view so that it will no longer be bound after the rebind request.
+     *
+     * @see InflationFlag
+     */
+    public void freeContentViews(@InflationFlag int contentViews) {
+        mContentViews &= ~contentViews;
+        mDirtyContentViews &= ~contentViews;
+    }
+
+    public @InflationFlag int getContentViews() {
+        return mContentViews;
+    }
+
+    /**
+     * Request that all content views be rebound. This may happen if, for example, the underlying
+     * layout has changed.
+     */
+    public void rebindAllContentViews() {
+        mDirtyContentViews = mContentViews;
+    }
+
+    /**
+     * Clears all dirty content views so that they no longer need to be rebound.
+     */
+    void clearDirtyContentViews() {
+        mDirtyContentViews = 0;
+    }
+
+    public @InflationFlag int getDirtyContentViews() {
+        return mDirtyContentViews;
+    }
+
+    /**
+     * Set whether all content views need to be reinflated even if cached.
+     *
+     * TODO: This should probably be a more global config on {@link NotifBindPipeline} since this
+     * generally corresponds to a Context/Configuration change that all stages should know about.
+     */
+    public void setNeedsReinflation(boolean needsReinflation) {
+        mViewsNeedReinflation = needsReinflation;
+        @InflationFlag int currentContentViews = mContentViews;
+        mDirtyContentViews |= currentContentViews;
+    }
+
+    public boolean needsReinflation() {
+        return mViewsNeedReinflation;
+    }
+
+    /**
+     * Content views that should be inflated by default for all notifications.
+     */
+    @InflationFlag private static final int DEFAULT_INFLATION_FLAGS =
+            FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED;
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
new file mode 100644
index 0000000..f124179
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
@@ -0,0 +1,118 @@
+/*
+ * 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.row;
+
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL;
+
+import android.os.RemoteException;
+import android.service.notification.StatusBarNotification;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams;
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback;
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * A stage that binds all content views for an already inflated {@link ExpandableNotificationRow}.
+ *
+ * In the farther future, the binder logic and consequently this stage should be broken into
+ * smaller stages.
+ */
+@Singleton
+public class RowContentBindStage extends BindStage<RowContentBindParams> {
+    private final NotificationRowContentBinder mBinder;
+    private final IStatusBarService mStatusBarService;
+
+    @Inject
+    RowContentBindStage(
+            NotificationRowContentBinder binder,
+            IStatusBarService statusBarService) {
+        mBinder = binder;
+        mStatusBarService = statusBarService;
+    }
+
+    @Override
+    protected void executeStage(
+            @NonNull NotificationEntry entry,
+            @NonNull ExpandableNotificationRow row,
+            @NonNull StageCallback callback) {
+        RowContentBindParams params = getStageParams(entry);
+
+        // Resolve content to bind/unbind.
+        @InflationFlag int inflationFlags = params.getContentViews();
+        @InflationFlag int invalidatedFlags = params.getDirtyContentViews();
+
+        @InflationFlag int contentToBind = invalidatedFlags & inflationFlags;
+        @InflationFlag int contentToUnbind = inflationFlags ^ FLAG_CONTENT_VIEW_ALL;
+
+        // Bind/unbind with parameters
+        mBinder.unbindContent(entry, row, contentToUnbind);
+
+        BindParams bindParams = new BindParams();
+        bindParams.isLowPriority = params.useLowPriority();
+        bindParams.isChildInGroup = params.useChildInGroup();
+        bindParams.usesIncreasedHeight = params.useIncreasedHeight();
+        bindParams.usesIncreasedHeadsUpHeight = params.useIncreasedHeadsUpHeight();
+        boolean forceInflate = params.needsReinflation();
+
+        InflationCallback inflationCallback = new InflationCallback() {
+            @Override
+            public void handleInflationException(NotificationEntry entry, Exception e) {
+                entry.setHasInflationError(true);
+                try {
+                    final StatusBarNotification sbn = entry.getSbn();
+                    mStatusBarService.onNotificationError(
+                            sbn.getPackageName(),
+                            sbn.getTag(),
+                            sbn.getId(),
+                            sbn.getUid(),
+                            sbn.getInitialPid(),
+                            e.getMessage(),
+                            sbn.getUserId());
+                } catch (RemoteException ex) {
+                }
+            }
+
+            @Override
+            public void onAsyncInflationFinished(NotificationEntry entry) {
+                entry.setHasInflationError(false);
+                getStageParams(entry).clearDirtyContentViews();
+                callback.onStageFinished(entry);
+            }
+        };
+        mBinder.cancelBind(entry, row);
+        mBinder.bindContent(entry, row, contentToBind, bindParams, forceInflate, inflationCallback);
+    }
+
+    @Override
+    protected void abortStage(
+            @NonNull NotificationEntry entry,
+            @NonNull ExpandableNotificationRow row) {
+        mBinder.cancelBind(entry, row);
+    }
+
+    @Override
+    protected RowContentBindParams newStageParams() {
+        return new RowContentBindParams();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
index 896b6e5..bdca9a4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
@@ -28,11 +28,12 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.statusbar.AlertingNotificationManager;
-import com.android.systemui.statusbar.InflationTask;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
+import com.android.systemui.statusbar.notification.row.RowContentBindParams;
+import com.android.systemui.statusbar.notification.row.RowContentBindStage;
 import com.android.systemui.statusbar.phone.NotificationGroupManager.NotificationGroup;
 import com.android.systemui.statusbar.phone.NotificationGroupManager.OnGroupChangeListener;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -67,6 +68,7 @@
     private final ArrayMap<String, PendingAlertInfo> mPendingAlerts = new ArrayMap<>();
 
     private HeadsUpManager mHeadsUpManager;
+    private final RowContentBindStage mRowContentBindStage;
     private final NotificationGroupManager mGroupManager =
             Dependency.get(NotificationGroupManager.class);
 
@@ -75,8 +77,9 @@
     private boolean mIsDozing;
 
     @Inject
-    public NotificationGroupAlertTransferHelper() {
+    public NotificationGroupAlertTransferHelper(RowContentBindStage bindStage) {
         Dependency.get(StatusBarStateController.class).addCallback(this);
+        mRowContentBindStage = bindStage;
     }
 
     /** Causes the TransferHelper to register itself as a listener to the appropriate classes. */
@@ -190,21 +193,6 @@
             }
         }
 
-        // Called when the entry's reinflation has finished. If there is an alert pending, we
-        // then show the alert.
-        @Override
-        public void onEntryReinflated(NotificationEntry entry) {
-            PendingAlertInfo alertInfo = mPendingAlerts.remove(entry.getKey());
-            if (alertInfo != null) {
-                if (alertInfo.isStillValid()) {
-                    alertNotificationWhenPossible(entry, mHeadsUpManager);
-                } else {
-                    // The transfer is no longer valid. Free the content.
-                    entry.getRow().freeContentViewWhenSafe(mHeadsUpManager.getContentFlag());
-                }
-            }
-        }
-
         @Override
         public void onEntryRemoved(
                 @Nullable NotificationEntry entry,
@@ -392,10 +380,21 @@
     private void alertNotificationWhenPossible(@NonNull NotificationEntry entry,
             @NonNull AlertingNotificationManager alertManager) {
         @InflationFlag int contentFlag = alertManager.getContentFlag();
-        if (!entry.getRow().isInflationFlagSet(contentFlag)) {
+        final RowContentBindParams params = mRowContentBindStage.getStageParams(entry);
+        if ((params.getContentViews() & contentFlag) == 0) {
             mPendingAlerts.put(entry.getKey(), new PendingAlertInfo(entry));
-            entry.getRow().setInflationFlags(contentFlag);
-            entry.getRow().inflateViews();
+            params.requireContentViews(contentFlag);
+            mRowContentBindStage.requestRebind(entry, en -> {
+                PendingAlertInfo alertInfo = mPendingAlerts.remove(entry.getKey());
+                if (alertInfo != null) {
+                    if (alertInfo.isStillValid()) {
+                        alertNotificationWhenPossible(entry, mHeadsUpManager);
+                    } else {
+                        // The transfer is no longer valid. Free the content.
+                        entry.getRow().freeContentViewWhenSafe(mHeadsUpManager.getContentFlag());
+                    }
+                }
+            });
             return;
         }
         if (alertManager.isAlerting(entry.getKey())) {
@@ -426,9 +425,9 @@
         /**
          * The notification is still pending inflation but we've decided that we no longer need
          * the content view (e.g. suppression might have changed and we decided we need to transfer
-         * back). However, there is no way to abort just this inflation if other inflation requests
-         * have started (see {@link InflationTask#supersedeTask(InflationTask)}). So instead
-         * we just flag it as aborted and free when it's inflated.
+         * back).
+         *
+         * TODO: Replace this entire structure with {@link RowContentBindStage#requestRebind)}.
          */
         boolean mAbortOnInflation;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 2907cd4..8dfcb0a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -134,6 +134,8 @@
         mBroadcastDispatcher.registerReceiver(
                 mReceiver, filter, null /* handler */, UserHandle.SYSTEM);
 
+        mSimpleUserSwitcher = shouldUseSimpleUserSwitcher();
+
         mSecondaryUserServiceIntent = new Intent(context, SystemUISecondaryUserService.class);
 
         filter = new IntentFilter();
@@ -258,22 +260,20 @@
                         && mUserManager.canAddMoreUsers();
                 boolean createIsRestricted = !addUsersWhenLocked;
 
-                if (!mSimpleUserSwitcher) {
-                    if (guestRecord == null) {
-                        if (canCreateGuest) {
-                            guestRecord = new UserRecord(null /* info */, null /* picture */,
-                                    true /* isGuest */, false /* isCurrent */,
-                                    false /* isAddUser */, createIsRestricted, canSwitchUsers);
-                            checkIfAddUserDisallowedByAdminOnly(guestRecord);
-                            records.add(guestRecord);
-                        }
-                    } else {
-                        int index = guestRecord.isCurrent ? 0 : records.size();
-                        records.add(index, guestRecord);
+                if (guestRecord == null) {
+                    if (canCreateGuest) {
+                        guestRecord = new UserRecord(null /* info */, null /* picture */,
+                                true /* isGuest */, false /* isCurrent */,
+                                false /* isAddUser */, createIsRestricted, canSwitchUsers);
+                        checkIfAddUserDisallowedByAdminOnly(guestRecord);
+                        records.add(guestRecord);
                     }
+                } else {
+                    int index = guestRecord.isCurrent ? 0 : records.size();
+                    records.add(index, guestRecord);
                 }
 
-                if (!mSimpleUserSwitcher && canCreateUser) {
+                if (canCreateUser) {
                     UserRecord addUserRecord = new UserRecord(null /* info */, null /* picture */,
                             false /* isGuest */, false /* isCurrent */, true /* isAddUser */,
                             createIsRestricted, canSwitchUsers);
@@ -562,8 +562,7 @@
 
     private final ContentObserver mSettingsObserver = new ContentObserver(new Handler()) {
         public void onChange(boolean selfChange) {
-            mSimpleUserSwitcher = Settings.Global.getInt(mContext.getContentResolver(),
-                    SIMPLE_USER_SWITCHER_GLOBAL_SETTING, 0) != 0;
+            mSimpleUserSwitcher = shouldUseSimpleUserSwitcher();
             mAddUsersWhenLocked = Settings.Global.getInt(mContext.getContentResolver(),
                     Settings.Global.ADD_USERS_WHEN_LOCKED, 0) != 0;
             refreshUsers(UserHandle.USER_NULL);
@@ -579,6 +578,7 @@
             final UserRecord u = mUsers.get(i);
             pw.print("    "); pw.println(u.toString());
         }
+        pw.println("mSimpleUserSwitcher=" + mSimpleUserSwitcher);
     }
 
     public String getCurrentUserName(Context context) {
@@ -717,6 +717,13 @@
         }
     }
 
+    private boolean shouldUseSimpleUserSwitcher() {
+        int defaultSimpleUserSwitcher = mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_expandLockScreenUserSwitcher) ? 1 : 0;
+        return Settings.Global.getInt(mContext.getContentResolver(),
+                SIMPLE_USER_SWITCHER_GLOBAL_SETTING, defaultSimpleUserSwitcher) != 0;
+    }
+
     public void startActivity(Intent intent) {
         mActivityStarter.startActivity(intent, true);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java
index a60ca62..49ada1a 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java
@@ -158,7 +158,7 @@
 
         String demoTime = "1010"; // 10:10, a classic choice of horologists
         try {
-            String[] versionParts = android.os.Build.VERSION.RELEASE.split("\\.");
+            String[] versionParts = android.os.Build.VERSION.RELEASE_OR_CODENAME.split("\\.");
             int majorVersion = Integer.valueOf(versionParts[0]);
             demoTime = String.format("%02d00", majorVersion % 24);
         } catch (IllegalArgumentException ex) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt
new file mode 100644
index 0000000..70bcc21
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt
@@ -0,0 +1,320 @@
+package com.android.systemui.util
+
+import android.graphics.Rect
+import android.util.Log
+import com.android.systemui.util.FloatingContentCoordinator.FloatingContent
+import java.util.HashMap
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/** Tag for debug logging. */
+private const val TAG = "FloatingCoordinator"
+
+/**
+ * Coordinates the positions and movement of floating content, such as PIP and Bubbles, to ensure
+ * that they don't overlap. If content does overlap due to content appearing or moving, the
+ * coordinator will ask content to move to resolve the conflict.
+ *
+ * After implementing [FloatingContent], content should call [onContentAdded] to begin coordination.
+ * Subsequently, call [onContentMoved] whenever the content moves, and the coordinator will move
+ * other content out of the way. [onContentRemoved] should be called when the content is removed or
+ * no longer visible.
+ */
+@Singleton
+class FloatingContentCoordinator @Inject constructor() {
+
+    /**
+     * Represents a piece of floating content, such as PIP or the Bubbles stack. Provides methods
+     * that allow the [FloatingContentCoordinator] to determine the current location of the content,
+     * as well as the ability to ask it to move out of the way of other content.
+     *
+     * The default implementation of [calculateNewBoundsOnOverlap] moves the content up or down,
+     * depending on the position of the conflicting content. You can override this method if you
+     * want your own custom conflict resolution logic.
+     */
+    interface FloatingContent {
+
+        /**
+         * Return the bounds claimed by this content. This should include the bounds occupied by the
+         * content itself, as well as any padding, if desired. The coordinator will ensure that no
+         * other content is located within these bounds.
+         *
+         * If the content is animating, this method should return the bounds to which the content is
+         * animating. If that animation is cancelled, or updated, be sure that your implementation
+         * of this method returns the appropriate bounds, and call [onContentMoved] so that the
+         * coordinator moves other content out of the way.
+         */
+        fun getFloatingBoundsOnScreen(): Rect
+
+        /**
+         * Return the area within which this floating content is allowed to move. When resolving
+         * conflicts, the coordinator will never ask your content to move to a position where any
+         * part of the content would be out of these bounds.
+         */
+        fun getAllowedFloatingBoundsRegion(): Rect
+
+        /**
+         * Called when the coordinator needs this content to move to the given bounds. It's up to
+         * you how to do that.
+         *
+         * Note that if you start an animation to these bounds, [getFloatingBoundsOnScreen] should
+         * return the destination bounds, not the in-progress animated bounds. This is so the
+         * coordinator knows where floating content is going to be and can resolve conflicts
+         * accordingly.
+         */
+        fun moveToBounds(bounds: Rect)
+
+        /**
+         * Called by the coordinator when it needs to find a new home for this floating content,
+         * because a new or moving piece of content is now overlapping with it.
+         *
+         * [findAreaForContentVertically] and [findAreaForContentAboveOrBelow] are helpful utility
+         * functions that will find new bounds for your content automatically. Unless you require
+         * specific conflict resolution logic, these should be sufficient. By default, this method
+         * delegates to [findAreaForContentVertically].
+         *
+         * @param overlappingContentBounds The bounds of the other piece of content, which
+         * necessitated this content's relocation. Your new position must not overlap with these
+         * bounds.
+         * @param otherContentBounds The bounds of any other pieces of floating content. Your new
+         * position must not overlap with any of these either. These bounds are guaranteed to be
+         * non-overlapping.
+         * @return The new bounds for this content.
+         */
+        @JvmDefault
+        fun calculateNewBoundsOnOverlap(
+            overlappingContentBounds: Rect,
+            otherContentBounds: List<Rect>
+        ): Rect {
+            return findAreaForContentVertically(
+                    getFloatingBoundsOnScreen(),
+                    overlappingContentBounds,
+                    otherContentBounds,
+                    getAllowedFloatingBoundsRegion())
+        }
+    }
+
+    /** The bounds of all pieces of floating content added to the coordinator. */
+    private val allContentBounds: MutableMap<FloatingContent, Rect> = HashMap()
+
+    /**
+     * Makes the coordinator aware of a new piece of floating content, and moves any existing
+     * content out of the way, if necessary.
+     *
+     * If you don't want your new content to move existing content, use [getOccupiedBounds] to find
+     * an unoccupied area, and move the content there before calling this method.
+     */
+    fun onContentAdded(newContent: FloatingContent) {
+        updateContentBounds()
+        allContentBounds[newContent] = newContent.getFloatingBoundsOnScreen()
+        maybeMoveConflictingContent(newContent)
+    }
+
+    /**
+     * Called to notify the coordinator that a piece of floating content has moved (or is animating)
+     * to a new position, and that any conflicting floating content should be moved out of the way.
+     *
+     * The coordinator will call [FloatingContent.getFloatingBoundsOnScreen] to find the new bounds
+     * for the moving content. If you're animating the content, be sure that your implementation of
+     * getFloatingBoundsOnScreen returns the bounds to which it's animating, not the content's
+     * current bounds.
+     *
+     * If the animation moving this content is cancelled or updated, you'll need to call this method
+     * again, to ensure that content is moved out of the way of the latest bounds.
+     *
+     * @param content The content that has moved.
+     */
+    @JvmOverloads
+    fun onContentMoved(content: FloatingContent) {
+        if (!allContentBounds.containsKey(content)) {
+            Log.wtf(TAG, "Received onContentMoved call before onContentAdded! " +
+                    "This should never happen.")
+            return
+        }
+
+        updateContentBounds()
+        maybeMoveConflictingContent(content)
+    }
+
+    /**
+     * Called to notify the coordinator that a piece of floating content has been removed or is no
+     * longer visible.
+     */
+    fun onContentRemoved(removedContent: FloatingContent) {
+        allContentBounds.remove(removedContent)
+    }
+
+    /**
+     * Returns a set of Rects that represent the bounds of all of the floating content on the
+     * screen.
+     *
+     * [onContentAdded] will move existing content out of the way if the added content intersects
+     * existing content. That's fine - but if your specific starting position is not important, you
+     * can use this function to find unoccupied space for your content before calling
+     * [onContentAdded], so that moving existing content isn't necessary.
+     */
+    fun getOccupiedBounds(): Collection<Rect> {
+        return allContentBounds.values
+    }
+
+    /**
+     * Identifies any pieces of content that are now overlapping with the given content, and asks
+     * them to move out of the way.
+     */
+    private fun maybeMoveConflictingContent(fromContent: FloatingContent) {
+        val conflictingNewBounds = allContentBounds[fromContent]!!
+        allContentBounds
+                // Filter to content that intersects with the new bounds. That's content that needs
+                // to move.
+                .filter { (content, bounds) ->
+                    content != fromContent && Rect.intersects(conflictingNewBounds, bounds) }
+                // Tell that content to get out of the way, and save the bounds it says it's moving
+                // (or animating) to.
+                .forEach { (content, bounds) ->
+                    content.moveToBounds(
+                            content.calculateNewBoundsOnOverlap(
+                                    conflictingNewBounds,
+                                    // Pass all of the content bounds except the bounds of the
+                                    // content we're asking to move, and the conflicting new bounds
+                                    // (since those are passed separately).
+                                    otherContentBounds = allContentBounds.values
+                                            .minus(bounds)
+                                            .minus(conflictingNewBounds)))
+                    allContentBounds[content] = content.getFloatingBoundsOnScreen()
+                }
+    }
+
+    /**
+     * Update [allContentBounds] by calling [FloatingContent.getFloatingBoundsOnScreen] for all
+     * content and saving the result.
+     */
+    private fun updateContentBounds() {
+        allContentBounds.keys.forEach { allContentBounds[it] = it.getFloatingBoundsOnScreen() }
+    }
+
+    companion object {
+        /**
+         * Finds new bounds for the given content, either above or below its current position. The
+         * new bounds won't intersect with the newly overlapping rect or the exclusion rects, and
+         * will be within the allowed bounds unless no possible position exists.
+         *
+         * You can use this method to help find a new position for your content when the coordinator
+         * calls [FloatingContent.moveToAreaExcluding].
+         *
+         * @param contentRect The bounds of the content for which we're finding a new home.
+         * @param newlyOverlappingRect The bounds of the content that forced this relocation by
+         * intersecting with the content we now need to move. If the overlapping content is
+         * overlapping the top half of this content, we'll try to move this content downward if
+         * possible (since the other content is 'pushing' it down), and vice versa.
+         * @param exclusionRects Any other areas that we need to avoid when finding a new home for
+         * the content. These areas must be non-overlapping with each other.
+         * @param allowedBounds The area within which we're allowed to find new bounds for the
+         * content.
+         * @return New bounds for the content that don't intersect the exclusion rects or the
+         * newly overlapping rect, and that is within bounds unless no possible in-bounds position
+         * exists.
+         */
+        @JvmStatic
+        fun findAreaForContentVertically(
+            contentRect: Rect,
+            newlyOverlappingRect: Rect,
+            exclusionRects: Collection<Rect>,
+            allowedBounds: Rect
+        ): Rect {
+            // If the newly overlapping Rect's center is above the content's center, we'll prefer to
+            // find a space for this content that is below the overlapping content, since it's
+            // 'pushing' it down. This may not be possible due to to screen bounds, in which case
+            // we'll find space in the other direction.
+            val overlappingContentPushingDown =
+                    newlyOverlappingRect.centerY() < contentRect.centerY()
+
+            // Filter to exclusion rects that are above or below the content that we're finding a
+            // place for. Then, split into two lists - rects above the content, and rects below it.
+            var (rectsToAvoidAbove, rectsToAvoidBelow) = exclusionRects
+                    .filter { rectToAvoid -> rectsIntersectVertically(rectToAvoid, contentRect) }
+                    .partition { rectToAvoid -> rectToAvoid.top < contentRect.top }
+
+            // Lazily calculate the closest possible new tops for the content, above and below its
+            // current location.
+            val newContentBoundsAbove by lazy { findAreaForContentAboveOrBelow(
+                    contentRect,
+                    exclusionRects = rectsToAvoidAbove.plus(newlyOverlappingRect),
+                    findAbove = true) }
+            val newContentBoundsBelow by lazy { findAreaForContentAboveOrBelow(
+                    contentRect,
+                    exclusionRects = rectsToAvoidBelow.plus(newlyOverlappingRect),
+                    findAbove = false) }
+
+            val positionAboveInBounds by lazy { allowedBounds.contains(newContentBoundsAbove) }
+            val positionBelowInBounds by lazy { allowedBounds.contains(newContentBoundsBelow) }
+
+            // Use the 'below' position if the content is being overlapped from the top, unless it's
+            // out of bounds. Also use it if the content is being overlapped from the bottom, but
+            // the 'above' position is out of bounds. Otherwise, use the 'above' position.
+            val usePositionBelow =
+                    overlappingContentPushingDown && positionBelowInBounds ||
+                            !overlappingContentPushingDown && !positionAboveInBounds
+
+            // Return the content rect, but offset to reflect the new position.
+            return if (usePositionBelow) newContentBoundsBelow else newContentBoundsAbove
+        }
+
+        /**
+         * Finds a new position for the given content, either above or below its current position
+         * depending on whether [findAbove] is true or false, respectively. This new position will
+         * not intersect with any of the [exclusionRects].
+         *
+         * This method is useful as a helper method for implementing your own conflict resolution
+         * logic. Otherwise, you'd want to use [findAreaForContentVertically], which takes screen
+         * bounds and conflicting bounds' location into account when deciding whether to move to new
+         * bounds above or below the current bounds.
+         *
+         * @param contentRect The content we're finding an area for.
+         * @param exclusionRects The areas we need to avoid when finding a new area for the content.
+         * These areas must be non-overlapping with each other.
+         * @param findAbove Whether we are finding an area above the content's current position,
+         * rather than an area below it.
+         */
+        fun findAreaForContentAboveOrBelow(
+            contentRect: Rect,
+            exclusionRects: Collection<Rect>,
+            findAbove: Boolean
+        ): Rect {
+            // Sort the rects, since we want to move the content as little as possible. We'll
+            // start with the rects closest to the content and move outward. If we're finding an
+            // area above the content, that means we sort in reverse order to search the rects
+            // from highest to lowest y-value.
+            val sortedExclusionRects =
+                    exclusionRects.sortedBy { if (findAbove) -it.top else it.top }
+
+            val proposedNewBounds = Rect(contentRect)
+            for (exclusionRect in sortedExclusionRects) {
+                // If the proposed new bounds don't intersect with this exclusion rect, that
+                // means there's room for the content here. We know this because the rects are
+                // sorted and non-overlapping, so any subsequent exclusion rects would be higher
+                // (or lower) than this one and can't possibly intersect if this one doesn't.
+                if (!Rect.intersects(proposedNewBounds, exclusionRect)) {
+                    break
+                } else {
+                    // Otherwise, we need to keep searching for new bounds. If we're finding an
+                    // area above, propose new bounds that place the content just above the
+                    // exclusion rect. If we're finding an area below, propose new bounds that
+                    // place the content just below the exclusion rect.
+                    val verticalOffset =
+                            if (findAbove) -contentRect.height() else exclusionRect.height()
+                    proposedNewBounds.offsetTo(
+                            proposedNewBounds.left,
+                            exclusionRect.top + verticalOffset)
+                }
+            }
+
+            return proposedNewBounds
+        }
+
+        /** Returns whether or not the two Rects share any of the same space on the X axis. */
+        private fun rectsIntersectVertically(r1: Rect, r2: Rect): Boolean {
+            return (r1.left >= r2.left && r1.left <= r2.right) ||
+                    (r1.right <= r2.right && r1.right >= r2.left)
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
index 1954b39..0e9a245 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
@@ -39,7 +39,7 @@
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 import android.testing.ViewUtils;
-import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
 import android.view.SurfaceView;
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
@@ -54,7 +54,6 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
 
 @RunWithLooper
 @RunWith(AndroidTestingRunner.class)
@@ -77,8 +76,8 @@
     private KeyguardSecurityCallback mKeyguardCallback;
     @Mock
     private KeyguardUpdateMonitor mUpdateMonitor;
-    @Spy
-    private StubTransaction mTransaction;
+    @Mock
+    private SurfaceControlViewHost.SurfacePackage mSurfacePackage;
 
     @Before
     public void setUp() {
@@ -97,21 +96,20 @@
         when(mKeyguardClient.asBinder()).thenReturn(mKeyguardClient);
 
         mTestController = new AdminSecondaryLockScreenController(
-                mContext, mParent, mUpdateMonitor, mKeyguardCallback, mHandler, mTransaction);
+                mContext, mParent, mUpdateMonitor, mKeyguardCallback, mHandler);
     }
 
     @Test
     public void testShow() throws Exception {
         doAnswer(invocation -> {
             IKeyguardCallback callback = (IKeyguardCallback) invocation.getArguments()[1];
-            callback.onSurfaceControlCreated(new SurfaceControl());
+            callback.onRemoteContentReady(mSurfacePackage);
             return null;
         }).when(mKeyguardClient).onSurfaceReady(any(), any(IKeyguardCallback.class));
 
         mTestController.show(mServiceIntent);
 
         verifySurfaceReady();
-        verify(mTransaction).reparent(any(), any());
         assertThat(mContext.isBound(mComponentName)).isTrue();
     }
 
@@ -133,7 +131,7 @@
         // Show the view first, then hide.
         doAnswer(invocation -> {
             IKeyguardCallback callback = (IKeyguardCallback) invocation.getArguments()[1];
-            callback.onSurfaceControlCreated(new SurfaceControl());
+            callback.onRemoteContentReady(mSurfacePackage);
             return null;
         }).when(mKeyguardClient).onSurfaceReady(any(), any(IKeyguardCallback.class));
 
@@ -189,19 +187,4 @@
         verify(mKeyguardCallback).dismiss(true, TARGET_USER_ID);
         assertThat(mContext.isBound(mComponentName)).isFalse();
     }
-
-    /**
-     * Stubbed {@link SurfaceControl.Transaction} class that can be used when unit testing to
-     * avoid calls to native code.
-     */
-    private class StubTransaction extends SurfaceControl.Transaction {
-        @Override
-        public void apply() {
-        }
-
-        @Override
-        public SurfaceControl.Transaction reparent(SurfaceControl sc, SurfaceControl newParent) {
-            return this;
-        }
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
index 364ee66..ffe8c28 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
@@ -31,8 +31,8 @@
 
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.statusbar.NotificationMediaManager;
-import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 import com.android.systemui.util.Assert;
 
 import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
index f264259..c6c7b87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
@@ -36,6 +36,7 @@
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricPrompt;
 import android.os.Bundle;
+import android.os.IBinder;
 import android.os.UserManager;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
@@ -43,6 +44,7 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.WindowManager;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.ScrollView;
@@ -175,6 +177,28 @@
         assertEquals(Utils.CREDENTIAL_PATTERN, mAuthContainer.mCredentialView.mCredentialType);
     }
 
+    @Test
+    public void testCredentialUI_disablesClickingOnBackground() {
+        // In the credential view, clicking on the background (to cancel authentication) is not
+        // valid. Thus, the listener should be null, and it should not be in the accessibility
+        // hierarchy.
+        initializeContainer(Authenticators.DEVICE_CREDENTIAL);
+
+        mAuthContainer.onAttachedToWindowInternal();
+
+        verify(mAuthContainer.mBackgroundView).setOnClickListener(eq(null));
+        verify(mAuthContainer.mBackgroundView).setImportantForAccessibility(
+                eq(View.IMPORTANT_FOR_ACCESSIBILITY_NO));
+    }
+
+    @Test
+    public void testLayoutParams_hasSecureWindowFlag() {
+        final IBinder windowToken = mock(IBinder.class);
+        final WindowManager.LayoutParams layoutParams =
+                AuthContainerView.getLayoutParams(windowToken);
+        assertTrue((layoutParams.flags & WindowManager.LayoutParams.FLAG_SECURE) != 0);
+    }
+
     private void initializeContainer(int authenticators) {
         AuthContainerView.Config config = new AuthContainerView.Config();
         config.mContext = mContext;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index dcaf4ec..d7f0f50 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -62,7 +62,6 @@
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationRemoveInterceptor;
-import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.SuperStatusBarViewFactory;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
@@ -72,6 +71,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
index c9f5b40..f40fc94 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
@@ -39,10 +39,10 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.bubbles.BubbleData.TimeSource;
-import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 
 import com.google.common.collect.ImmutableList;
 
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 897091f..e3bcdc8 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
@@ -32,12 +32,13 @@
 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.management.ControlsListingController
 import com.android.systemui.controls.ui.ControlsUiController
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.time.FakeSystemClock
 import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
 import org.junit.Before
 import org.junit.Test
@@ -82,7 +83,7 @@
     private lateinit var broadcastReceiverCaptor: ArgumentCaptor<BroadcastReceiver>
 
     private lateinit var delayableExecutor: FakeExecutor
-    private lateinit var controller: ControlsController
+    private lateinit var controller: ControlsControllerImpl
 
     companion object {
         fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
@@ -416,5 +417,70 @@
         verify(listingController).changeUser(UserHandle.of(otherUser))
         assertTrue(controller.getFavoriteControls().isEmpty())
         assertEquals(otherUser, controller.currentUserId)
+        assertTrue(controller.available)
     }
-}
\ No newline at end of file
+
+    @Test
+    fun testDisableFeature_notAvailable() {
+        Settings.Secure.putIntForUser(mContext.contentResolver,
+                ControlsControllerImpl.CONTROLS_AVAILABLE, 0, user)
+        controller.settingObserver.onChange(false, ControlsControllerImpl.URI, 0)
+        assertFalse(controller.available)
+    }
+
+    @Test
+    fun testDisableFeature_clearFavorites() {
+        controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+        assertFalse(controller.getFavoriteControls().isEmpty())
+
+        Settings.Secure.putIntForUser(mContext.contentResolver,
+                ControlsControllerImpl.CONTROLS_AVAILABLE, 0, user)
+        controller.settingObserver.onChange(false, ControlsControllerImpl.URI, user)
+        assertTrue(controller.getFavoriteControls().isEmpty())
+    }
+
+    @Test
+    fun testDisableFeature_noChangeForNotCurrentUser() {
+        controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+        Settings.Secure.putIntForUser(mContext.contentResolver,
+                ControlsControllerImpl.CONTROLS_AVAILABLE, 0, otherUser)
+        controller.settingObserver.onChange(false, ControlsControllerImpl.URI, otherUser)
+
+        assertTrue(controller.available)
+        assertFalse(controller.getFavoriteControls().isEmpty())
+    }
+
+    @Test
+    fun testCorrectUserSettingOnUserChange() {
+        Settings.Secure.putIntForUser(mContext.contentResolver,
+                ControlsControllerImpl.CONTROLS_AVAILABLE, 0, otherUser)
+
+        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)
+
+        assertFalse(controller.available)
+    }
+
+    @Test
+    fun testCountFavoritesForComponent_singleComponent() {
+        controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+
+        assertEquals(1, controller.countFavoritesForComponent(TEST_COMPONENT))
+        assertEquals(0, controller.countFavoritesForComponent(TEST_COMPONENT_2))
+    }
+
+    @Test
+    fun testCountFavoritesForComponent_multipleComponents() {
+        controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+        controller.changeFavoriteStatus(TEST_CONTROL_INFO_2, true)
+
+        assertEquals(1, controller.countFavoritesForComponent(TEST_COMPONENT))
+        assertEquals(1, controller.countFavoritesForComponent(TEST_COMPONENT_2))
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/RichEventTest.java b/packages/SystemUI/tests/src/com/android/systemui/log/RichEventTest.java
deleted file mode 100644
index 4a90bb9..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/log/RichEventTest.java
+++ /dev/null
@@ -1,69 +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.log;
-
-import static junit.framework.Assert.assertEquals;
-
-import android.testing.AndroidTestingRunner;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-
-import junit.framework.Assert;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class RichEventTest extends SysuiTestCase {
-
-    private static final int TOTAL_EVENT_TYPES = 1;
-
-    @Test
-    public void testCreateRichEvent_invalidType() {
-        try {
-            // indexing for events starts at 0, so TOTAL_EVENT_TYPES is an invalid type
-            new TestableRichEvent(Event.DEBUG, TOTAL_EVENT_TYPES, "msg");
-        } catch (IllegalArgumentException e) {
-            // expected
-            return;
-        }
-
-        Assert.fail("Expected an invalidArgumentException since the event type was invalid.");
-    }
-
-    @Test
-    public void testCreateRichEvent() {
-        final int eventType = 0;
-        RichEvent e = new TestableRichEvent(Event.DEBUG, eventType, "msg");
-        assertEquals(e.getType(), eventType);
-    }
-
-    class TestableRichEvent extends RichEvent {
-        TestableRichEvent(int logLevel, int type, String reason) {
-            init(logLevel, type, reason);
-        }
-
-        @Override
-        public String[] getEventLabels() {
-            return new String[]{"ACTION_NAME"};
-        }
-    }
-
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/SysuiLogTest.java b/packages/SystemUI/tests/src/com/android/systemui/log/SysuiLogTest.java
deleted file mode 100644
index e7b317e..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/log/SysuiLogTest.java
+++ /dev/null
@@ -1,111 +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.log;
-
-import static junit.framework.Assert.assertEquals;
-
-import android.testing.AndroidTestingRunner;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.DumpController;
-import com.android.systemui.SysuiTestCase;
-
-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)
-public class SysuiLogTest extends SysuiTestCase {
-    private static final String TEST_ID = "TestLogger";
-    private static final String TEST_MSG = "msg";
-    private static final int MAX_LOGS = 5;
-
-    @Mock
-    private DumpController mDumpController;
-    private SysuiLog<Event> mSysuiLog;
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-    }
-
-    @Test
-    public void testLogDisabled_noLogsWritten() {
-        mSysuiLog = new TestSysuiLog(mDumpController, TEST_ID, MAX_LOGS, false);
-        assertEquals(null, mSysuiLog.mTimeline);
-
-        mSysuiLog.log(createEvent(TEST_MSG));
-        assertEquals(null, mSysuiLog.mTimeline);
-    }
-
-    @Test
-    public void testLogEnabled_logWritten() {
-        mSysuiLog = new TestSysuiLog(mDumpController, TEST_ID, MAX_LOGS, true);
-        assertEquals(0, mSysuiLog.mTimeline.size());
-
-        mSysuiLog.log(createEvent(TEST_MSG));
-        assertEquals(1, mSysuiLog.mTimeline.size());
-    }
-
-    @Test
-    public void testMaxLogs() {
-        mSysuiLog = new TestSysuiLog(mDumpController, TEST_ID, MAX_LOGS, true);
-        assertEquals(mSysuiLog.mTimeline.size(), 0);
-
-        for (int i = 0; i < MAX_LOGS + 1; i++) {
-            mSysuiLog.log(createEvent(TEST_MSG + i));
-        }
-
-        assertEquals(MAX_LOGS, mSysuiLog.mTimeline.size());
-
-        // check the first message (msg0) was replaced with msg1:
-        assertEquals(TEST_MSG + "1", mSysuiLog.mTimeline.getFirst().getMessage());
-    }
-
-    @Test
-    public void testRecycleLogs() {
-        // GIVEN a SysuiLog with one log
-        mSysuiLog = new TestSysuiLog(mDumpController, TEST_ID, MAX_LOGS, true);
-        Event e = createEvent(TEST_MSG); // msg
-        mSysuiLog.log(e); // Logs: [msg]
-
-        Event recycledEvent = null;
-        // WHEN we add MAX_LOGS after the first log
-        for (int i = 0; i < MAX_LOGS; i++) {
-            recycledEvent = mSysuiLog.log(createEvent(TEST_MSG + i));
-        }
-        // Logs: [msg1, msg2, msg3, msg4]
-
-        // THEN we see the recycledEvent is e
-        assertEquals(e, recycledEvent);
-    }
-
-    private Event createEvent(String msg) {
-        return new Event().init(msg);
-    }
-
-    public class TestSysuiLog extends SysuiLog<Event> {
-        protected TestSysuiLog(DumpController dumpController, String id, int maxLogs,
-                boolean enabled) {
-            super(dumpController, id, maxLogs, enabled, false);
-        }
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index 0a3bc6d..1d4b4be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -53,8 +53,8 @@
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.KeyguardUpdateMonitor.BatteryStatus;
 import com.android.settingslib.Utils;
+import com.android.settingslib.fuelgauge.BatteryStatus;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dock.DockManager;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
index 63c911b5..60163f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
@@ -49,6 +49,7 @@
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.stack.ForegroundServiceSectionController;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java
index 4103ede..9d667a9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java
@@ -26,8 +26,8 @@
 import android.widget.FrameLayout;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 
 import org.junit.Assert;
 import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index 20c67fa..07f6936 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -79,14 +79,14 @@
 import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
-import com.android.systemui.statusbar.notification.logging.NotifLog;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.NotifRemoteViewCache;
-import com.android.systemui.statusbar.notification.row.NotificationContentInflater;
+import com.android.systemui.statusbar.notification.row.NotifBindPipeline;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
+import com.android.systemui.statusbar.notification.row.RowContentBindParams;
+import com.android.systemui.statusbar.notification.row.RowContentBindStage;
 import com.android.systemui.statusbar.notification.row.RowInflaterTask;
 import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -99,6 +99,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -137,7 +138,7 @@
     @Mock private NotificationRemoteInputManager mRemoteInputManager;
     @Mock private DeviceProvisionedController mDeviceProvisionedController;
     @Mock private RowInflaterTask mAsyncInflationTask;
-    @Mock private NotifLog mNotifLog;
+    @Mock private NotificationEntryManagerLogger mLogger;
     @Mock private FeatureFlags mFeatureFlags;
     @Mock private LeakDetector mLeakDetector;
     @Mock private ActivatableNotificationViewController mActivatableNotificationViewController;
@@ -206,20 +207,20 @@
 
         mEntry.expandedIcon = mock(StatusBarIconView.class);
 
-        NotificationContentInflater contentBinder = new NotificationContentInflater(
-                mock(NotifRemoteViewCache.class),
-                mRemoteInputManager);
-        contentBinder.setInflateSynchronously(true);
-
         when(mNotificationRowComponentBuilder.activatableNotificationView(any()))
                 .thenReturn(mNotificationRowComponentBuilder);
         when(mNotificationRowComponentBuilder.build()).thenReturn(
                 () -> mActivatableNotificationViewController);
+
+        RowContentBindStage bindStage = mock(RowContentBindStage.class);
+        when(bindStage.getStageParams(any())).thenReturn(new RowContentBindParams());
+
         NotificationRowBinderImpl notificationRowBinder =
                 new NotificationRowBinderImpl(mContext,
                         mRemoteInputManager,
                         mLockscreenUserManager,
-                        contentBinder,
+                        mock(NotifBindPipeline.class),
+                        bindStage,
                         true, /* allowLongPress */
                         mock(KeyguardBypassController.class),
                         mock(StatusBarStateController.class),
@@ -232,14 +233,14 @@
         when(mFeatureFlags.isNewNotifPipelineEnabled()).thenReturn(false);
         when(mFeatureFlags.isNewNotifPipelineRenderingEnabled()).thenReturn(false);
         mEntryManager = new TestableNotificationEntryManager(
-                mNotifLog,
+                mLogger,
                 mGroupManager,
                 new NotificationRankingManager(
                         () -> mock(NotificationMediaManager.class),
                         mGroupManager,
                         mHeadsUpManager,
                         mock(NotificationFilter.class),
-                        mNotifLog,
+                        mLogger,
                         mock(NotificationSectionsFeatureManager.class),
                         mock(PeopleNotificationIdentifier.class),
                         mock(HighPriorityProvider.class)),
@@ -269,7 +270,10 @@
         mEntry.abortTask();
     }
 
+    // TODO: These tests are closer to functional tests and we should move them to their own file.
+    // and also strip some of the verifies that make the test too complex
     @Test
+    @Ignore
     public void testAddNotification() throws Exception {
         TestableLooper.get(this).processAllMessages();
 
@@ -306,6 +310,7 @@
     }
 
     @Test
+    @Ignore
     public void testUpdateNotification() throws Exception {
         TestableLooper.get(this).processAllMessages();
 
@@ -331,6 +336,7 @@
     }
 
     @Test
+    @Ignore
     public void testUpdateNotification_prePostEntryOrder() throws Exception {
         TestableLooper.get(this).processAllMessages();
 
@@ -399,7 +405,6 @@
         setSmartActions(mEntry.getKey(), new ArrayList<>(Arrays.asList(createAction())));
 
         mEntryManager.updateNotificationRanking(mRankingMap);
-        verify(mRow).setEntry(eq(mEntry));
         assertEquals(1, mEntry.getSmartActions().size());
         assertEquals("action", mEntry.getSmartActions().get(0).title);
         verify(mEntryListener).onNotificationRankingUpdated(mRankingMap);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
index 5aed61b..1116a33 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
@@ -42,11 +42,11 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.ShadeController;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt
index 7431459..0e730e5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt
@@ -22,7 +22,6 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 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.notification.stack.NotificationListContainer
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
 import com.android.systemui.statusbar.phone.NotificationGroupManager
@@ -34,7 +33,7 @@
  * Enable some test capabilities for NEM without making everything public on the base class
  */
 class TestableNotificationEntryManager(
-    log: NotifLog,
+    logger: NotificationEntryManagerLogger,
     gm: NotificationGroupManager,
     rm: NotificationRankingManager,
     ke: KeyguardEnvironment,
@@ -43,13 +42,13 @@
     notificationRemoteInputManagerLazy: dagger.Lazy<NotificationRemoteInputManager>,
     leakDetector: LeakDetector,
     fgsFeatureController: ForegroundServiceDismissalFeatureController
-) : NotificationEntryManager(log, gm, rm, ke, ff, rb,
+) : NotificationEntryManager(logger, gm, rm, ke, ff, rb,
         notificationRemoteInputManagerLazy, leakDetector, fgsFeatureController) {
 
     public var countDownLatch: CountDownLatch = CountDownLatch(1)
 
-    override fun onAsyncInflationFinished(entry: NotificationEntry?, inflatedFlags: Int) {
-        super.onAsyncInflationFinished(entry, inflatedFlags)
+    override fun onAsyncInflationFinished(entry: NotificationEntry) {
+        super.onAsyncInflationFinished(entry)
         countDownLatch.countDown()
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
index 7ab4846..c6b496d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
@@ -27,10 +27,10 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking
 import com.android.systemui.statusbar.NotificationMediaManager
+import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger
 import com.android.systemui.statusbar.notification.NotificationFilter
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
-import com.android.systemui.statusbar.notification.logging.NotifLog
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
 import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING
 import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT
@@ -62,7 +62,7 @@
                 mock(NotificationGroupManager::class.java),
                 mock(HeadsUpManager::class.java),
                 mock(NotificationFilter::class.java),
-                mock(NotifLog::class.java),
+                mock(NotificationEntryManagerLogger::class.java),
                 mock(NotificationSectionsFeatureManager::class.java),
                 personNotificationIdentifier,
                 HighPriorityProvider(personNotificationIdentifier)
@@ -189,7 +189,7 @@
         groupManager: NotificationGroupManager,
         headsUpManager: HeadsUpManager,
         filter: NotificationFilter,
-        notifLog: NotifLog,
+        logger: NotificationEntryManagerLogger,
         sectionsFeatureManager: NotificationSectionsFeatureManager,
         peopleNotificationIdentifier: PeopleNotificationIdentifier,
         highPriorityProvider: HighPriorityProvider
@@ -198,7 +198,7 @@
         groupManager,
         headsUpManager,
         filter,
-        notifLog,
+        logger,
         sectionsFeatureManager,
         peopleNotificationIdentifier,
         highPriorityProvider
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index 3d79ce1..d8cf6ed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -21,7 +21,6 @@
 import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
-import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -51,7 +50,6 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer;
 
@@ -147,15 +145,6 @@
     }
 
     @Test
-    public void setNeedsRedactionSetsInflationFlag() throws Exception {
-        ExpandableNotificationRow row = mNotificationTestHelper.createRow();
-
-        row.setNeedsRedaction(true);
-
-        assertTrue(row.isInflationFlagSet(FLAG_CONTENT_VIEW_PUBLIC));
-    }
-
-    @Test
     public void setNeedsRedactionFreesViewWhenFalse() throws Exception {
         ExpandableNotificationRow row = mNotificationTestHelper.createRow(FLAG_CONTENT_VIEW_ALL);
         row.setNeedsRedaction(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java
new file mode 100644
index 0000000..8f9f65d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java
@@ -0,0 +1,158 @@
+/*
+ * 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.statusbar.notification.row;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.annotation.NonNull;
+import androidx.core.os.CancellationSignal;
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.notification.NotificationEntryListener;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class NotifBindPipelineTest extends SysuiTestCase {
+
+    private NotifBindPipeline mBindPipeline;
+    private TestBindStage mStage = new TestBindStage();
+
+    @Mock private NotificationEntry mEntry;
+    @Mock private ExpandableNotificationRow mRow;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        NotificationEntryManager entryManager = mock(NotificationEntryManager.class);
+
+        mBindPipeline = new NotifBindPipeline(entryManager);
+        mBindPipeline.setStage(mStage);
+
+        ArgumentCaptor<NotificationEntryListener> entryListenerCaptor =
+                ArgumentCaptor.forClass(NotificationEntryListener.class);
+        verify(entryManager).addNotificationEntryListener(entryListenerCaptor.capture());
+        NotificationEntryListener entryListener = entryListenerCaptor.getValue();
+
+        entryListener.onPendingEntryAdded(mEntry);
+    }
+
+    @Test
+    public void testCallbackCalled() {
+        // GIVEN a bound row
+        mBindPipeline.manageRow(mEntry, mRow);
+
+        // WHEN content is invalidated
+        BindCallback callback = mock(BindCallback.class);
+        mStage.requestRebind(mEntry, callback);
+
+        // WHEN stage finishes its work
+        mStage.doWorkSynchronously();
+
+        // THEN the callback is called when bind finishes
+        verify(callback).onBindFinished(mEntry);
+    }
+
+    @Test
+    public void testCallbackCancelled() {
+        // GIVEN a bound row
+        mBindPipeline.manageRow(mEntry, mRow);
+
+        // GIVEN an in-progress pipeline run
+        BindCallback callback = mock(BindCallback.class);
+        CancellationSignal signal = mStage.requestRebind(mEntry, callback);
+
+        // WHEN the callback is cancelled.
+        signal.cancel();
+
+        // WHEN the stage finishes all its work
+        mStage.doWorkSynchronously();
+
+        // THEN the callback is not called when bind finishes
+        verify(callback, never()).onBindFinished(mEntry);
+    }
+
+    @Test
+    public void testMultipleCallbacks() {
+        // GIVEN a bound row
+        mBindPipeline.manageRow(mEntry, mRow);
+
+        // WHEN the pipeline is invalidated.
+        BindCallback callback = mock(BindCallback.class);
+        mStage.requestRebind(mEntry, callback);
+
+        // WHEN the pipeline is invalidated again before the work completes.
+        BindCallback callback2 = mock(BindCallback.class);
+        mStage.requestRebind(mEntry, callback2);
+
+        // WHEN the stage finishes all work.
+        mStage.doWorkSynchronously();
+
+        // THEN both callbacks are called when the bind finishes
+        verify(callback).onBindFinished(mEntry);
+        verify(callback2).onBindFinished(mEntry);
+    }
+
+    /**
+     * Bind stage for testing where asynchronous work can be synchronously controlled.
+     */
+    private static class TestBindStage extends BindStage {
+        private List<Runnable> mExecutionRequests = new ArrayList<>();
+
+        @Override
+        protected void executeStage(@NonNull NotificationEntry entry,
+                @NonNull ExpandableNotificationRow row, @NonNull StageCallback callback) {
+            mExecutionRequests.add(() -> callback.onStageFinished(entry));
+        }
+
+        @Override
+        protected void abortStage(@NonNull NotificationEntry entry,
+                @NonNull ExpandableNotificationRow row) {
+
+        }
+
+        @Override
+        protected Object newStageParams() {
+            return null;
+        }
+
+        public void doWorkSynchronously() {
+            for (Runnable work: mExecutionRequests) {
+                work.run();
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java
index d7214f3..20cc01a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java
@@ -32,10 +32,10 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.notification.NotificationEntryListener;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -50,7 +50,7 @@
 
     private NotifRemoteViewCacheImpl mNotifRemoteViewCache;
     private NotificationEntry mEntry;
-    private NotificationEntryListener mEntryListener;
+    private NotifCollectionListener mEntryListener;
     @Mock private RemoteViews mRemoteViews;
 
     @Before
@@ -58,19 +58,17 @@
         MockitoAnnotations.initMocks(this);
         mEntry = new NotificationEntryBuilder().build();
 
-        NotificationEntryManager entryManager = mock(NotificationEntryManager.class);
-        mNotifRemoteViewCache = new NotifRemoteViewCacheImpl(entryManager);
-        ArgumentCaptor<NotificationEntryListener> entryListenerCaptor =
-                ArgumentCaptor.forClass(NotificationEntryListener.class);
-        verify(entryManager).addNotificationEntryListener(entryListenerCaptor.capture());
+        CommonNotifCollection collection = mock(CommonNotifCollection.class);
+        mNotifRemoteViewCache = new NotifRemoteViewCacheImpl(collection);
+        ArgumentCaptor<NotifCollectionListener> entryListenerCaptor =
+                ArgumentCaptor.forClass(NotifCollectionListener.class);
+        verify(collection).addCollectionListener(entryListenerCaptor.capture());
         mEntryListener = entryListenerCaptor.getValue();
+        mEntryListener.onEntryInit(mEntry);
     }
 
     @Test
     public void testPutCachedView() {
-        // GIVEN an initialized cache for an entry.
-        mEntryListener.onPendingEntryAdded(mEntry);
-
         // WHEN a notification's cached remote views is put in.
         mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews);
 
@@ -85,7 +83,6 @@
     @Test
     public void testRemoveCachedView() {
         // GIVEN a cache with a cached view.
-        mEntryListener.onPendingEntryAdded(mEntry);
         mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews);
 
         // WHEN we remove the cached view.
@@ -98,7 +95,6 @@
     @Test
     public void testClearCache() {
         // GIVEN a non-empty cache.
-        mEntryListener.onPendingEntryAdded(mEntry);
         mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews);
         mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_EXPANDED, mRemoteViews);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java
index 444a6e5..1dfe7bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java
@@ -46,7 +46,6 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
-import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.util.Assert;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index cb9da6a..8a42e5f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -49,9 +49,7 @@
 import androidx.test.filters.Suppress;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.InflationTask;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback;
@@ -200,8 +198,7 @@
                     }
 
                     @Override
-                    public void onAsyncInflationFinished(NotificationEntry entry,
-                            @InflationFlag int inflatedFlags) {
+                    public void onAsyncInflationFinished(NotificationEntry entry) {
                         countDownLatch.countDown();
                     }
                 }, mRow.getPrivateLayout(), null, null, new HashMap<>(),
@@ -219,34 +216,6 @@
         assertTrue(countDownLatch.await(500, TimeUnit.MILLISECONDS));
     }
 
-    /* Cancelling requires us to be on the UI thread otherwise we might have a race */
-    @Test
-    public void testSupersedesExistingTask() {
-        mNotificationInflater.bindContent(
-                mRow.getEntry(),
-                mRow,
-                FLAG_CONTENT_VIEW_ALL,
-                new BindParams(),
-                false /* forceInflate */,
-                null /* callback */);
-
-        // Trigger inflation of contracted only.
-        mNotificationInflater.bindContent(
-                mRow.getEntry(),
-                mRow,
-                FLAG_CONTENT_VIEW_CONTRACTED,
-                new BindParams(),
-                false /* forceInflate */,
-                null /* callback */);
-
-        InflationTask runningTask = mRow.getEntry().getRunningTask();
-        NotificationContentInflater.AsyncInflationTask asyncInflationTask =
-                (NotificationContentInflater.AsyncInflationTask) runningTask;
-        assertEquals("Successive inflations don't inherit the previous flags!",
-                FLAG_CONTENT_VIEW_ALL, asyncInflationTask.getReInflateFlags());
-        runningTask.abort();
-    }
-
     @Test
     public void doesntReapplyDisallowedRemoteView() throws Exception {
         mBuilder.setStyle(new Notification.MediaStyle());
@@ -349,8 +318,7 @@
             }
 
             @Override
-            public void onAsyncInflationFinished(NotificationEntry entry,
-                    @InflationFlag int inflatedFlags) {
+            public void onAsyncInflationFinished(NotificationEntry entry) {
                 if (expectingException) {
                     exceptionHolder.setException(new RuntimeException(
                             "Inflation finished even though there should be an error"));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
index 20a089f..f080d67 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
@@ -655,13 +655,13 @@
                 ArgumentCaptor.forClass(NotificationChannel.class);
         verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
                 anyString(), anyInt(), captor.capture());
-        assertTrue(captor.getValue().canBypassDnd());
+        assertTrue(captor.getValue().isImportantConversation());
     }
 
     @Test
     public void testFavorite_unfavorite() throws Exception {
-        mNotificationChannel.setBypassDnd(true);
-        mConversationChannel.setBypassDnd(true);
+        mNotificationChannel.setImportantConversation(true);
+        mConversationChannel.setImportantConversation(true);
 
         mNotificationInfo.bindNotification(
                 mShortcutManager,
@@ -688,7 +688,7 @@
                 ArgumentCaptor.forClass(NotificationChannel.class);
         verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
                 anyString(), anyInt(), captor.capture());
-        assertFalse(captor.getValue().canBypassDnd());
+        assertFalse(captor.getValue().isImportantConversation());
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index 4e27770..bbb6723 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -66,7 +66,6 @@
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationPresenter;
-import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
similarity index 88%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 457bbe2..3d9832d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -11,10 +11,10 @@
  * 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
+ * limitations under the License.
  */
 
-package com.android.systemui.statusbar;
+package com.android.systemui.statusbar.notification.row;
 
 import static android.app.Notification.FLAG_BUBBLE;
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
@@ -24,6 +24,7 @@
 
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 
 import android.annotation.Nullable;
 import android.app.ActivityManager;
@@ -40,17 +41,20 @@
 import android.view.LayoutInflater;
 import android.widget.RemoteViews;
 
+import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.TestableDependency;
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.bubbles.BubblesTestActivity;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.SmartReplyController;
+import com.android.systemui.statusbar.notification.NotificationEntryListener;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpansionLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener;
-import com.android.systemui.statusbar.notification.row.NotifRemoteViewCache;
-import com.android.systemui.statusbar.notification.row.NotificationContentInflater;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -58,6 +62,8 @@
 import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.tests.R;
 
+import org.mockito.ArgumentCaptor;
+
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -82,6 +88,9 @@
     private final NotificationGroupManager mGroupManager;
     private ExpandableNotificationRow mRow;
     private HeadsUpManagerPhone mHeadsUpManager;
+    private final NotifBindPipeline mBindPipeline;
+    private final NotificationEntryListener mBindPipelineEntryListener;
+    private final RowContentBindStage mBindStage;
 
     public NotificationTestHelper(Context context, TestableDependency dependency) {
         mContext = context;
@@ -95,6 +104,23 @@
                 mock(KeyguardBypassController.class));
         mHeadsUpManager.setUp(null, mGroupManager, null, null);
         mGroupManager.setHeadsUpManager(mHeadsUpManager);
+
+
+        NotificationContentInflater contentBinder = new NotificationContentInflater(
+                mock(NotifRemoteViewCache.class),
+                mock(NotificationRemoteInputManager.class));
+        contentBinder.setInflateSynchronously(true);
+        mBindStage = new RowContentBindStage(contentBinder, mock(IStatusBarService.class));
+
+        NotificationEntryManager entryManager = mock(NotificationEntryManager.class);
+
+        mBindPipeline = new NotifBindPipeline(entryManager);
+        mBindPipeline.setStage(mBindStage);
+
+        ArgumentCaptor<NotificationEntryListener> entryListenerCaptor =
+                ArgumentCaptor.forClass(NotificationEntryListener.class);
+        verify(entryManager).addNotificationEntryListener(entryListenerCaptor.capture());
+        mBindPipelineEntryListener = entryListenerCaptor.getValue();
     }
 
     /**
@@ -331,10 +357,8 @@
         entry.createIcons(mContext, entry.getSbn());
         row.setEntry(entry);
 
-        NotificationContentInflater contentBinder = new NotificationContentInflater(
-                mock(NotifRemoteViewCache.class),
-                mock(NotificationRemoteInputManager.class));
-        contentBinder.setInflateSynchronously(true);
+        mBindPipelineEntryListener.onPendingEntryAdded(entry);
+        mBindPipeline.manageRow(entry, row);
 
         row.initialize(
                 APP_NAME,
@@ -343,12 +367,11 @@
                 mock(KeyguardBypassController.class),
                 mGroupManager,
                 mHeadsUpManager,
-                contentBinder,
+                mBindStage,
                 mock(OnExpandClickListener.class));
         row.setAboveShelfChangedListener(aboveShelf -> { });
-
-        row.setInflationFlags(extraInflationFlags);
-        inflateAndWait(row);
+        mBindStage.getStageParams(entry).requireContentViews(extraInflationFlags);
+        inflateAndWait(entry, mBindStage);
 
         // This would be done as part of onAsyncInflationFinished, but we skip large amounts of
         // the callback chain, so we need to make up for not adding it to the group manager
@@ -357,24 +380,10 @@
         return row;
     }
 
-    private static void inflateAndWait(ExpandableNotificationRow row) throws Exception {
+    private static void inflateAndWait(NotificationEntry entry, RowContentBindStage stage)
+            throws Exception {
         CountDownLatch countDownLatch = new CountDownLatch(1);
-        NotificationContentInflater.InflationCallback callback =
-                new NotificationContentInflater.InflationCallback() {
-                    @Override
-                    public void handleInflationException(NotificationEntry entry,
-                            Exception e) {
-                        countDownLatch.countDown();
-                    }
-
-                    @Override
-                    public void onAsyncInflationFinished(NotificationEntry entry,
-                            int inflatedFlags) {
-                        countDownLatch.countDown();
-                    }
-                };
-        row.setInflationCallback(callback);
-        row.inflateViews();
+        stage.requestRebind(entry, en -> countDownLatch.countDown());
         assertTrue(countDownLatch.await(500, TimeUnit.MILLISECONDS));
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java
new file mode 100644
index 0000000..775f722
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java
@@ -0,0 +1,269 @@
+/*
+ * 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.row;
+
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL;
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
+
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class RowContentBindStageTest extends SysuiTestCase {
+
+    private RowContentBindStage mRowContentBindStage;
+
+    @Mock private NotificationRowContentBinder mBinder;
+    @Mock private NotificationEntry mEntry;
+    @Mock private ExpandableNotificationRow mRow;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mRowContentBindStage = new RowContentBindStage(mBinder,
+                mock(IStatusBarService.class));
+        mRowContentBindStage.createStageParams(mEntry);
+    }
+
+    @Test
+    public void testRequireContentViews() {
+        // WHEN inflation flags are set and pipeline is invalidated.
+        final int flags = FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED;
+        RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);
+        params.requireContentViews(flags);
+        mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { });
+
+        // THEN binder binds inflation flags.
+        verify(mBinder).bindContent(
+                eq(mEntry),
+                any(),
+                eq(flags),
+                any(),
+                anyBoolean(),
+                any());
+    }
+
+    @Test
+    public void testFreeContentViews() {
+        // GIVEN a view with all content bound.
+        RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);
+        params.requireContentViews(FLAG_CONTENT_VIEW_ALL);
+
+        // WHEN inflation flags are cleared and stage executed.
+        final int flags = FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED;
+        params.freeContentViews(flags);
+        mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { });
+
+        // THEN binder unbinds flags.
+        verify(mBinder).unbindContent(eq(mEntry), any(), eq(flags));
+    }
+
+    @Test
+    public void testRebindAllContentViews() {
+        // GIVEN a view with content bound.
+        RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);
+        final int flags = FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED;
+        params.requireContentViews(flags);
+        params.clearDirtyContentViews();
+
+        // WHEN we request rebind and stage executed.
+        params.rebindAllContentViews();
+        mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { });
+
+        // THEN binder binds inflation flags.
+        verify(mBinder).bindContent(
+                eq(mEntry),
+                any(),
+                eq(flags),
+                any(),
+                anyBoolean(),
+                any());
+    }
+
+    @Test
+    public void testSetUseLowPriority() {
+        // GIVEN a view with all content bound.
+        RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);
+        params.requireContentViews(FLAG_CONTENT_VIEW_ALL);
+        params.clearDirtyContentViews();
+
+        // WHEN low priority is set and stage executed.
+        params.setUseLowPriority(true);
+        mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { });
+
+        // THEN binder is called with use low priority and contracted/expanded are called to bind.
+        ArgumentCaptor<BindParams> bindParamsCaptor = ArgumentCaptor.forClass(BindParams.class);
+        verify(mBinder).bindContent(
+                eq(mEntry),
+                any(),
+                eq(FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED),
+                bindParamsCaptor.capture(),
+                anyBoolean(),
+                any());
+        BindParams usedParams = bindParamsCaptor.getValue();
+        assertTrue(usedParams.isLowPriority);
+    }
+
+    @Test
+    public void testSetUseGroupInChild() {
+        // GIVEN a view with all content bound.
+        RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);
+        params.requireContentViews(FLAG_CONTENT_VIEW_ALL);
+        params.clearDirtyContentViews();
+
+        // WHEN use group is set and stage executed.
+        params.setUseChildInGroup(true);
+        mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { });
+
+        // THEN binder is called with use group view and contracted/expanded are called to bind.
+        ArgumentCaptor<BindParams> bindParamsCaptor = ArgumentCaptor.forClass(BindParams.class);
+        verify(mBinder).bindContent(
+                eq(mEntry),
+                any(),
+                eq(FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED),
+                bindParamsCaptor.capture(),
+                anyBoolean(),
+                any());
+        BindParams usedParams = bindParamsCaptor.getValue();
+        assertTrue(usedParams.isChildInGroup);
+    }
+
+    @Test
+    public void testSetUseIncreasedHeight() {
+        // GIVEN a view with all content bound.
+        RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);
+        params.requireContentViews(FLAG_CONTENT_VIEW_ALL);
+        params.clearDirtyContentViews();
+
+        // WHEN use increased height is set and stage executed.
+        params.setUseIncreasedCollapsedHeight(true);
+        mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { });
+
+        // THEN binder is called with group view and contracted is bound.
+        ArgumentCaptor<BindParams> bindParamsCaptor = ArgumentCaptor.forClass(BindParams.class);
+        verify(mBinder).bindContent(
+                eq(mEntry),
+                any(),
+                eq(FLAG_CONTENT_VIEW_CONTRACTED),
+                bindParamsCaptor.capture(),
+                anyBoolean(),
+                any());
+        BindParams usedParams = bindParamsCaptor.getValue();
+        assertTrue(usedParams.usesIncreasedHeight);
+    }
+
+    @Test
+    public void testSetUseIncreasedHeadsUpHeight() {
+        // GIVEN a view with all content bound.
+        RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);
+        params.requireContentViews(FLAG_CONTENT_VIEW_ALL);
+        params.clearDirtyContentViews();
+
+        // WHEN use increased heads up height is set and stage executed.
+        params.setUseIncreasedHeadsUpHeight(true);
+        mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { });
+
+        // THEN binder is called with use group view and heads up is bound.
+        ArgumentCaptor<BindParams> bindParamsCaptor = ArgumentCaptor.forClass(BindParams.class);
+        verify(mBinder).bindContent(
+                eq(mEntry),
+                any(),
+                eq(FLAG_CONTENT_VIEW_HEADS_UP),
+                bindParamsCaptor.capture(),
+                anyBoolean(),
+                any());
+        BindParams usedParams = bindParamsCaptor.getValue();
+        assertTrue(usedParams.usesIncreasedHeadsUpHeight);
+    }
+
+    @Test
+    public void testSetNeedsReinflation() {
+        // GIVEN a view with all content bound.
+        RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);
+        params.requireContentViews(FLAG_CONTENT_VIEW_ALL);
+        params.clearDirtyContentViews();
+
+        // WHEN needs reinflation is set.
+        params.setNeedsReinflation(true);
+        mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { });
+
+        // THEN binder is called with forceInflate and all views are requested to bind.
+        verify(mBinder).bindContent(
+                eq(mEntry),
+                any(),
+                eq(FLAG_CONTENT_VIEW_ALL),
+                any(),
+                eq(true),
+                any());
+    }
+
+    @Test
+    public void testSupersedesPreviousContentViews() {
+        // GIVEN a view with content view bind already in progress.
+        RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);
+        int defaultFlags = FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED;
+        params.requireContentViews(defaultFlags);
+        mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { });
+
+        // WHEN we bind with another content view before the first finishes.
+        params.requireContentViews(FLAG_CONTENT_VIEW_HEADS_UP);
+        mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { });
+
+        // THEN binder is called with BOTH content views.
+        verify(mBinder).bindContent(
+                eq(mEntry),
+                any(),
+                eq(defaultFlags),
+                any(),
+                anyBoolean(),
+                any());
+        verify(mBinder).bindContent(
+                eq(mEntry),
+                any(),
+                eq(defaultFlags | FLAG_CONTENT_VIEW_HEADS_UP),
+                any(),
+                anyBoolean(),
+                any());
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java
index d280f18..0790cb7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java
@@ -25,8 +25,8 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 import com.android.systemui.tests.R;
 
 import org.junit.Assert;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java
index 4f45f68..038eff7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java
@@ -38,8 +38,8 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java
index 14e2fde..9567f33 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java
@@ -29,8 +29,8 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 import com.android.systemui.util.Assert;
 
 import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
index ddd2884e..1773175 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
@@ -25,8 +25,8 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 
 import org.junit.Assert;
 import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
index 34a309f..e84f14a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
@@ -31,11 +31,11 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.util.DeviceConfigProxy;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 70d76f0..b16e52c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -65,6 +65,7 @@
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.ForegroundServiceDismissalFeatureController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger;
 import com.android.systemui.statusbar.notification.NotificationFilter;
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
 import com.android.systemui.statusbar.notification.TestableNotificationEntryManager;
@@ -74,7 +75,6 @@
 import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
-import com.android.systemui.statusbar.notification.logging.NotifLog;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.FooterView;
@@ -163,14 +163,14 @@
         ArgumentCaptor<UserChangedListener> userChangedCaptor = ArgumentCaptor
                 .forClass(UserChangedListener.class);
         mEntryManager = new TestableNotificationEntryManager(
-                mock(NotifLog.class),
+                mock(NotificationEntryManagerLogger.class),
                 mock(NotificationGroupManager.class),
                 new NotificationRankingManager(
                         () -> mock(NotificationMediaManager.class),
                         mGroupManager,
                         mHeadsUpManager,
                         mock(NotificationFilter.class),
-                        mock(NotifLog.class),
+                        mock(NotificationEntryManagerLogger.class),
                         mock(NotificationSectionsFeatureManager.class),
                         mock(PeopleNotificationIdentifier.class),
                         mock(HighPriorityProvider.class)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
index 7448dbd..f71d0fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
@@ -35,9 +35,9 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.HeadsUpStatusBarView;
-import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
index 5b54fba..e171a28 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
@@ -16,8 +16,12 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
+
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
@@ -38,6 +42,9 @@
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback;
+import com.android.systemui.statusbar.notification.row.RowContentBindParams;
+import com.android.systemui.statusbar.notification.row.RowContentBindStage;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
 import org.junit.Before;
@@ -47,6 +54,7 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
 import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
@@ -62,8 +70,8 @@
     private NotificationGroupManager mGroupManager;
     private HeadsUpManager mHeadsUpManager;
     @Mock private NotificationEntryManager mNotificationEntryManager;
-    @Captor
-    private ArgumentCaptor<NotificationEntryListener> mListenerCaptor;
+    @Mock private RowContentBindStage mBindStage;
+    @Captor private ArgumentCaptor<NotificationEntryListener> mListenerCaptor;
     private NotificationEntryListener mNotificationEntryListener;
     private final HashMap<String, NotificationEntry> mPendingEntries = new HashMap<>();
     private final NotificationGroupTestHelper mGroupTestHelper =
@@ -72,6 +80,7 @@
 
     @Before
     public void setup() {
+        MockitoAnnotations.initMocks(this);
         mDependency.injectMockDependency(BubbleController.class);
         mHeadsUpManager = new HeadsUpManager(mContext) {};
 
@@ -82,7 +91,9 @@
         mDependency.injectTestDependency(NotificationGroupManager.class, mGroupManager);
         mGroupManager.setHeadsUpManager(mHeadsUpManager);
 
-        mGroupAlertTransferHelper = new NotificationGroupAlertTransferHelper();
+        when(mBindStage.getStageParams(any())).thenReturn(new RowContentBindParams());
+
+        mGroupAlertTransferHelper = new NotificationGroupAlertTransferHelper(mBindStage);
         mGroupAlertTransferHelper.setHeadsUpManager(mHeadsUpManager);
 
         mGroupAlertTransferHelper.bind(mNotificationEntryManager, mGroupManager);
@@ -97,6 +108,10 @@
         mHeadsUpManager.showNotification(summaryEntry);
         NotificationEntry childEntry = mGroupTestHelper.createChildNotification();
 
+        RowContentBindParams params = new RowContentBindParams();
+        params.requireContentViews(FLAG_CONTENT_VIEW_HEADS_UP);
+        when(mBindStage.getStageParams(eq(childEntry))).thenReturn(params);
+
         // Summary will be suppressed because there is only one child.
         mGroupManager.onEntryAdded(summaryEntry);
         mGroupManager.onEntryAdded(childEntry);
@@ -160,8 +175,8 @@
         NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification();
         mHeadsUpManager.showNotification(summaryEntry);
         NotificationEntry childEntry = mGroupTestHelper.createChildNotification();
-        when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag()))
-            .thenReturn(false);
+        RowContentBindParams params = new RowContentBindParams();
+        when(mBindStage.getStageParams(eq(childEntry))).thenReturn(params);
 
         mGroupManager.onEntryAdded(summaryEntry);
         mGroupManager.onEntryAdded(childEntry);
@@ -178,15 +193,16 @@
         NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification();
         mHeadsUpManager.showNotification(summaryEntry);
         NotificationEntry childEntry = mGroupTestHelper.createChildNotification();
-        when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag()))
-            .thenReturn(false);
+        RowContentBindParams params = new RowContentBindParams();
+        when(mBindStage.getStageParams(eq(childEntry))).thenReturn(params);
 
         mGroupManager.onEntryAdded(summaryEntry);
         mGroupManager.onEntryAdded(childEntry);
 
-        when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag()))
-            .thenReturn(true);
-        mNotificationEntryListener.onEntryReinflated(childEntry);
+        // Child entry finishes its inflation.
+        ArgumentCaptor<BindCallback> callbackCaptor = ArgumentCaptor.forClass(BindCallback.class);
+        verify(mBindStage).requestRebind(eq(childEntry), callbackCaptor.capture());
+        callbackCaptor.getValue().onBindFinished(childEntry);
 
         // Alert is immediately removed from summary, and we show child as its content is inflated.
         assertFalse(mHeadsUpManager.isAlerting(summaryEntry.getKey()));
@@ -199,8 +215,9 @@
                 mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY);
         NotificationEntry childEntry =
                 mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY);
-        when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag()))
-            .thenReturn(false);
+        RowContentBindParams params = new RowContentBindParams();
+        when(mBindStage.getStageParams(eq(childEntry))).thenReturn(params);
+
         NotificationEntry childEntry2 =
                 mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY);
         mHeadsUpManager.showNotification(summaryEntry);
@@ -214,9 +231,9 @@
         mGroupManager.onEntryAdded(childEntry2);
 
         // Child entry finishes its inflation.
-        when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag()))
-            .thenReturn(true);
-        mNotificationEntryListener.onEntryReinflated(childEntry);
+        ArgumentCaptor<BindCallback> callbackCaptor = ArgumentCaptor.forClass(BindCallback.class);
+        verify(mBindStage).requestRebind(eq(childEntry), callbackCaptor.capture());
+        callbackCaptor.getValue().onBindFinished(childEntry);
 
         verify(childEntry.getRow(), times(1)).freeContentViewWhenSafe(mHeadsUpManager
             .getContentFlag());
@@ -229,8 +246,9 @@
                 mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY);
         NotificationEntry childEntry =
                 mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY);
-        when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag()))
-            .thenReturn(false);
+        RowContentBindParams params = new RowContentBindParams();
+        when(mBindStage.getStageParams(eq(childEntry))).thenReturn(params);
+
         mHeadsUpManager.showNotification(summaryEntry);
         // Trigger a transfer of alert state from summary to child.
         mGroupManager.onEntryAdded(summaryEntry);
@@ -247,8 +265,9 @@
                 mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY);
         NotificationEntry childEntry =
                 mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY);
-        when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag()))
-            .thenReturn(false);
+        RowContentBindParams params = new RowContentBindParams();
+        when(mBindStage.getStageParams(eq(childEntry))).thenReturn(params);
+
         mHeadsUpManager.showNotification(summaryEntry);
         // Trigger a transfer of alert state from summary to child.
         mGroupManager.onEntryAdded(summaryEntry);
@@ -270,8 +289,9 @@
                 mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY);
         NotificationEntry childEntry =
                 mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY, 47);
-        when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag()))
-            .thenReturn(false);
+        RowContentBindParams params = new RowContentBindParams();
+        when(mBindStage.getStageParams(eq(childEntry))).thenReturn(params);
+
         mHeadsUpManager.showNotification(summaryEntry);
         // Trigger a transfer of alert state from summary to child.
         mGroupManager.onEntryAdded(summaryEntry);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java
index 54dc728..d405fea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.phone;
 
-import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
@@ -87,7 +86,6 @@
         ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
         entry.setRow(row);
         when(row.getEntry()).thenReturn(entry);
-        when(row.isInflationFlagSet(anyInt())).thenReturn(true);
         return entry;
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index fea4b8b..5027610 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -61,7 +61,6 @@
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
@@ -70,6 +69,7 @@
 import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
index 390e812..df62254 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -39,9 +39,9 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.util.Assert;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt
new file mode 100644
index 0000000..8eecde1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt
@@ -0,0 +1,218 @@
+package com.android.systemui.util
+
+import android.graphics.Rect
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class FloatingContentCoordinatorTest : SysuiTestCase() {
+
+    private val screenBounds = Rect(0, 0, 1000, 1000)
+
+    private val rect100px = Rect()
+    private val rect100pxFloating = FloatingRect(rect100px)
+
+    private val rect200px = Rect()
+    private val rect200pxFloating = FloatingRect(rect200px)
+
+    private val rect300px = Rect()
+    private val rect300pxFloating = FloatingRect(rect300px)
+
+    private val floatingCoordinator = FloatingContentCoordinator()
+
+    @Before
+    fun setup() {
+        rect100px.set(0, 0, 100, 100)
+        rect200px.set(0, 0, 200, 200)
+        rect300px.set(0, 0, 300, 300)
+    }
+
+    @After
+    fun tearDown() {
+        // We need to remove this stuff since it's a singleton object and it'll be there for the
+        // next test.
+        floatingCoordinator.onContentRemoved(rect100pxFloating)
+        floatingCoordinator.onContentRemoved(rect200pxFloating)
+        floatingCoordinator.onContentRemoved(rect300pxFloating)
+    }
+
+    @Test
+    fun testOnContentAdded() {
+        // Add rect1, and verify that the coordinator didn't move it.
+        floatingCoordinator.onContentAdded(rect100pxFloating)
+        assertEquals(rect100px.top, 0)
+
+        // Add rect2, which intersects rect1. Verify that rect2 was not moved, since newly added
+        // content is allowed to remain where it is. rect1 should have been moved below rect2
+        // since it was in the way.
+        floatingCoordinator.onContentAdded(rect200pxFloating)
+        assertEquals(rect200px.top, 0)
+        assertEquals(rect100px.top, 200)
+
+        verifyRectSizes()
+    }
+
+    @Test
+    fun testOnContentRemoved() {
+        // Add rect1, and remove it. Then add rect2. Since rect1 was removed before that, it should
+        // no longer be considered in the way, so it shouldn't move when rect2 is added.
+        floatingCoordinator.onContentAdded(rect100pxFloating)
+        floatingCoordinator.onContentRemoved(rect100pxFloating)
+        floatingCoordinator.onContentAdded(rect200pxFloating)
+
+        assertEquals(rect100px.top, 0)
+        assertEquals(rect200px.top, 0)
+
+        verifyRectSizes()
+    }
+
+    @Test
+    fun testOnContentMoved_twoRects() {
+        // Add rect1, which is at y = 0.
+        floatingCoordinator.onContentAdded(rect100pxFloating)
+
+        // Move rect2 down to 500px, where it won't conflict with rect1.
+        rect200px.offsetTo(0, 500)
+        floatingCoordinator.onContentAdded(rect200pxFloating)
+
+        // Then, move it to 0px where it will absolutely conflict with rect1.
+        rect200px.offsetTo(0, 0)
+        floatingCoordinator.onContentMoved(rect200pxFloating)
+
+        // The coordinator should have left rect2 alone, and moved rect1 below it. rect1 should now
+        // be at y = 200.
+        assertEquals(rect200px.top, 0)
+        assertEquals(rect100px.top, 200)
+
+        verifyRectSizes()
+
+        // Move rect2 to y = 275px. Since this puts it at the bottom half of rect1, it should push
+        // rect1 upward and leave rect2 alone.
+        rect200px.offsetTo(0, 275)
+        floatingCoordinator.onContentMoved(rect200pxFloating)
+
+        assertEquals(rect200px.top, 275)
+        assertEquals(rect100px.top, 175)
+
+        verifyRectSizes()
+
+        // Move rect2 to y = 110px. This makes it intersect rect1 again, but above its center of
+        // mass. That means rect1 should be pushed downward.
+        rect200px.offsetTo(0, 110)
+        floatingCoordinator.onContentMoved(rect200pxFloating)
+
+        assertEquals(rect200px.top, 110)
+        assertEquals(rect100px.top, 310)
+
+        verifyRectSizes()
+    }
+
+    @Test
+    fun testOnContentMoved_threeRects() {
+        floatingCoordinator.onContentAdded(rect100pxFloating)
+
+        // Add rect2, which should displace rect1 to y = 200
+        floatingCoordinator.onContentAdded(rect200pxFloating)
+        assertEquals(rect200px.top, 0)
+        assertEquals(rect100px.top, 200)
+
+        // Add rect3, which should completely cover both rect1 and rect2. That should cause them to
+        // move away. The order in which they do so is non-deterministic, so just make sure none of
+        // the three Rects intersect.
+        floatingCoordinator.onContentAdded(rect300pxFloating)
+
+        assertFalse(Rect.intersects(rect100px, rect200px))
+        assertFalse(Rect.intersects(rect100px, rect300px))
+        assertFalse(Rect.intersects(rect200px, rect300px))
+
+        // Move rect2 to intersect both rect1 and rect3.
+        rect200px.offsetTo(0, 150)
+        floatingCoordinator.onContentMoved(rect200pxFloating)
+
+        assertFalse(Rect.intersects(rect100px, rect200px))
+        assertFalse(Rect.intersects(rect100px, rect300px))
+        assertFalse(Rect.intersects(rect200px, rect300px))
+    }
+
+    @Test
+    fun testOnContentMoved_respectsUpperBounds() {
+        // Add rect1, which is at y = 0.
+        floatingCoordinator.onContentAdded(rect100pxFloating)
+
+        // Move rect2 down to 500px, where it won't conflict with rect1.
+        rect200px.offsetTo(0, 500)
+        floatingCoordinator.onContentAdded(rect200pxFloating)
+
+        // Then, move it to 90px where it will conflict with rect1, but with a center of mass below
+        // that of rect1's. This would normally mean that rect1 moves upward. However, since it's at
+        // the top of the screen, it should go downward instead.
+        rect200px.offsetTo(0, 90)
+        floatingCoordinator.onContentMoved(rect200pxFloating)
+
+        // rect2 should have been left alone, rect1 is now below rect2 at y = 290px even though it
+        // was intersected from below.
+        assertEquals(rect200px.top, 90)
+        assertEquals(rect100px.top, 290)
+    }
+
+    @Test
+    fun testOnContentMoved_respectsLowerBounds() {
+        // Put rect1 at the bottom of the screen and add it.
+        rect100px.offsetTo(0, screenBounds.bottom - 100)
+        floatingCoordinator.onContentAdded(rect100pxFloating)
+
+        // Put rect2 at the bottom as well. Since its center of mass is above rect1's, rect1 would
+        // normally move downward. Since it's at the bottom of the screen, it should go upward
+        // instead.
+        rect200px.offsetTo(0, 800)
+        floatingCoordinator.onContentAdded(rect200pxFloating)
+
+        assertEquals(rect200px.top, 800)
+        assertEquals(rect100px.top, 700)
+    }
+
+    /**
+     * Tests that the rect sizes didn't change when the coordinator manipulated them. This allows us
+     * to assert only the value of rect.top in tests, since if top, width, and height are correct,
+     * that means top/left/right/bottom are all correct.
+     */
+    private fun verifyRectSizes() {
+        assertEquals(100, rect100px.width())
+        assertEquals(200, rect200px.width())
+        assertEquals(300, rect300px.width())
+
+        assertEquals(100, rect100px.height())
+        assertEquals(200, rect200px.height())
+        assertEquals(300, rect300px.height())
+    }
+
+    /**
+     * Helper class that uses [floatingCoordinator.findAreaForContentVertically] to move a
+     * Rect when needed.
+     */
+    inner class FloatingRect(
+        private val underlyingRect: Rect
+    ) : FloatingContentCoordinator.FloatingContent {
+        override fun moveToBounds(bounds: Rect) {
+            underlyingRect.set(bounds)
+        }
+
+        override fun getAllowedFloatingBoundsRegion(): Rect {
+            return screenBounds
+        }
+
+        override fun getFloatingBoundsOnScreen(): Rect {
+            return underlyingRect
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
index 07abe1a..39c402b 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
@@ -272,13 +272,6 @@
 
         mStateReceiver = new StateReceiver();
 
-        mNetdCallback = new NetdCallback();
-        try {
-            mNetd.registerUnsolicitedEventListener(mNetdCallback);
-        } catch (RemoteException e) {
-            mLog.e("Unable to register netd UnsolicitedEventListener");
-        }
-
         final UserManager userManager = (UserManager) mContext.getSystemService(
                 Context.USER_SERVICE);
         mTetheringRestriction = new UserRestrictionActionListener(userManager, this);
@@ -287,6 +280,14 @@
 
         // Load tethering configuration.
         updateConfiguration();
+        // NetdCallback should be registered after updateConfiguration() to ensure
+        // TetheringConfiguration is created.
+        mNetdCallback = new NetdCallback();
+        try {
+            mNetd.registerUnsolicitedEventListener(mNetdCallback);
+        } catch (RemoteException e) {
+            mLog.e("Unable to register netd UnsolicitedEventListener");
+        }
 
         startStateMachineUpdaters(mHandler);
         startTrackDefaultNetwork();
diff --git a/packages/overlays/Android.mk b/packages/overlays/Android.mk
index fc7709c..dcdb80b 100644
--- a/packages/overlays/Android.mk
+++ b/packages/overlays/Android.mk
@@ -28,6 +28,7 @@
 	DisplayCutoutEmulationDoubleOverlay \
         DisplayCutoutEmulationHoleOverlay \
 	DisplayCutoutEmulationTallOverlay \
+	DisplayCutoutEmulationWaterfallOverlay \
 	FontNotoSerifSourceOverlay \
 	IconPackCircularAndroidOverlay \
 	IconPackCircularLauncherOverlay \
diff --git a/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/Android.mk b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/Android.mk
new file mode 100644
index 0000000..b6b6dd1
--- /dev/null
+++ b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/Android.mk
@@ -0,0 +1,14 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_RRO_THEME := DisplayCutoutEmulationWaterfall
+
+
+LOCAL_PRODUCT_MODULE := true
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+
+LOCAL_PACKAGE_NAME := DisplayCutoutEmulationWaterfallOverlay
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_RRO_PACKAGE)
diff --git a/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/AndroidManifest.xml b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/AndroidManifest.xml
new file mode 100644
index 0000000..2d5bb14
--- /dev/null
+++ b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ 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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.internal.display.cutout.emulation.waterfall"
+        android:versionCode="1"
+        android:versionName="1.0">
+    <overlay android:targetPackage="android"
+        android:category="com.android.internal.display_cutout_emulation"
+        android:priority="1"/>
+
+    <application android:label="@string/display_cutout_emulation_overlay" android:hasCode="false"/>
+</manifest>
diff --git a/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values-land/config.xml b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values-land/config.xml
new file mode 100644
index 0000000..df2f3d1
--- /dev/null
+++ b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values-land/config.xml
@@ -0,0 +1,22 @@
+<!--
+  ~ 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>
+    <!-- Can't link to other dimensions here, but this should be status_bar_height_landscape -->
+    <dimen name="quick_qs_offset_height">48dp</dimen>
+    <!-- Total height of QQS in landscape; quick_qs_offset_height + 128 -->
+    <dimen name="quick_qs_total_height">176dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/config.xml b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/config.xml
new file mode 100644
index 0000000..6f692c8
--- /dev/null
+++ b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/config.xml
@@ -0,0 +1,35 @@
+<!--
+  ~ 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 xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+    <!-- Height of the status bar in portrait. The height should be
+         Max((status bar content height + waterfall top size), top cutout size) -->
+    <dimen name="status_bar_height_portrait">28dp</dimen>
+    <!-- Max((28 + 20), 0) = 48 -->
+    <dimen name="status_bar_height_landscape">48dp</dimen>
+    <!-- Height of area above QQS where battery/time go (equal to status bar height if > 48dp) -->
+    <dimen name="quick_qs_offset_height">28dp</dimen>
+    <!-- Total height of QQS (quick_qs_offset_height + 128) -->
+    <dimen name="quick_qs_total_height">156dp</dimen>
+
+    <dimen name="waterfall_display_left_edge_size">20dp</dimen>
+    <dimen name="waterfall_display_top_edge_size">0dp</dimen>
+    <dimen name="waterfall_display_right_edge_size">20dp</dimen>
+    <dimen name="waterfall_display_bottom_edge_size">0dp</dimen>
+</resources>
+
+
diff --git a/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/strings.xml b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/strings.xml
new file mode 100644
index 0000000..ed073d0
--- /dev/null
+++ b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/strings.xml
@@ -0,0 +1,21 @@
+<?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 xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+    <string name="display_cutout_emulation_overlay">Waterfall cutout</string>
+
+</resources>
\ No newline at end of file
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index a5877cc..565ee63 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -16,6 +16,8 @@
 
 package com.android.server.accessibility;
 
+import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE_ID;
+import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER;
 import static android.accessibilityservice.AccessibilityServiceInfo.DEFAULT;
 import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS;
@@ -36,10 +38,11 @@
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
-import android.graphics.Bitmap;
+import android.graphics.GraphicBuffer;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.Region;
+import android.hardware.HardwareBuffer;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerGlobal;
 import android.os.Binder;
@@ -50,6 +53,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.PowerManager;
+import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
@@ -71,6 +75,7 @@
 import com.android.internal.compat.IPlatformCompat;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.DumpUtils;
+import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.LocalServices;
 import com.android.server.accessibility.AccessibilityWindowManager.RemoteAccessibilityConnection;
 import com.android.server.wm.ActivityTaskManagerInternal;
@@ -106,6 +111,8 @@
     private final PowerManager mPowerManager;
     private final IPlatformCompat mIPlatformCompat;
 
+    private final Handler mMainHandler;
+
     // Handler for scheduling method invocations on the main thread.
     public final InvocationHandler mInvocationHandler;
 
@@ -238,6 +245,7 @@
         mSecurityPolicy = securityPolicy;
         mSystemActionPerformer = systemActionPerfomer;
         mSystemSupport = systemSupport;
+        mMainHandler = mainHandler;
         mInvocationHandler = new InvocationHandler(mainHandler.getLooper());
         mA11yWindowManager = a11yWindowManager;
         mDisplayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
@@ -959,52 +967,72 @@
         mInvocationHandler.setSoftKeyboardCallbackEnabled(enabled);
     }
 
-    @Nullable
     @Override
-    public Bitmap takeScreenshot(int displayId) {
+    public void takeScreenshot(int displayId, RemoteCallback callback) {
         synchronized (mLock) {
             if (!hasRightsToCurrentUserLocked()) {
-                return null;
+                sendScreenshotResult(true, null, callback);
+                return;
             }
 
             if (!mSecurityPolicy.canTakeScreenshotLocked(this)) {
-                return null;
+                sendScreenshotResult(true, null, callback);
+                throw new SecurityException("Services don't have the capability of taking"
+                        + " the screenshot.");
             }
         }
 
         if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
-            return null;
+            sendScreenshotResult(true, null, callback);
+            return;
         }
 
         final Display display = DisplayManagerGlobal.getInstance()
                 .getRealDisplay(displayId);
         if (display == null) {
-            return null;
+            sendScreenshotResult(true, null, callback);
+            return;
         }
-        final Point displaySize = new Point();
-        display.getRealSize(displaySize);
 
-        final int rotation = display.getRotation();
-        Bitmap screenShot = null;
+        sendScreenshotResult(false, display, callback);
+    }
 
+    private void sendScreenshotResult(boolean noResult, Display display, RemoteCallback callback) {
+        final boolean noScreenshot = noResult;
         final long identity = Binder.clearCallingIdentity();
         try {
-            final Rect crop = new Rect(0, 0, displaySize.x, displaySize.y);
-            // TODO (b/145893483): calling new API with the display as a parameter
-            // when surface control supported.
-            screenShot = SurfaceControl.screenshot(crop, displaySize.x, displaySize.y,
-                    rotation);
-            if (screenShot != null) {
-                // Optimization for telling the bitmap that all of the pixels are known to be
-                // opaque (false). This is meant as a drawing hint, as in some cases a bitmap
-                // that is known to be opaque can take a faster drawing case than one that may
-                // have non-opaque per-pixel alpha values.
-                screenShot.setHasAlpha(false);
-            }
+            mMainHandler.post(PooledLambda.obtainRunnable((nonArg) -> {
+                if (noScreenshot) {
+                    callback.sendResult(null);
+                    return;
+                }
+                final Point displaySize = new Point();
+                // TODO (b/145893483): calling new API with the display as a parameter
+                // when surface control supported.
+                final IBinder token = SurfaceControl.getInternalDisplayToken();
+                final Rect crop = new Rect(0, 0, displaySize.x, displaySize.y);
+                final int rotation = display.getRotation();
+                display.getRealSize(displaySize);
+
+                final SurfaceControl.ScreenshotGraphicBuffer screenshotBuffer =
+                        SurfaceControl.screenshotToBufferWithSecureLayersUnsafe(token, crop,
+                                displaySize.x, displaySize.y, false,
+                                rotation);
+                final GraphicBuffer graphicBuffer = screenshotBuffer.getGraphicBuffer();
+                final HardwareBuffer hardwareBuffer =
+                        HardwareBuffer.createFromGraphicBuffer(graphicBuffer);
+                final int colorSpaceId = screenshotBuffer.getColorSpace().getId();
+
+                // Send back the result.
+                final Bundle payload = new Bundle();
+                payload.putParcelable(KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER,
+                        hardwareBuffer);
+                payload.putInt(KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE_ID, colorSpaceId);
+                callback.sendResult(payload);
+            }, null).recycleOnUse());
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
-        return screenShot;
     }
 
     @Override
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index 25911a7..edb4445 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -16,8 +16,6 @@
 
 package com.android.server.accessibility;
 
-import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT;
-
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
 import android.Manifest;
@@ -27,20 +25,16 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ParceledListSlice;
-import android.graphics.Bitmap;
 import android.os.Binder;
-import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Process;
-import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.Slog;
 import android.view.Display;
 
-import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
@@ -393,15 +387,4 @@
             }
         }
     }
-
-    @Override
-    public void takeScreenshotWithCallback(int displayId, RemoteCallback callback) {
-        mMainHandler.post(PooledLambda.obtainRunnable((nonArg) -> {
-            final Bitmap screenshot = super.takeScreenshot(displayId);
-            // Send back the result.
-            final Bundle payload = new Bundle();
-            payload.putParcelable(KEY_ACCESSIBILITY_SCREENSHOT, screenshot);
-            callback.sendResult(payload);
-        }, null).recycleOnUse());
-    }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
index 5d9af26..d1c3a02 100644
--- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
@@ -328,6 +328,6 @@
         public void onFingerprintGesture(int gesture) {}
 
         @Override
-        public void takeScreenshotWithCallback(int displayId, RemoteCallback callback) {}
+        public void takeScreenshot(int displayId, RemoteCallback callback) {}
     }
 }
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 5d170d3..b74be7e 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
@@ -30,6 +30,13 @@
 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_4_FINGER_DOUBLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SINGLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_RIGHT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_UP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_TRIPLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP_AND_HOLD;
 import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN;
@@ -133,6 +140,13 @@
                 new MultiFingerMultiTap(mContext, 3, 2, GESTURE_3_FINGER_DOUBLE_TAP, this));
         mMultiFingerGestures.add(
                 new MultiFingerMultiTap(mContext, 3, 3, GESTURE_3_FINGER_TRIPLE_TAP, this));
+        // Four-finger taps.
+        mMultiFingerGestures.add(
+                new MultiFingerMultiTap(mContext, 4, 1, GESTURE_4_FINGER_SINGLE_TAP, this));
+        mMultiFingerGestures.add(
+                new MultiFingerMultiTap(mContext, 4, 2, GESTURE_4_FINGER_DOUBLE_TAP, this));
+        mMultiFingerGestures.add(
+                new MultiFingerMultiTap(mContext, 4, 3, GESTURE_4_FINGER_TRIPLE_TAP, this));
         // Two-finger swipes.
         mMultiFingerGestures.add(
                 new MultiFingerSwipe(context, 2, DOWN, GESTURE_2_FINGER_SWIPE_DOWN, this));
@@ -151,6 +165,15 @@
                 new MultiFingerSwipe(context, 3, RIGHT, GESTURE_3_FINGER_SWIPE_RIGHT, this));
         mMultiFingerGestures.add(
                 new MultiFingerSwipe(context, 3, UP, GESTURE_3_FINGER_SWIPE_UP, this));
+        // Four-finger swipes.
+        mMultiFingerGestures.add(
+                new MultiFingerSwipe(context, 4, DOWN, GESTURE_4_FINGER_SWIPE_DOWN, this));
+        mMultiFingerGestures.add(
+                new MultiFingerSwipe(context, 4, LEFT, GESTURE_4_FINGER_SWIPE_LEFT, this));
+        mMultiFingerGestures.add(
+                new MultiFingerSwipe(context, 4, RIGHT, GESTURE_4_FINGER_SWIPE_RIGHT, this));
+        mMultiFingerGestures.add(
+                new MultiFingerSwipe(context, 4, UP, GESTURE_4_FINGER_SWIPE_UP, this));
     }
 
     /**
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerSwipe.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerSwipe.java
index 8249239..a14584a 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerSwipe.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerSwipe.java
@@ -139,7 +139,7 @@
         final int actionIndex = getActionIndex(rawEvent);
         final int pointerId = rawEvent.getPointerId(actionIndex);
         int pointerIndex = rawEvent.getPointerCount() - 1;
-        if (pointerId < 0 || pointerId > rawEvent.getPointerCount() - 1) {
+        if (pointerId < 0) {
             // Nonsensical pointer id.
             cancelGesture(event, rawEvent, policyFlags);
             return;
@@ -185,7 +185,7 @@
         }
         final int actionIndex = getActionIndex(rawEvent);
         final int pointerId = rawEvent.getPointerId(actionIndex);
-        if (pointerId < 0 || pointerId > rawEvent.getPointerCount() - 1) {
+        if (pointerId < 0) {
             // Nonsensical pointer id.
             cancelGesture(event, rawEvent, policyFlags);
             return;
@@ -224,7 +224,7 @@
         mCurrentFingerCount -= 1;
         final int actionIndex = getActionIndex(event);
         final int pointerId = event.getPointerId(actionIndex);
-        if (pointerId < 0 || pointerId > rawEvent.getPointerCount() - 1) {
+        if (pointerId < 0) {
             // Nonsensical pointer id.
             cancelGesture(event, rawEvent, policyFlags);
             return;
@@ -250,11 +250,29 @@
 
     @Override
     protected void onMove(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
-        for (int pointerIndex = 0; pointerIndex < rawEvent.getPointerCount(); ++pointerIndex) {
+        for (int pointerIndex = 0; pointerIndex < mTargetFingerCount; ++pointerIndex) {
+            if (mPointerIds[pointerIndex] == INVALID_POINTER_ID) {
+                // Fingers have started to move before the required number of fingers are down.
+                // However, they can still move less than the touch slop and still be considered
+                // touching, not moving.
+                // So we just ignore fingers that haven't been assigned a pointer id and process
+                // those who have.
+                continue;
+            }
             if (DEBUG) {
                 Slog.d(getGestureName(), "Processing move on finger " + pointerIndex);
             }
             int index = rawEvent.findPointerIndex(mPointerIds[pointerIndex]);
+            if (index < 0) {
+                // This finger is not present in this event. It could have gone up just before this
+                // movement.
+                if (DEBUG) {
+                    Slog.d(
+                            getGestureName(),
+                            "Finger " + pointerIndex + " not found in this event. skipping.");
+                }
+                continue;
+            }
             final float x = rawEvent.getX(index);
             final float y = rawEvent.getY(index);
             if (x < 0f || y < 0f) {
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 1a4fc32..1cb9313 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -818,27 +818,26 @@
         }
     }
 
-    void logAugmentedAutofillSelected(int sessionId, @Nullable String suggestionId,
-            @Nullable Bundle clientState) {
+    void logAugmentedAutofillSelected(int sessionId, @Nullable String suggestionId) {
         synchronized (mLock) {
             if (mAugmentedAutofillEventHistory == null
                     || mAugmentedAutofillEventHistory.getSessionId() != sessionId) {
                 return;
             }
             mAugmentedAutofillEventHistory.addEvent(
-                    new Event(Event.TYPE_DATASET_SELECTED, suggestionId, clientState, null, null,
+                    new Event(Event.TYPE_DATASET_SELECTED, suggestionId, null, null, null,
                             null, null, null, null, null, null));
         }
     }
 
-    void logAugmentedAutofillShown(int sessionId, @Nullable Bundle clientState) {
+    void logAugmentedAutofillShown(int sessionId) {
         synchronized (mLock) {
             if (mAugmentedAutofillEventHistory == null
                     || mAugmentedAutofillEventHistory.getSessionId() != sessionId) {
                 return;
             }
             mAugmentedAutofillEventHistory.addEvent(
-                    new Event(Event.TYPE_DATASETS_SHOWN, null, clientState, null, null, null,
+                    new Event(Event.TYPE_DATASETS_SHOWN, null, null, null, null, null,
                             null, null, null, null, null));
 
         }
@@ -1227,16 +1226,15 @@
                         }
 
                         @Override
-                        public void logAugmentedAutofillShown(int sessionId, Bundle clientState) {
-                            AutofillManagerServiceImpl.this.logAugmentedAutofillShown(sessionId,
-                                    clientState);
+                        public void logAugmentedAutofillShown(int sessionId) {
+                            AutofillManagerServiceImpl.this.logAugmentedAutofillShown(sessionId);
                         }
 
                         @Override
-                        public void logAugmentedAutofillSelected(int sessionId, String suggestionId,
-                                Bundle clientState) {
+                        public void logAugmentedAutofillSelected(int sessionId,
+                                String suggestionId) {
                             AutofillManagerServiceImpl.this.logAugmentedAutofillSelected(sessionId,
-                                    suggestionId, clientState);
+                                    suggestionId);
                         }
 
                         @Override
diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
index 5e6f6fea..880c401 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
@@ -55,6 +55,7 @@
 import com.android.internal.os.IResultReceiver;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.view.IInlineSuggestionsResponseCallback;
+import com.android.server.autofill.ui.InlineSuggestionFactory;
 
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.TimeUnit;
@@ -144,7 +145,8 @@
             int taskId, @NonNull ComponentName activityComponent, @NonNull AutofillId focusedId,
             @Nullable AutofillValue focusedValue,
             @Nullable InlineSuggestionsRequest inlineSuggestionsRequest,
-            @Nullable IInlineSuggestionsResponseCallback inlineSuggestionsCallback) {
+            @Nullable IInlineSuggestionsResponseCallback inlineSuggestionsCallback,
+            @NonNull Runnable onErrorCallback) {
         long requestTime = SystemClock.elapsedRealtime();
         AtomicReference<ICancellationSignal> cancellationRef = new AtomicReference<>();
 
@@ -161,12 +163,12 @@
                             focusedId, focusedValue, requestTime, inlineSuggestionsRequest,
                             new IFillCallback.Stub() {
                                 @Override
-                                public void onSuccess(@Nullable Dataset[] inlineSuggestionsData,
-                                        @Nullable Bundle clientState) {
+                                public void onSuccess(@Nullable Dataset[] inlineSuggestionsData) {
                                     mCallbacks.resetLastResponse();
                                     maybeRequestShowInlineSuggestions(sessionId,
                                             inlineSuggestionsData, focusedId,
-                                            inlineSuggestionsCallback, client, clientState);
+                                            inlineSuggestionsCallback, client,
+                                            onErrorCallback);
                                     requestAutofill.complete(null);
                                 }
 
@@ -231,29 +233,31 @@
     private void maybeRequestShowInlineSuggestions(int sessionId,
             @Nullable Dataset[] inlineSuggestionsData, @NonNull AutofillId focusedId,
             @Nullable IInlineSuggestionsResponseCallback inlineSuggestionsCallback,
-            @NonNull IAutoFillManagerClient client, @Nullable Bundle clientState) {
+            @NonNull IAutoFillManagerClient client, @NonNull Runnable onErrorCallback) {
         if (ArrayUtils.isEmpty(inlineSuggestionsData) || inlineSuggestionsCallback == null) {
             return;
         }
         mCallbacks.setLastResponse(sessionId);
+
         try {
             inlineSuggestionsCallback.onInlineSuggestionsResponse(
                     InlineSuggestionFactory.createAugmentedInlineSuggestionsResponse(
                             inlineSuggestionsData, focusedId, mContext,
                             dataset -> {
                                 mCallbacks.logAugmentedAutofillSelected(sessionId,
-                                        dataset.getId(), clientState);
+                                        dataset.getId());
                                 try {
                                     client.autofill(sessionId, dataset.getFieldIds(),
                                             dataset.getFieldValues());
                                 } catch (RemoteException e) {
                                     Slog.w(TAG, "Encounter exception autofilling the values");
                                 }
-                            }));
+                            }, onErrorCallback));
         } catch (RemoteException e) {
             Slog.w(TAG, "Exception sending inline suggestions response back to IME.");
         }
-        mCallbacks.logAugmentedAutofillShown(sessionId, clientState);
+
+        mCallbacks.logAugmentedAutofillShown(sessionId);
     }
 
     @Override
@@ -275,9 +279,8 @@
 
         void setLastResponse(int sessionId);
 
-        void logAugmentedAutofillShown(int sessionId, @Nullable Bundle clientState);
+        void logAugmentedAutofillShown(int sessionId);
 
-        void logAugmentedAutofillSelected(int sessionId, @Nullable String suggestionId,
-                @Nullable Bundle clientState);
+        void logAugmentedAutofillSelected(int sessionId, @Nullable String suggestionId);
     }
 }
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 415ecd8..7e5123c 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -103,6 +103,7 @@
 import com.android.internal.view.IInlineSuggestionsRequestCallback;
 import com.android.internal.view.IInlineSuggestionsResponseCallback;
 import com.android.server.autofill.ui.AutoFillUI;
+import com.android.server.autofill.ui.InlineSuggestionFactory;
 import com.android.server.autofill.ui.PendingUi;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 
@@ -2681,7 +2682,6 @@
             }
         }
 
-
         getUiForShowing().showFillUi(filledId, response, filterText,
                 mService.getServicePackageName(), mComponentName,
                 serviceLabel, serviceIcon, this, id, mCompatMode);
@@ -2733,7 +2733,11 @@
 
         InlineSuggestionsResponse inlineSuggestionsResponse =
                 InlineSuggestionFactory.createInlineSuggestionsResponse(response.getRequestId(),
-                        datasets.toArray(new Dataset[]{}), mCurrentViewId, mContext, this);
+                        datasets.toArray(new Dataset[]{}), mCurrentViewId, mContext, this, () -> {
+                    synchronized (mLock) {
+                        requestHideFillUi(mCurrentViewId);
+                    }
+                });
         try  {
             inlineContentCallback.onInlineSuggestionsResponse(inlineSuggestionsResponse);
         } catch (RemoteException e) {
@@ -3024,7 +3028,11 @@
                 mInlineSuggestionsRequestCallback != null
                         ? mInlineSuggestionsRequestCallback.getResponseCallback() : null;
         remoteService.onRequestAutofillLocked(id, mClient, taskId, mComponentName, focusedId,
-                currentValue, inlineSuggestionsRequest, inlineSuggestionsResponseCallback);
+                currentValue, inlineSuggestionsRequest, inlineSuggestionsResponseCallback, () -> {
+                    synchronized (mLock) {
+                        cancelAugmentedAutofillLocked();
+                    }
+                });
 
         if (mAugmentedAutofillDestroyer == null) {
             mAugmentedAutofillDestroyer = () -> remoteService.onDestroyAutofillWindowsRequest();
diff --git a/services/autofill/java/com/android/server/autofill/InlineSuggestionFactory.java b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java
similarity index 80%
rename from services/autofill/java/com/android/server/autofill/InlineSuggestionFactory.java
rename to services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java
index cb6c8f5..38a5b5b 100644
--- a/services/autofill/java/com/android/server/autofill/InlineSuggestionFactory.java
+++ b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.autofill;
+package com.android.server.autofill.ui;
 
 import static com.android.server.autofill.Helper.sDebug;
 
@@ -32,18 +32,13 @@
 import android.view.inputmethod.InlineSuggestionInfo;
 import android.view.inputmethod.InlineSuggestionsResponse;
 
+import com.android.internal.util.function.QuadFunction;
 import com.android.internal.view.inline.IInlineContentCallback;
 import com.android.internal.view.inline.IInlineContentProvider;
 import com.android.server.UiThread;
-import com.android.server.autofill.ui.AutoFillUI;
-import com.android.server.autofill.ui.InlineSuggestionUi;
 
 import java.util.ArrayList;
 
-
-/**
- * @hide
- */
 public final class InlineSuggestionFactory {
     private static final String TAG = "InlineSuggestionFactory";
 
@@ -65,28 +60,12 @@
             @NonNull Dataset[] datasets,
             @NonNull AutofillId autofillId,
             @NonNull Context context,
-            @NonNull InlineSuggestionUiCallback inlineSuggestionUiCallback) {
-        if (sDebug) Slog.d(TAG, "createAugmentedInlineSuggestionsResponse called");
-
-        final ArrayList<InlineSuggestion> inlineSuggestions = new ArrayList<>();
-        final InlineSuggestionUi inlineSuggestionUi = new InlineSuggestionUi(context);
-        for (Dataset dataset : datasets) {
-            final int fieldIndex = dataset.getFieldIds().indexOf(autofillId);
-            if (fieldIndex < 0) {
-                Slog.w(TAG, "AutofillId=" + autofillId + " not found in dataset");
-                return null;
-            }
-            final InlinePresentation inlinePresentation = dataset.getFieldInlinePresentation(
-                    fieldIndex);
-            if (inlinePresentation == null) {
-                Slog.w(TAG, "InlinePresentation not found in dataset");
-                return null;
-            }
-            InlineSuggestion inlineSuggestion = createAugmentedInlineSuggestion(dataset,
-                    inlinePresentation, inlineSuggestionUi, inlineSuggestionUiCallback);
-            inlineSuggestions.add(inlineSuggestion);
-        }
-        return new InlineSuggestionsResponse(inlineSuggestions);
+            @NonNull InlineSuggestionUiCallback inlineSuggestionUiCallback,
+            @NonNull Runnable onErrorCallback) {
+        return createInlineSuggestionsResponseInternal(datasets, autofillId,
+                context, onErrorCallback, (dataset, inlinePresentation, inlineSuggestionUi,
+                        filedIndex) -> createAugmentedInlineSuggestion(dataset,
+                    inlinePresentation, inlineSuggestionUi, inlineSuggestionUiCallback));
     }
 
     /**
@@ -97,11 +76,26 @@
             @NonNull Dataset[] datasets,
             @NonNull AutofillId autofillId,
             @NonNull Context context,
-            @NonNull AutoFillUI.AutoFillUiCallback client) {
-        if (sDebug) Slog.d(TAG, "createInlineSuggestionsResponse called");
+            @NonNull AutoFillUI.AutoFillUiCallback client,
+            @NonNull Runnable onErrorCallback) {
+        return createInlineSuggestionsResponseInternal(datasets, autofillId,
+                context, onErrorCallback, (dataset, inlinePresentation, inlineSuggestionUi,
+                        filedIndex) -> createInlineSuggestion(requestId, dataset, filedIndex,
+                        inlinePresentation, inlineSuggestionUi, client));
+    }
+
+    private static InlineSuggestionsResponse createInlineSuggestionsResponseInternal(
+            @NonNull Dataset[] datasets,
+            @NonNull AutofillId autofillId,
+            @NonNull Context context,
+            @NonNull Runnable onErrorCallback,
+            @NonNull QuadFunction<Dataset, InlinePresentation, InlineSuggestionUi,
+                    Integer, InlineSuggestion> suggestionFactory) {
+        if (sDebug) Slog.d(TAG, "createAugmentedInlineSuggestionsResponse called");
 
         final ArrayList<InlineSuggestion> inlineSuggestions = new ArrayList<>();
-        final InlineSuggestionUi inlineSuggestionUi = new InlineSuggestionUi(context);
+        final InlineSuggestionUi inlineSuggestionUi = new InlineSuggestionUi(context,
+                onErrorCallback);
         for (Dataset dataset : datasets) {
             final int fieldIndex = dataset.getFieldIds().indexOf(autofillId);
             if (fieldIndex < 0) {
@@ -114,9 +108,8 @@
                 Slog.w(TAG, "InlinePresentation not found in dataset");
                 return null;
             }
-            InlineSuggestion inlineSuggestion = createInlineSuggestion(requestId, dataset,
-                    fieldIndex,
-                    inlinePresentation, inlineSuggestionUi, client);
+            InlineSuggestion inlineSuggestion = suggestionFactory.apply(dataset,
+                    inlinePresentation, inlineSuggestionUi, fieldIndex);
             inlineSuggestions.add(inlineSuggestion);
         }
         return new InlineSuggestionsResponse(inlineSuggestions);
@@ -131,9 +124,8 @@
                 inlinePresentation.getInlinePresentationSpec(),
                 InlineSuggestionInfo.SOURCE_PLATFORM, new String[]{""},
                 InlineSuggestionInfo.TYPE_SUGGESTION);
-        final View.OnClickListener onClickListener = v -> {
+        final View.OnClickListener onClickListener = v ->
             inlineSuggestionUiCallback.autofill(dataset);
-        };
         final InlineSuggestion inlineSuggestion = new InlineSuggestion(inlineSuggestionInfo,
                 createInlineContentProvider(inlinePresentation, inlineSuggestionUi,
                         onClickListener));
diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionRoot.java b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionRoot.java
new file mode 100644
index 0000000..8d476d7
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionRoot.java
@@ -0,0 +1,82 @@
+/*
+ * 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.autofill.ui;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.util.Log;
+import android.util.MathUtils;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+import android.widget.FrameLayout;
+
+import com.android.server.LocalServices;
+import com.android.server.wm.WindowManagerInternal;
+
+/**
+ * This class is the root view for an inline suggestion. It is responsible for
+ * detecting the click on the item and to also transfer input focus to the IME
+ * window if we detect the user is scrolling.
+ */
+ // TODO(b/146453086) Move to ExtServices and add @SystemApi to transfer touch focus
+@SuppressLint("ViewConstructor")
+class InlineSuggestionRoot extends FrameLayout {
+    private static final String LOG_TAG = InlineSuggestionRoot.class.getSimpleName();
+
+    private final @NonNull Runnable mOnErrorCallback;
+    private final int mTouchSlop;
+
+    private float mDownX;
+    private float mDownY;
+
+    InlineSuggestionRoot(@NonNull Context context, @NonNull Runnable onErrorCallback) {
+        super(context);
+        mOnErrorCallback = onErrorCallback;
+        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+    }
+
+    @Override
+    @SuppressLint("ClickableViewAccessibility")
+    public boolean onTouchEvent(@NonNull MotionEvent event) {
+        switch (event.getActionMasked()) {
+            case MotionEvent.ACTION_DOWN: {
+                mDownX = event.getX();
+                mDownY = event.getY();
+            } break;
+
+            case MotionEvent.ACTION_MOVE: {
+                final float distance = MathUtils.dist(mDownX, mDownY,
+                        event.getX(), event.getY());
+                if (distance > mTouchSlop) {
+                    transferTouchFocusToImeWindow();
+                }
+            } break;
+        }
+        return super.onTouchEvent(event);
+    }
+
+    private void transferTouchFocusToImeWindow() {
+        final WindowManagerInternal windowManagerInternal = LocalServices.getService(
+                WindowManagerInternal.class);
+        if (!windowManagerInternal.transferTouchFocusToImeWindow(getViewRootImpl().getInputToken(),
+                getContext().getDisplayId())) {
+            Log.e(LOG_TAG, "Cannot transfer touch focus from suggestion to IME");
+            mOnErrorCallback.run();
+        }
+    }
+}
diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java
index 2adefea..bf148a6 100644
--- a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java
@@ -67,10 +67,12 @@
     // (int)}. This name is a single string of the form "package:type/entry".
     private static final Pattern RESOURCE_NAME_PATTERN = Pattern.compile("([^:]+):([^/]+)/(\\S+)");
 
-    private final Context mContext;
+    private final @NonNull Context mContext;
+    private final @NonNull Runnable mOnErrorCallback;
 
-    public InlineSuggestionUi(Context context) {
+    InlineSuggestionUi(@NonNull Context context, @NonNull Runnable onErrorCallback) {
         this.mContext = context;
+        mOnErrorCallback = onErrorCallback;
     }
 
     /**
@@ -94,15 +96,17 @@
         }
         final View suggestionView = renderSlice(inlinePresentation.getSlice(),
                 contextThemeWrapper);
-        if (onClickListener != null) {
-            suggestionView.setOnClickListener(onClickListener);
-        }
+
+        final InlineSuggestionRoot suggestionRoot = new InlineSuggestionRoot(
+                mContext, mOnErrorCallback);
+        suggestionRoot.addView(suggestionView);
+        suggestionRoot.setOnClickListener(onClickListener);
 
         WindowManager.LayoutParams lp =
                 new WindowManager.LayoutParams(width, height,
                         WindowManager.LayoutParams.TYPE_APPLICATION, 0,
                         PixelFormat.TRANSPARENT);
-        wvr.addView(suggestionView, lp);
+        wvr.addView(suggestionRoot, lp);
         return sc;
     }
 
diff --git a/services/core/Android.bp b/services/core/Android.bp
index a603fa9..f33237f 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -115,8 +115,8 @@
         "android.hardware.health-V2.0-java",
         "android.hardware.light-java",
         "android.hardware.weaver-V1.0-java",
-        "android.hardware.biometrics.face-V1.0-java",
-        "android.hardware.biometrics.fingerprint-V2.1-java",
+        "android.hardware.biometrics.face-V1.1-java",
+        "android.hardware.biometrics.fingerprint-V2.2-java",
         "android.hardware.oemlock-V1.0-java",
         "android.hardware.configstore-V1.0-java",
         "android.hardware.contexthub-V1.0-java",
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index ce5e241..caacf13 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -48,8 +48,11 @@
 import static android.system.OsConstants.IPPROTO_TCP;
 import static android.system.OsConstants.IPPROTO_UDP;
 
+import static java.util.Map.Entry;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.AppOpsManager;
 import android.app.BroadcastOptions;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
@@ -62,6 +65,8 @@
 import android.database.ContentObserver;
 import android.net.CaptivePortal;
 import android.net.ConnectionInfo;
+import android.net.ConnectivityDiagnosticsManager.ConnectivityReport;
+import android.net.ConnectivityDiagnosticsManager.DataStallReport;
 import android.net.ConnectivityManager;
 import android.net.ICaptivePortal;
 import android.net.IConnectivityDiagnosticsCallback;
@@ -130,6 +135,7 @@
 import android.os.Messenger;
 import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
+import android.os.PersistableBundle;
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
@@ -170,6 +176,7 @@
 import com.android.internal.util.AsyncChannel;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.LocationPermissionChecker;
 import com.android.internal.util.MessageUtils;
 import com.android.internal.util.XmlUtils;
 import com.android.server.am.BatteryStatsService;
@@ -492,9 +499,9 @@
      /**
       * Event for NetworkMonitor/NetworkAgentInfo to inform ConnectivityService that the network has
       * been tested.
-      * obj = String representing URL that Internet probe was redirect to, if it was redirected.
-      * arg1 = One of the NETWORK_TESTED_RESULT_* constants.
-      * arg2 = NetID.
+      * obj = {@link NetworkTestedResults} representing information sent from NetworkMonitor.
+      * data = PersistableBundle of extras passed from NetworkMonitor. If {@link
+      * NetworkMonitorCallbacks#notifyNetworkTested} is called, this will be null.
       */
     private static final int EVENT_NETWORK_TESTED = 41;
 
@@ -595,7 +602,10 @@
 
     private Set<String> mWolSupportedInterfaces;
 
-    private TelephonyManager mTelephonyManager;
+    private final TelephonyManager mTelephonyManager;
+    private final AppOpsManager mAppOpsManager;
+
+    private final LocationPermissionChecker mLocationPermissionChecker;
 
     private KeepaliveTracker mKeepaliveTracker;
     private NetworkNotificationManager mNotifier;
@@ -951,6 +961,7 @@
         mDeps = Objects.requireNonNull(deps, "missing Dependencies");
         mSystemProperties = mDeps.getSystemProperties();
         mNetIdManager = mDeps.makeNetIdManager();
+        mContext = Objects.requireNonNull(context, "missing Context");
 
         mMetricsLog = logger;
         mDefaultRequest = createDefaultInternetRequestForTransport(-1, NetworkRequest.Type.REQUEST);
@@ -979,7 +990,6 @@
 
         mLingerDelayMs = mSystemProperties.getInt(LINGER_DELAY_PROPERTY, DEFAULT_LINGER_DELAY_MS);
 
-        mContext = Objects.requireNonNull(context, "missing Context");
         mNMS = Objects.requireNonNull(netManager, "missing INetworkManagementService");
         mStatsService = Objects.requireNonNull(statsService, "missing INetworkStatsService");
         mPolicyManager = Objects.requireNonNull(policyManager, "missing INetworkPolicyManager");
@@ -992,6 +1002,8 @@
         mNetd = netd;
         mKeyStore = KeyStore.getInstance();
         mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+        mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
+        mLocationPermissionChecker = new LocationPermissionChecker(mContext);
 
         // To ensure uid rules are synchronized with Network Policy, register for
         // NetworkPolicyManagerService events must happen prior to NetworkPolicyManagerService
@@ -1157,6 +1169,7 @@
             int transportType, NetworkRequest.Type type) {
         final NetworkCapabilities netCap = new NetworkCapabilities();
         netCap.addCapability(NET_CAPABILITY_INTERNET);
+        netCap.setRequestorUidAndPackageName(Process.myUid(), mContext.getPackageName());
         if (transportType > -1) {
             netCap.addTransportType(transportType);
         }
@@ -1687,10 +1700,12 @@
         return newLp;
     }
 
-    private void restrictRequestUidsForCaller(NetworkCapabilities nc) {
+    private void restrictRequestUidsForCallerAndSetRequestorInfo(NetworkCapabilities nc,
+            int callerUid, String callerPackageName) {
         if (!checkSettingsPermission()) {
-            nc.setSingleUid(Binder.getCallingUid());
+            nc.setSingleUid(callerUid);
         }
+        nc.setRequestorUidAndPackageName(callerUid, callerPackageName);
         nc.setAdministratorUids(Collections.EMPTY_LIST);
 
         // Clear owner UID; this can never come from an app.
@@ -2101,6 +2116,12 @@
                 NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
     }
 
+    private boolean checkNetworkStackPermission(int pid, int uid) {
+        return checkAnyPermissionOf(pid, uid,
+                android.Manifest.permission.NETWORK_STACK,
+                NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
+    }
+
     private boolean checkNetworkSignalStrengthWakeupPermission(int pid, int uid) {
         return checkAnyPermissionOf(pid, uid,
                 android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP,
@@ -2747,88 +2768,21 @@
                     break;
                 }
                 case EVENT_NETWORK_TESTED: {
-                    final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(msg.arg2);
+                    final NetworkTestedResults results = (NetworkTestedResults) msg.obj;
+
+                    final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(results.mNetId);
                     if (nai == null) break;
 
-                    final boolean wasPartial = nai.partialConnectivity;
-                    nai.partialConnectivity = ((msg.arg1 & NETWORK_VALIDATION_RESULT_PARTIAL) != 0);
-                    final boolean partialConnectivityChanged =
-                            (wasPartial != nai.partialConnectivity);
+                    handleNetworkTested(nai, results.mTestResult,
+                            (results.mRedirectUrl == null) ? "" : results.mRedirectUrl);
 
-                    final boolean valid = ((msg.arg1 & NETWORK_VALIDATION_RESULT_VALID) != 0);
-                    final boolean wasValidated = nai.lastValidated;
-                    final boolean wasDefault = isDefaultNetwork(nai);
-                    // Only show a connected notification if the network is pending validation
-                    // after the captive portal app was open, and it has now validated.
-                    if (nai.captivePortalValidationPending && valid) {
-                        // User is now logged in, network validated.
-                        nai.captivePortalValidationPending = false;
-                        showNetworkNotification(nai, NotificationType.LOGGED_IN);
-                    }
-
-                    final String redirectUrl = (msg.obj instanceof String) ? (String) msg.obj : "";
-
-                    if (DBG) {
-                        final String logMsg = !TextUtils.isEmpty(redirectUrl)
-                                 ? " with redirect to " + redirectUrl
-                                 : "";
-                        log(nai.name() + " validation " + (valid ? "passed" : "failed") + logMsg);
-                    }
-                    if (valid != nai.lastValidated) {
-                        if (wasDefault) {
-                            mDeps.getMetricsLogger()
-                                    .defaultNetworkMetrics().logDefaultNetworkValidity(
-                                            SystemClock.elapsedRealtime(), valid);
-                        }
-                        final int oldScore = nai.getCurrentScore();
-                        nai.lastValidated = valid;
-                        nai.everValidated |= valid;
-                        updateCapabilities(oldScore, nai, nai.networkCapabilities);
-                        // If score has changed, rebroadcast to NetworkProviders. b/17726566
-                        if (oldScore != nai.getCurrentScore()) sendUpdatedScoreToFactories(nai);
-                        if (valid) {
-                            handleFreshlyValidatedNetwork(nai);
-                            // Clear NO_INTERNET, PRIVATE_DNS_BROKEN, PARTIAL_CONNECTIVITY and
-                            // LOST_INTERNET notifications if network becomes valid.
-                            mNotifier.clearNotification(nai.network.netId,
-                                    NotificationType.NO_INTERNET);
-                            mNotifier.clearNotification(nai.network.netId,
-                                    NotificationType.LOST_INTERNET);
-                            mNotifier.clearNotification(nai.network.netId,
-                                    NotificationType.PARTIAL_CONNECTIVITY);
-                            mNotifier.clearNotification(nai.network.netId,
-                                    NotificationType.PRIVATE_DNS_BROKEN);
-                            // If network becomes valid, the hasShownBroken should be reset for
-                            // that network so that the notification will be fired when the private
-                            // DNS is broken again.
-                            nai.networkAgentConfig.hasShownBroken = false;
-                        }
-                    } else if (partialConnectivityChanged) {
-                        updateCapabilities(nai.getCurrentScore(), nai, nai.networkCapabilities);
-                    }
-                    updateInetCondition(nai);
-                    // Let the NetworkAgent know the state of its network
-                    Bundle redirectUrlBundle = new Bundle();
-                    redirectUrlBundle.putString(NetworkAgent.REDIRECT_URL_KEY, redirectUrl);
-                    // TODO: Evaluate to update partial connectivity to status to NetworkAgent.
-                    nai.asyncChannel.sendMessage(
-                            NetworkAgent.CMD_REPORT_NETWORK_STATUS,
-                            (valid ? NetworkAgent.VALID_NETWORK : NetworkAgent.INVALID_NETWORK),
-                            0, redirectUrlBundle);
-
-                    // If NetworkMonitor detects partial connectivity before
-                    // EVENT_PROMPT_UNVALIDATED arrives, show the partial connectivity notification
-                    // immediately. Re-notify partial connectivity silently if no internet
-                    // notification already there.
-                    if (!wasPartial && nai.partialConnectivity) {
-                        // Remove delayed message if there is a pending message.
-                        mHandler.removeMessages(EVENT_PROMPT_UNVALIDATED, nai.network);
-                        handlePromptUnvalidated(nai.network);
-                    }
-
-                    if (wasValidated && !nai.lastValidated) {
-                        handleNetworkUnvalidated(nai);
-                    }
+                    // Invoke ConnectivityReport generation for this Network test event.
+                    final Message m =
+                            mConnectivityDiagnosticsHandler.obtainMessage(
+                                    ConnectivityDiagnosticsHandler.EVENT_NETWORK_TESTED,
+                                    new ConnectivityReportEvent(results.mTimestampMillis, nai));
+                    m.setData(msg.getData());
+                    mConnectivityDiagnosticsHandler.sendMessage(m);
                     break;
                 }
                 case EVENT_PROVISIONING_NOTIFICATION: {
@@ -2879,6 +2833,87 @@
             return true;
         }
 
+        private void handleNetworkTested(
+                @NonNull NetworkAgentInfo nai, int testResult, @NonNull String redirectUrl) {
+            final boolean wasPartial = nai.partialConnectivity;
+            nai.partialConnectivity = ((testResult & NETWORK_VALIDATION_RESULT_PARTIAL) != 0);
+            final boolean partialConnectivityChanged =
+                    (wasPartial != nai.partialConnectivity);
+
+            final boolean valid = ((testResult & NETWORK_VALIDATION_RESULT_VALID) != 0);
+            final boolean wasValidated = nai.lastValidated;
+            final boolean wasDefault = isDefaultNetwork(nai);
+            // Only show a connected notification if the network is pending validation
+            // after the captive portal app was open, and it has now validated.
+            if (nai.captivePortalValidationPending && valid) {
+                // User is now logged in, network validated.
+                nai.captivePortalValidationPending = false;
+                showNetworkNotification(nai, NotificationType.LOGGED_IN);
+            }
+
+            if (DBG) {
+                final String logMsg = !TextUtils.isEmpty(redirectUrl)
+                        ? " with redirect to " + redirectUrl
+                        : "";
+                log(nai.name() + " validation " + (valid ? "passed" : "failed") + logMsg);
+            }
+            if (valid != nai.lastValidated) {
+                if (wasDefault) {
+                    mDeps.getMetricsLogger()
+                            .defaultNetworkMetrics().logDefaultNetworkValidity(
+                            SystemClock.elapsedRealtime(), valid);
+                }
+                final int oldScore = nai.getCurrentScore();
+                nai.lastValidated = valid;
+                nai.everValidated |= valid;
+                updateCapabilities(oldScore, nai, nai.networkCapabilities);
+                // If score has changed, rebroadcast to NetworkProviders. b/17726566
+                if (oldScore != nai.getCurrentScore()) sendUpdatedScoreToFactories(nai);
+                if (valid) {
+                    handleFreshlyValidatedNetwork(nai);
+                    // Clear NO_INTERNET, PRIVATE_DNS_BROKEN, PARTIAL_CONNECTIVITY and
+                    // LOST_INTERNET notifications if network becomes valid.
+                    mNotifier.clearNotification(nai.network.netId,
+                            NotificationType.NO_INTERNET);
+                    mNotifier.clearNotification(nai.network.netId,
+                            NotificationType.LOST_INTERNET);
+                    mNotifier.clearNotification(nai.network.netId,
+                            NotificationType.PARTIAL_CONNECTIVITY);
+                    mNotifier.clearNotification(nai.network.netId,
+                            NotificationType.PRIVATE_DNS_BROKEN);
+                    // If network becomes valid, the hasShownBroken should be reset for
+                    // that network so that the notification will be fired when the private
+                    // DNS is broken again.
+                    nai.networkAgentConfig.hasShownBroken = false;
+                }
+            } else if (partialConnectivityChanged) {
+                updateCapabilities(nai.getCurrentScore(), nai, nai.networkCapabilities);
+            }
+            updateInetCondition(nai);
+            // Let the NetworkAgent know the state of its network
+            Bundle redirectUrlBundle = new Bundle();
+            redirectUrlBundle.putString(NetworkAgent.REDIRECT_URL_KEY, redirectUrl);
+            // TODO: Evaluate to update partial connectivity to status to NetworkAgent.
+            nai.asyncChannel.sendMessage(
+                    NetworkAgent.CMD_REPORT_NETWORK_STATUS,
+                    (valid ? NetworkAgent.VALID_NETWORK : NetworkAgent.INVALID_NETWORK),
+                    0, redirectUrlBundle);
+
+            // If NetworkMonitor detects partial connectivity before
+            // EVENT_PROMPT_UNVALIDATED arrives, show the partial connectivity notification
+            // immediately. Re-notify partial connectivity silently if no internet
+            // notification already there.
+            if (!wasPartial && nai.partialConnectivity) {
+                // Remove delayed message if there is a pending message.
+                mHandler.removeMessages(EVENT_PROMPT_UNVALIDATED, nai.network);
+                handlePromptUnvalidated(nai.network);
+            }
+
+            if (wasValidated && !nai.lastValidated) {
+                handleNetworkUnvalidated(nai);
+            }
+        }
+
         private int getCaptivePortalMode() {
             return Settings.Global.getInt(mContext.getContentResolver(),
                     Settings.Global.CAPTIVE_PORTAL_MODE,
@@ -2927,8 +2962,23 @@
 
         @Override
         public void notifyNetworkTested(int testResult, @Nullable String redirectUrl) {
-            mTrackerHandler.sendMessage(mTrackerHandler.obtainMessage(EVENT_NETWORK_TESTED,
-                    testResult, mNetId, redirectUrl));
+            notifyNetworkTestedWithExtras(testResult, redirectUrl, SystemClock.elapsedRealtime(),
+                    PersistableBundle.EMPTY);
+        }
+
+        @Override
+        public void notifyNetworkTestedWithExtras(
+                int testResult,
+                @Nullable String redirectUrl,
+                long timestampMillis,
+                @NonNull PersistableBundle extras) {
+            final Message msg =
+                    mTrackerHandler.obtainMessage(
+                            EVENT_NETWORK_TESTED,
+                            new NetworkTestedResults(
+                                    mNetId, testResult, timestampMillis, redirectUrl));
+            msg.setData(new Bundle(extras));
+            mTrackerHandler.sendMessage(msg);
         }
 
         @Override
@@ -2970,6 +3020,21 @@
         }
 
         @Override
+        public void notifyDataStallSuspected(
+                long timestampMillis, int detectionMethod, PersistableBundle extras) {
+            final Message msg =
+                    mConnectivityDiagnosticsHandler.obtainMessage(
+                            ConnectivityDiagnosticsHandler.EVENT_DATA_STALL_SUSPECTED,
+                            detectionMethod, mNetId, timestampMillis);
+            msg.setData(new Bundle(extras));
+
+            // NetworkStateTrackerHandler currently doesn't take any actions based on data
+            // stalls so send the message directly to ConnectivityDiagnosticsHandler and avoid
+            // the cost of going through two handlers.
+            mConnectivityDiagnosticsHandler.sendMessage(msg);
+        }
+
+        @Override
         public int getInterfaceVersion() {
             return this.VERSION;
         }
@@ -4143,6 +4208,19 @@
         final int connectivityInfo = encodeBool(hasConnectivity);
         mHandler.sendMessage(
                 mHandler.obtainMessage(EVENT_REVALIDATE_NETWORK, uid, connectivityInfo, network));
+
+        final NetworkAgentInfo nai;
+        if (network == null) {
+            nai = getDefaultNetwork();
+        } else {
+            nai = getNetworkAgentInfoForNetwork(network);
+        }
+        if (nai != null) {
+            mConnectivityDiagnosticsHandler.sendMessage(
+                    mConnectivityDiagnosticsHandler.obtainMessage(
+                            ConnectivityDiagnosticsHandler.EVENT_NETWORK_CONNECTIVITY_REPORTED,
+                            connectivityInfo, 0, nai));
+        }
     }
 
     private void handleReportNetworkConnectivity(
@@ -5229,7 +5307,7 @@
     // This checks that the passed capabilities either do not request a
     // specific SSID/SignalStrength, or the calling app has permission to do so.
     private void ensureSufficientPermissionsForRequest(NetworkCapabilities nc,
-            int callerPid, int callerUid) {
+            int callerPid, int callerUid, String callerPackageName) {
         if (null != nc.getSSID() && !checkSettingsPermission(callerPid, callerUid)) {
             throw new SecurityException("Insufficient permissions to request a specific SSID");
         }
@@ -5239,6 +5317,7 @@
             throw new SecurityException(
                     "Insufficient permissions to request a specific signal strength");
         }
+        mAppOpsManager.checkPackage(callerUid, callerPackageName);
     }
 
     private ArrayList<Integer> getSignalStrengthThresholds(NetworkAgentInfo nai) {
@@ -5285,7 +5364,6 @@
             return;
         }
         MatchAllNetworkSpecifier.checkNotMatchAllNetworkSpecifier(ns);
-        ns.assertValidFromUid(Binder.getCallingUid());
     }
 
     private void ensureValid(NetworkCapabilities nc) {
@@ -5297,7 +5375,9 @@
 
     @Override
     public NetworkRequest requestNetwork(NetworkCapabilities networkCapabilities,
-            Messenger messenger, int timeoutMs, IBinder binder, int legacyType) {
+            Messenger messenger, int timeoutMs, IBinder binder, int legacyType,
+            @NonNull String callingPackageName) {
+        final int callingUid = Binder.getCallingUid();
         final NetworkRequest.Type type = (networkCapabilities == null)
                 ? NetworkRequest.Type.TRACK_DEFAULT
                 : NetworkRequest.Type.REQUEST;
@@ -5305,7 +5385,7 @@
         // the default network request. This allows callers to keep track of
         // the system default network.
         if (type == NetworkRequest.Type.TRACK_DEFAULT) {
-            networkCapabilities = createDefaultNetworkCapabilitiesForUid(Binder.getCallingUid());
+            networkCapabilities = createDefaultNetworkCapabilitiesForUid(callingUid);
             enforceAccessPermission();
         } else {
             networkCapabilities = new NetworkCapabilities(networkCapabilities);
@@ -5317,13 +5397,14 @@
         }
         ensureRequestableCapabilities(networkCapabilities);
         ensureSufficientPermissionsForRequest(networkCapabilities,
-                Binder.getCallingPid(), Binder.getCallingUid());
+                Binder.getCallingPid(), callingUid, callingPackageName);
         // Set the UID range for this request to the single UID of the requester, or to an empty
         // set of UIDs if the caller has the appropriate permission and UIDs have not been set.
         // This will overwrite any allowed UIDs in the requested capabilities. Though there
         // are no visible methods to set the UIDs, an app could use reflection to try and get
         // networks for other apps so it's essential that the UIDs are overwritten.
-        restrictRequestUidsForCaller(networkCapabilities);
+        restrictRequestUidsForCallerAndSetRequestorInfo(networkCapabilities,
+                callingUid, callingPackageName);
 
         if (timeoutMs < 0) {
             throw new IllegalArgumentException("Bad timeout specified");
@@ -5398,16 +5479,18 @@
 
     @Override
     public NetworkRequest pendingRequestForNetwork(NetworkCapabilities networkCapabilities,
-            PendingIntent operation) {
+            PendingIntent operation, @NonNull String callingPackageName) {
         Objects.requireNonNull(operation, "PendingIntent cannot be null.");
+        final int callingUid = Binder.getCallingUid();
         networkCapabilities = new NetworkCapabilities(networkCapabilities);
         enforceNetworkRequestPermissions(networkCapabilities);
         enforceMeteredApnPolicy(networkCapabilities);
         ensureRequestableCapabilities(networkCapabilities);
         ensureSufficientPermissionsForRequest(networkCapabilities,
-                Binder.getCallingPid(), Binder.getCallingUid());
+                Binder.getCallingPid(), callingUid, callingPackageName);
         ensureValidNetworkSpecifier(networkCapabilities);
-        restrictRequestUidsForCaller(networkCapabilities);
+        restrictRequestUidsForCallerAndSetRequestorInfo(networkCapabilities,
+                callingUid, callingPackageName);
 
         NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, TYPE_NONE,
                 nextNetworkRequestId(), NetworkRequest.Type.REQUEST);
@@ -5455,15 +5538,16 @@
 
     @Override
     public NetworkRequest listenForNetwork(NetworkCapabilities networkCapabilities,
-            Messenger messenger, IBinder binder) {
+            Messenger messenger, IBinder binder, @NonNull String callingPackageName) {
+        final int callingUid = Binder.getCallingUid();
         if (!hasWifiNetworkListenPermission(networkCapabilities)) {
             enforceAccessPermission();
         }
 
         NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities);
         ensureSufficientPermissionsForRequest(networkCapabilities,
-                Binder.getCallingPid(), Binder.getCallingUid());
-        restrictRequestUidsForCaller(nc);
+                Binder.getCallingPid(), callingUid, callingPackageName);
+        restrictRequestUidsForCallerAndSetRequestorInfo(nc, callingUid, callingPackageName);
         // Apps without the CHANGE_NETWORK_STATE permission can't use background networks, so
         // make all their listens include NET_CAPABILITY_FOREGROUND. That way, they will get
         // onLost and onAvailable callbacks when networks move in and out of the background.
@@ -5483,17 +5567,17 @@
 
     @Override
     public void pendingListenForNetwork(NetworkCapabilities networkCapabilities,
-            PendingIntent operation) {
+            PendingIntent operation, @NonNull String callingPackageName) {
         Objects.requireNonNull(operation, "PendingIntent cannot be null.");
+        final int callingUid = Binder.getCallingUid();
         if (!hasWifiNetworkListenPermission(networkCapabilities)) {
             enforceAccessPermission();
         }
         ensureValid(networkCapabilities);
         ensureSufficientPermissionsForRequest(networkCapabilities,
-                Binder.getCallingPid(), Binder.getCallingUid());
-
+                Binder.getCallingPid(), callingUid, callingPackageName);
         final NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities);
-        restrictRequestUidsForCaller(nc);
+        restrictRequestUidsForCallerAndSetRequestorInfo(nc, callingUid, callingPackageName);
 
         NetworkRequest networkRequest = new NetworkRequest(nc, TYPE_NONE, nextNetworkRequestId(),
                 NetworkRequest.Type.LISTEN);
@@ -6506,6 +6590,7 @@
     }
 
     private ArrayMap<NetworkRequestInfo, NetworkAgentInfo> computeRequestReassignmentForNetwork(
+            @NonNull final NetworkReassignment changes,
             @NonNull final NetworkAgentInfo newNetwork) {
         final int score = newNetwork.getCurrentScore();
         final ArrayMap<NetworkRequestInfo, NetworkAgentInfo> reassignedRequests = new ArrayMap<>();
@@ -6516,7 +6601,10 @@
             // requests or not, and doesn't affect the network's score.
             if (nri.request.isListen()) continue;
 
-            final NetworkAgentInfo currentNetwork = nri.mSatisfier;
+            // The reassignment has been seeded with the initial assignment, therefore
+            // getReassignment can't be null and mNewNetwork is only null if there was no
+            // satisfier in the first place or there was an explicit reassignment to null.
+            final NetworkAgentInfo currentNetwork = changes.getReassignment(nri).mNewNetwork;
             final boolean satisfies = newNetwork.satisfies(nri.request);
             if (newNetwork == currentNetwork && satisfies) continue;
 
@@ -6566,7 +6654,7 @@
         if (VDBG || DDBG) log("rematching " + newNetwork.name());
 
         final ArrayMap<NetworkRequestInfo, NetworkAgentInfo> reassignedRequests =
-                computeRequestReassignmentForNetwork(newNetwork);
+                computeRequestReassignmentForNetwork(changes, newNetwork);
 
         // Find and migrate to this Network any NetworkRequests for
         // which this network is now the best.
@@ -7373,7 +7461,11 @@
 
     @GuardedBy("mVpns")
     private Vpn getVpnIfOwner() {
-        final int uid = Binder.getCallingUid();
+        return getVpnIfOwner(Binder.getCallingUid());
+    }
+
+    @GuardedBy("mVpns")
+    private Vpn getVpnIfOwner(int uid) {
         final int user = UserHandle.getUserId(uid);
 
         final Vpn vpn = mVpns.get(user);
@@ -7469,6 +7561,8 @@
      */
     @VisibleForTesting
     class ConnectivityDiagnosticsHandler extends Handler {
+        private final String mTag = ConnectivityDiagnosticsHandler.class.getSimpleName();
+
         /**
          * Used to handle ConnectivityDiagnosticsCallback registration events from {@link
          * android.net.ConnectivityDiagnosticsManager}.
@@ -7485,6 +7579,37 @@
          */
         private static final int EVENT_UNREGISTER_CONNECTIVITY_DIAGNOSTICS_CALLBACK = 2;
 
+        /**
+         * Event for {@link NetworkStateTrackerHandler} to trigger ConnectivityReport callbacks
+         * after processing {@link #EVENT_NETWORK_TESTED} events.
+         * obj = {@link ConnectivityReportEvent} representing ConnectivityReport info reported from
+         * NetworkMonitor.
+         * data = PersistableBundle of extras passed from NetworkMonitor.
+         *
+         * <p>See {@link ConnectivityService#EVENT_NETWORK_TESTED}.
+         */
+        private static final int EVENT_NETWORK_TESTED = ConnectivityService.EVENT_NETWORK_TESTED;
+
+        /**
+         * Event for NetworkMonitor to inform ConnectivityService that a potential data stall has
+         * been detected on the network.
+         * obj = Long the timestamp (in millis) for when the suspected data stall was detected.
+         * arg1 = {@link DataStallReport#DetectionMethod} indicating the detection method.
+         * arg2 = NetID.
+         * data = PersistableBundle of extras passed from NetworkMonitor.
+         */
+        private static final int EVENT_DATA_STALL_SUSPECTED = 4;
+
+        /**
+         * Event for ConnectivityDiagnosticsHandler to handle network connectivity being reported to
+         * the platform. This event will invoke {@link
+         * IConnectivityDiagnosticsCallback#onNetworkConnectivityReported} for permissioned
+         * callbacks.
+         * obj = Network that was reported on
+         * arg1 = boolint for the quality reported
+         */
+        private static final int EVENT_NETWORK_CONNECTIVITY_REPORTED = 5;
+
         private ConnectivityDiagnosticsHandler(Looper looper) {
             super(looper);
         }
@@ -7502,6 +7627,37 @@
                             (IConnectivityDiagnosticsCallback) msg.obj, msg.arg1);
                     break;
                 }
+                case EVENT_NETWORK_TESTED: {
+                    final ConnectivityReportEvent reportEvent =
+                            (ConnectivityReportEvent) msg.obj;
+
+                    // This is safe because {@link
+                    // NetworkMonitorCallbacks#notifyNetworkTestedWithExtras} receives a
+                    // PersistableBundle and converts it to the Bundle in the incoming Message. If
+                    // {@link NetworkMonitorCallbacks#notifyNetworkTested} is called, msg.data will
+                    // not be set. This is also safe, as msg.getData() will return an empty Bundle.
+                    final PersistableBundle extras = new PersistableBundle(msg.getData());
+                    handleNetworkTestedWithExtras(reportEvent, extras);
+                    break;
+                }
+                case EVENT_DATA_STALL_SUSPECTED: {
+                    final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(msg.arg2);
+                    if (nai == null) break;
+
+                    // This is safe because NetworkMonitorCallbacks#notifyDataStallSuspected
+                    // receives a PersistableBundle and converts it to the Bundle in the incoming
+                    // Message.
+                    final PersistableBundle extras = new PersistableBundle(msg.getData());
+                    handleDataStallSuspected(nai, (long) msg.obj, msg.arg1, extras);
+                    break;
+                }
+                case EVENT_NETWORK_CONNECTIVITY_REPORTED: {
+                    handleNetworkConnectivityReported((NetworkAgentInfo) msg.obj, toBool(msg.arg1));
+                    break;
+                }
+                default: {
+                    Log.e(mTag, "Unrecognized event in ConnectivityDiagnostics: " + msg.what);
+                }
             }
         }
     }
@@ -7511,12 +7667,16 @@
     class ConnectivityDiagnosticsCallbackInfo implements Binder.DeathRecipient {
         @NonNull private final IConnectivityDiagnosticsCallback mCb;
         @NonNull private final NetworkRequestInfo mRequestInfo;
+        @NonNull private final String mCallingPackageName;
 
         @VisibleForTesting
         ConnectivityDiagnosticsCallbackInfo(
-                @NonNull IConnectivityDiagnosticsCallback cb, @NonNull NetworkRequestInfo nri) {
+                @NonNull IConnectivityDiagnosticsCallback cb,
+                @NonNull NetworkRequestInfo nri,
+                @NonNull String callingPackageName) {
             mCb = cb;
             mRequestInfo = nri;
+            mCallingPackageName = callingPackageName;
         }
 
         @Override
@@ -7526,6 +7686,39 @@
         }
     }
 
+    /**
+     * Class used for sending information from {@link
+     * NetworkMonitorCallbacks#notifyNetworkTestedWithExtras} to the handler for processing it.
+     */
+    private static class NetworkTestedResults {
+        private final int mNetId;
+        private final int mTestResult;
+        private final long mTimestampMillis;
+        @Nullable private final String mRedirectUrl;
+
+        private NetworkTestedResults(
+                int netId, int testResult, long timestampMillis, @Nullable String redirectUrl) {
+            mNetId = netId;
+            mTestResult = testResult;
+            mTimestampMillis = timestampMillis;
+            mRedirectUrl = redirectUrl;
+        }
+    }
+
+    /**
+     * Class used for sending information from {@link NetworkStateTrackerHandler} to {@link
+     * ConnectivityDiagnosticsHandler}.
+     */
+    private static class ConnectivityReportEvent {
+        private final long mTimestampMillis;
+        @NonNull private final NetworkAgentInfo mNai;
+
+        private ConnectivityReportEvent(long timestampMillis, @NonNull NetworkAgentInfo nai) {
+            mTimestampMillis = timestampMillis;
+            mNai = nai;
+        }
+    }
+
     private void handleRegisterConnectivityDiagnosticsCallback(
             @NonNull ConnectivityDiagnosticsCallbackInfo cbInfo) {
         ensureRunningOnConnectivityServiceThread();
@@ -7573,18 +7766,115 @@
         cb.asBinder().unlinkToDeath(mConnectivityDiagnosticsCallbacks.remove(cb), 0);
     }
 
+    private void handleNetworkTestedWithExtras(
+            @NonNull ConnectivityReportEvent reportEvent, @NonNull PersistableBundle extras) {
+        final NetworkAgentInfo nai = reportEvent.mNai;
+        final ConnectivityReport report =
+                new ConnectivityReport(
+                        reportEvent.mNai.network,
+                        reportEvent.mTimestampMillis,
+                        nai.linkProperties,
+                        nai.networkCapabilities,
+                        extras);
+        final List<IConnectivityDiagnosticsCallback> results =
+                getMatchingPermissionedCallbacks(nai);
+        for (final IConnectivityDiagnosticsCallback cb : results) {
+            try {
+                cb.onConnectivityReport(report);
+            } catch (RemoteException ex) {
+                loge("Error invoking onConnectivityReport", ex);
+            }
+        }
+    }
+
+    private void handleDataStallSuspected(
+            @NonNull NetworkAgentInfo nai, long timestampMillis, int detectionMethod,
+            @NonNull PersistableBundle extras) {
+        final DataStallReport report =
+                new DataStallReport(nai.network, timestampMillis, detectionMethod, extras);
+        final List<IConnectivityDiagnosticsCallback> results =
+                getMatchingPermissionedCallbacks(nai);
+        for (final IConnectivityDiagnosticsCallback cb : results) {
+            try {
+                cb.onDataStallSuspected(report);
+            } catch (RemoteException ex) {
+                loge("Error invoking onDataStallSuspected", ex);
+            }
+        }
+    }
+
+    private void handleNetworkConnectivityReported(
+            @NonNull NetworkAgentInfo nai, boolean connectivity) {
+        final List<IConnectivityDiagnosticsCallback> results =
+                getMatchingPermissionedCallbacks(nai);
+        for (final IConnectivityDiagnosticsCallback cb : results) {
+            try {
+                cb.onNetworkConnectivityReported(nai.network, connectivity);
+            } catch (RemoteException ex) {
+                loge("Error invoking onNetworkConnectivityReported", ex);
+            }
+        }
+    }
+
+    private List<IConnectivityDiagnosticsCallback> getMatchingPermissionedCallbacks(
+            @NonNull NetworkAgentInfo nai) {
+        final List<IConnectivityDiagnosticsCallback> results = new ArrayList<>();
+        for (Entry<IConnectivityDiagnosticsCallback, ConnectivityDiagnosticsCallbackInfo> entry :
+                mConnectivityDiagnosticsCallbacks.entrySet()) {
+            final ConnectivityDiagnosticsCallbackInfo cbInfo = entry.getValue();
+            final NetworkRequestInfo nri = cbInfo.mRequestInfo;
+            if (nai.satisfies(nri.request)) {
+                if (checkConnectivityDiagnosticsPermissions(
+                        nri.mPid, nri.mUid, nai, cbInfo.mCallingPackageName)) {
+                    results.add(entry.getKey());
+                }
+            }
+        }
+        return results;
+    }
+
+    @VisibleForTesting
+    boolean checkConnectivityDiagnosticsPermissions(
+            int callbackPid, int callbackUid, NetworkAgentInfo nai, String callbackPackageName) {
+        if (checkNetworkStackPermission(callbackPid, callbackUid)) {
+            return true;
+        }
+
+        if (!mLocationPermissionChecker.checkLocationPermission(
+                callbackPackageName, null /* featureId */, callbackUid, null /* message */)) {
+            return false;
+        }
+
+        synchronized (mVpns) {
+            if (getVpnIfOwner(callbackUid) != null) {
+                return true;
+            }
+        }
+
+        // Administrator UIDs also contains the Owner UID
+        if (nai.networkCapabilities.getAdministratorUids().contains(callbackUid)) {
+            return true;
+        }
+
+        return false;
+    }
+
     @Override
     public void registerConnectivityDiagnosticsCallback(
-            @NonNull IConnectivityDiagnosticsCallback callback, @NonNull NetworkRequest request) {
+            @NonNull IConnectivityDiagnosticsCallback callback,
+            @NonNull NetworkRequest request,
+            @NonNull String callingPackageName) {
         if (request.legacyType != TYPE_NONE) {
             throw new IllegalArgumentException("ConnectivityManager.TYPE_* are deprecated."
                     + " Please use NetworkCapabilities instead.");
         }
+        final int callingUid = Binder.getCallingUid();
+        mAppOpsManager.checkPackage(callingUid, callingPackageName);
 
         // 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);
+        restrictRequestUidsForCallerAndSetRequestorInfo(nc, callingUid, callingPackageName);
 
         final NetworkRequest requestWithId =
                 new NetworkRequest(
@@ -7597,7 +7887,7 @@
         // callback's binder death.
         final NetworkRequestInfo nri = new NetworkRequestInfo(requestWithId);
         final ConnectivityDiagnosticsCallbackInfo cbInfo =
-                new ConnectivityDiagnosticsCallbackInfo(callback, nri);
+                new ConnectivityDiagnosticsCallbackInfo(callback, nri, callingPackageName);
 
         mConnectivityDiagnosticsHandler.sendMessage(
                 mConnectivityDiagnosticsHandler.obtainMessage(
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 50843b0..63cddac 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -30,6 +30,7 @@
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
@@ -305,11 +306,7 @@
                         public void onOpChanged(int op, String packageName) {
                             // onOpChanged invoked on ui thread, move to our thread to reduce risk
                             // of blocking ui thread
-                            mHandler.post(() -> {
-                                synchronized (mLock) {
-                                    onAppOpChangedLocked();
-                                }
-                            });
+                            mHandler.post(() -> onAppOpChanged(packageName));
                         }
                     });
             mPackageManager.addOnPermissionsChangeListener(
@@ -392,13 +389,26 @@
         }
     }
 
-    @GuardedBy("mLock")
-    private void onAppOpChangedLocked() {
-        for (Receiver receiver : mReceivers.values()) {
-            receiver.updateMonitoring(true);
-        }
-        for (LocationProviderManager manager : mProviderManagers) {
-            applyRequirementsLocked(manager);
+    private void onAppOpChanged(String packageName) {
+        synchronized (mLock) {
+            for (Receiver receiver : mReceivers.values()) {
+                if (receiver.mCallerIdentity.mPackageName.equals(packageName)) {
+                    receiver.updateMonitoring(true);
+                }
+            }
+
+            HashSet<String> affectedProviders = new HashSet<>(mRecordsByProvider.size());
+            for (Entry<String, ArrayList<UpdateRecord>> entry : mRecordsByProvider.entrySet()) {
+                String provider = entry.getKey();
+                for (UpdateRecord record : entry.getValue()) {
+                    if (record.mReceiver.mCallerIdentity.mPackageName.equals(packageName)) {
+                        affectedProviders.add(provider);
+                    }
+                }
+            }
+            for (String provider : affectedProviders) {
+                applyRequirementsLocked(provider);
+            }
         }
     }
 
@@ -2161,10 +2171,10 @@
 
     @Override
     public boolean injectLocation(Location location) {
-        mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE,
-                "Location Hardware permission not granted to inject location");
-        mContext.enforceCallingPermission(ACCESS_FINE_LOCATION,
-                "Access Fine Location permission not granted to inject Location");
+        mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE, null);
+        mContext.enforceCallingPermission(ACCESS_FINE_LOCATION, null);
+
+        Preconditions.checkArgument(location.isComplete());
 
         synchronized (mLock) {
             LocationProviderManager manager = getLocationProviderManager(location.getProvider());
@@ -2410,20 +2420,15 @@
 
     @Override
     public boolean isLocationEnabledForUser(int userId) {
-        if (UserHandle.getCallingUserId() != userId) {
-            mContext.enforceCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_USERS,
-                    null);
-        }
-
+        userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+                userId, false, false, "isLocationEnabledForUser", null);
         return mSettingsHelper.isLocationEnabled(userId);
     }
 
     @Override
     public boolean isProviderEnabledForUser(String providerName, int userId) {
-        if (UserHandle.getCallingUserId() != userId) {
-            mContext.enforceCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_USERS,
-                    null);
-        }
+        userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+                userId, false, false, "isProviderEnabledForUser", null);
 
         // Fused provider is accessed indirectly via criteria rather than the provider-based APIs,
         // so we discourage its use
@@ -2732,6 +2737,9 @@
 
     @Override
     public void setTestProviderLocation(String provider, Location location, String packageName) {
+        Preconditions.checkArgument(location.isComplete(),
+                "incomplete location object, missing timestamp or accuracy?");
+
         if (mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), packageName)
                 != AppOpsManager.MODE_ALLOWED) {
             return;
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 4a65a96..fabe92db 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -287,7 +287,7 @@
     // When the restriction is enabled, foreground service started from background will not have
     // while-in-use permissions like location, camera and microphone. (The foreground service can be
     // started, the restriction is on while-in-use permissions.)
-    volatile boolean mFlagBackgroundFgsStartRestrictionEnabled;
+    volatile boolean mFlagBackgroundFgsStartRestrictionEnabled = true;
 
     private final ActivityManagerService mService;
     private ContentResolver mResolver;
@@ -304,18 +304,19 @@
     // we have no limit on the number of service, visible, foreground, or other such
     // processes and the number of those processes does not count against the cached
     // process limit.
-    public int CUR_MAX_CACHED_PROCESSES;
+    public int CUR_MAX_CACHED_PROCESSES = DEFAULT_MAX_CACHED_PROCESSES;
 
     // The maximum number of empty app processes we will let sit around.
-    public int CUR_MAX_EMPTY_PROCESSES;
+    public int CUR_MAX_EMPTY_PROCESSES = computeEmptyProcessLimit(CUR_MAX_CACHED_PROCESSES);
 
     // The number of empty apps at which we don't consider it necessary to do
     // memory trimming.
-    public int CUR_TRIM_EMPTY_PROCESSES;
+    public int CUR_TRIM_EMPTY_PROCESSES = computeEmptyProcessLimit(MAX_CACHED_PROCESSES) / 2;
 
     // The number of cached at which we don't consider it necessary to do
     // memory trimming.
-    public int CUR_TRIM_CACHED_PROCESSES;
+    public int CUR_TRIM_CACHED_PROCESSES =
+            (MAX_CACHED_PROCESSES - computeEmptyProcessLimit(MAX_CACHED_PROCESSES)) / 3;
 
     /**
      * Packages that can't be killed even if it's requested to be killed on imperceptible.
@@ -419,6 +420,8 @@
                 context.getResources().getIntArray(
                 com.android.internal.R.array.config_defaultImperceptibleKillingExemptionProcStates))
                 .boxed().collect(Collectors.toList());
+        IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES.addAll(mDefaultImperceptibleKillExemptPackages);
+        IMPERCEPTIBLE_KILL_EXEMPT_PROC_STATES.addAll(mDefaultImperceptibleKillExemptProcStates);
     }
 
     public void start(ContentResolver resolver) {
@@ -550,8 +553,6 @@
 
             // For new flags that are intended for server-side experiments, please use the new
             // DeviceConfig package.
-
-            updateMaxCachedProcesses();
         }
     }
 
@@ -580,6 +581,9 @@
     }
 
     private void updateOomAdjUpdatePolicy() {
+
+
+
         OOMADJ_UPDATE_QUICK = DeviceConfig.getInt(
                 DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                 KEY_OOMADJ_UPDATE_POLICY,
diff --git a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
index b19a37e..f872c6b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
+++ b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
@@ -50,7 +50,7 @@
     static final boolean DEBUG_BROADCAST_LIGHT = DEBUG_BROADCAST || false;
     static final boolean DEBUG_BROADCAST_DEFERRAL = DEBUG_BROADCAST || false;
     static final boolean DEBUG_COMPACTION = DEBUG_ALL || false;
-    static final boolean DEBUG_FREEZER = DEBUG_ALL || false;
+    static final boolean DEBUG_FREEZER = DEBUG_ALL || true;
     static final boolean DEBUG_LRU = DEBUG_ALL || false;
     static final boolean DEBUG_MU = DEBUG_ALL || false;
     static final boolean DEBUG_NETWORK = DEBUG_ALL || false;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 148f7de..3ad96ea 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2617,7 +2617,7 @@
         mProcessCpuThread.start();
 
         mBatteryStatsService.publish();
-        mAppOpsService.publish(mContext);
+        mAppOpsService.publish();
         Slog.d("AppOps", "AppOpsService published");
         LocalServices.addService(ActivityManagerInternal.class, mInternal);
         mActivityTaskManager.onActivityManagerInternalAdded();
@@ -6843,7 +6843,8 @@
                     cpi = cpr.info;
                     if (isSingleton(cpi.processName, cpi.applicationInfo,
                             cpi.name, cpi.flags)
-                            && isValidSingletonCall(r.uid, cpi.applicationInfo.uid)) {
+                            && isValidSingletonCall(r == null ? callingUid : r.uid,
+                                    cpi.applicationInfo.uid)) {
                         userId = UserHandle.USER_SYSTEM;
                         checkCrossUser = false;
                     } else {
@@ -6931,7 +6932,8 @@
                 conn = incProviderCountLocked(r, cpr, token, callingUid, callingPackage, callingTag,
                         stable);
                 if (conn != null && (conn.stableCount+conn.unstableCount) == 1) {
-                    if (cpr.proc != null && r.setAdj <= ProcessList.PERCEPTIBLE_LOW_APP_ADJ) {
+                    if (cpr.proc != null
+                            && r != null && r.setAdj <= ProcessList.PERCEPTIBLE_LOW_APP_ADJ) {
                         // If this is a perceptible app accessing the provider,
                         // make sure to count it as being accessed and thus
                         // back up on the LRU list.  This is good because
@@ -7003,7 +7005,8 @@
                 // Then allow connecting to the singleton provider
                 boolean singleton = isSingleton(cpi.processName, cpi.applicationInfo,
                         cpi.name, cpi.flags)
-                        && isValidSingletonCall(r.uid, cpi.applicationInfo.uid);
+                        && isValidSingletonCall(r == null ? callingUid : r.uid,
+                                cpi.applicationInfo.uid);
                 if (singleton) {
                     userId = UserHandle.USER_SYSTEM;
                 }
@@ -19507,7 +19510,7 @@
         }
 
         public AppOpsService getAppOpsService(File file, Handler handler) {
-            return new AppOpsService(file, handler);
+            return new AppOpsService(file, handler, getContext());
         }
 
         public Handler getUiHandler(ActivityManagerService service) {
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 313c185..d047a3c 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -442,7 +442,7 @@
      */
     @GuardedBy("mPhenotypeFlagLock")
     private void updateUseFreezer() {
-        if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+        if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
                     KEY_USE_FREEZER, DEFAULT_USE_FREEZER)) {
             mUseFreezer = isFreezerSupported();
         }
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index b107626..a651d9d 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -330,6 +330,7 @@
             // If this proc state is changed, need to update its uid record here
             if (uidRec.getCurProcState() != PROCESS_STATE_NONEXISTENT
                     && (uidRec.setProcState != uidRec.getCurProcState()
+                    || uidRec.setCapability != uidRec.curCapability
                     || uidRec.setWhitelist != uidRec.curWhitelist)) {
                 ActiveUids uids = mTmpUidRecords;
                 uids.clear();
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 71486d3..dcada89 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -3271,6 +3271,9 @@
     }
 
     final ProcessRecord getLRURecordForAppLocked(IApplicationThread thread) {
+        if (thread == null) {
+            return null;
+        }
         final IBinder threadBinder = thread.asBinder();
         // Find the application record.
         for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 784ce4a..06561f5 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -19,11 +19,15 @@
 import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_CAMERA;
 import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION;
 import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_MICROPHONE;
+import static android.app.AppOpsManager.CALL_BACK_ON_CHANGED_LISTENER_WITH_SWITCHED_OP_CHANGE;
 import static android.app.AppOpsManager.FILTER_BY_FEATURE_ID;
 import static android.app.AppOpsManager.FILTER_BY_OP_NAMES;
 import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME;
 import static android.app.AppOpsManager.FILTER_BY_UID;
 import static android.app.AppOpsManager.HistoricalOpsRequestFilter;
+import static android.app.AppOpsManager.KEY_BG_STATE_SETTLE_TIME;
+import static android.app.AppOpsManager.KEY_FG_SERVICE_STATE_SETTLE_TIME;
+import static android.app.AppOpsManager.KEY_TOP_STATE_SETTLE_TIME;
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.NoteOpEvent;
 import static android.app.AppOpsManager.OP_CAMERA;
@@ -41,6 +45,7 @@
 import static android.app.AppOpsManager.UID_STATE_MAX_LAST_NON_RESTRICTED;
 import static android.app.AppOpsManager.UID_STATE_PERSISTENT;
 import static android.app.AppOpsManager.UID_STATE_TOP;
+import static android.app.AppOpsManager.WATCH_FOREGROUND_CHANGES;
 import static android.app.AppOpsManager._NUM_OP;
 import static android.app.AppOpsManager.extractFlagsFromKey;
 import static android.app.AppOpsManager.extractUidStateFromKey;
@@ -53,6 +58,10 @@
 import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
 import static android.os.Process.STATSD_UID;
 
+import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS;
+
+import static java.lang.Long.max;
+
 import android.Manifest;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -70,6 +79,7 @@
 import android.app.AppOpsManagerInternal;
 import android.app.AppOpsManagerInternal.CheckOpsDelegate;
 import android.app.AsyncNotedAppOp;
+import android.compat.Compatibility;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -129,7 +139,6 @@
 import com.android.internal.app.IAppOpsService;
 import com.android.internal.os.Zygote;
 import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.Preconditions;
@@ -216,7 +225,7 @@
     //TODO: remove this when development is done.
     private static final int TEMP_PROCESS_CAPABILITY_FOREGROUND_LOCATION = 1 << 31;
 
-    Context mContext;
+    final Context mContext;
     final AtomicFile mFile;
     final Handler mHandler;
 
@@ -291,8 +300,11 @@
     @GuardedBy("this")
     private CheckOpsDelegate mCheckOpsDelegate;
 
-    @GuardedBy("this")
-    private SparseArray<List<Integer>> mSwitchOpToOps;
+    /**
+      * Reverse lookup for {@link AppOpsManager#opToSwitch(int)}. Initialized once and never
+      * changed
+      */
+    private final SparseArray<int[]> mSwitchedOps = new SparseArray<>();
 
     private ActivityManagerInternal mActivityManagerInternal;
 
@@ -343,30 +355,25 @@
      */
     @VisibleForTesting
     final class Constants extends ContentObserver {
-        // Key names stored in the settings value.
-        private static final String KEY_TOP_STATE_SETTLE_TIME = "top_state_settle_time";
-        private static final String KEY_FG_SERVICE_STATE_SETTLE_TIME
-                = "fg_service_state_settle_time";
-        private static final String KEY_BG_STATE_SETTLE_TIME = "bg_state_settle_time";
 
         /**
          * How long we want for a drop in uid state from top to settle before applying it.
          * @see Settings.Global#APP_OPS_CONSTANTS
-         * @see #KEY_TOP_STATE_SETTLE_TIME
+         * @see AppOpsManager#KEY_TOP_STATE_SETTLE_TIME
          */
         public long TOP_STATE_SETTLE_TIME;
 
         /**
          * How long we want for a drop in uid state from foreground to settle before applying it.
          * @see Settings.Global#APP_OPS_CONSTANTS
-         * @see #KEY_FG_SERVICE_STATE_SETTLE_TIME
+         * @see AppOpsManager#KEY_FG_SERVICE_STATE_SETTLE_TIME
          */
         public long FG_SERVICE_STATE_SETTLE_TIME;
 
         /**
          * How long we want for a drop in uid state from background to settle before applying it.
          * @see Settings.Global#APP_OPS_CONSTANTS
-         * @see #KEY_BG_STATE_SETTLE_TIME
+         * @see AppOpsManager#KEY_BG_STATE_SETTLE_TIME
          */
         public long BG_STATE_SETTLE_TIME;
 
@@ -1191,17 +1198,22 @@
     final AudioRestrictionManager mAudioRestrictionManager = new AudioRestrictionManager();
 
     final class ModeCallback implements DeathRecipient {
+        /** If mWatchedOpCode==ALL_OPS notify for ops affected by the switch-op */
+        public static final int ALL_OPS = -2;
+
         final IAppOpsCallback mCallback;
         final int mWatchingUid;
         final int mFlags;
+        final int mWatchedOpCode;
         final int mCallingUid;
         final int mCallingPid;
 
-        ModeCallback(IAppOpsCallback callback, int watchingUid, int flags, int callingUid,
-                int callingPid) {
+        ModeCallback(IAppOpsCallback callback, int watchingUid, int flags, int watchedOp,
+                int callingUid, int callingPid) {
             mCallback = callback;
             mWatchingUid = watchingUid;
             mFlags = flags;
+            mWatchedOpCode = watchedOp;
             mCallingUid = callingUid;
             mCallingPid = callingPid;
             try {
@@ -1224,6 +1236,10 @@
             UserHandle.formatUid(sb, mWatchingUid);
             sb.append(" flags=0x");
             sb.append(Integer.toHexString(mFlags));
+            if (mWatchedOpCode != OP_NONE) {
+                sb.append(" op=");
+                sb.append(opToName(mWatchedOpCode));
+            }
             sb.append(" from uid=");
             UserHandle.formatUid(sb, mCallingUid);
             sb.append(" pid=");
@@ -1337,16 +1353,23 @@
         featureOp.onClientDeath(clientId);
     }
 
-    public AppOpsService(File storagePath, Handler handler) {
+    public AppOpsService(File storagePath, Handler handler, Context context) {
+        mContext = context;
+
         LockGuard.installLock(this, LockGuard.INDEX_APP_OPS);
         mFile = new AtomicFile(storagePath, "appops");
         mHandler = handler;
         mConstants = new Constants(mHandler);
         readState();
+
+        for (int switchedCode = 0; switchedCode < _NUM_OP; switchedCode++) {
+            int switchCode = AppOpsManager.opToSwitch(switchedCode);
+            mSwitchedOps.put(switchCode,
+                    ArrayUtils.appendInt(mSwitchedOps.get(switchCode), switchedCode));
+        }
     }
 
-    public void publish(Context context) {
-        mContext = context;
+    public void publish() {
         ServiceManager.addService(Context.APP_OPS_SERVICE, asBinder());
         LocalServices.addService(AppOpsManagerInternal.class, mAppOpsManagerInternal);
     }
@@ -1616,6 +1639,19 @@
         }
     }
 
+    /**
+     * Update the pending state for the uid
+     *
+     * @param currentTime The current elapsed real time
+     * @param uid The uid that has a pending state
+     */
+    private void updatePendingState(long currentTime, int uid) {
+        synchronized (this) {
+            mLastRealtime = max(currentTime, mLastRealtime);
+            updatePendingStateIfNeededLocked(mUidStates.get(uid));
+        }
+    }
+
     public void updateUidProcState(int uid, int procState,
             @ActivityManager.ProcessCapability int capability) {
         synchronized (this) {
@@ -1647,7 +1683,12 @@
                     } else {
                         settleTime = mConstants.BG_STATE_SETTLE_TIME;
                     }
-                    uidState.pendingStateCommitTime = SystemClock.elapsedRealtime() + settleTime;
+                    final long commitTime = SystemClock.elapsedRealtime() + settleTime;
+                    uidState.pendingStateCommitTime = commitTime;
+
+                    mHandler.sendMessageDelayed(
+                            PooledLambda.obtainMessage(AppOpsService::updatePendingState, this,
+                                    commitTime + 1, uid), settleTime + 1);
                 }
 
                 if (uidState.pkgOps != null) {
@@ -2014,6 +2055,19 @@
             uidState.evalForegroundOps(mOpModeWatchers);
         }
 
+        notifyOpChangedForAllPkgsInUid(code, uid, false, callbackToIgnore);
+        notifyOpChangedSync(code, uid, null, mode);
+    }
+
+    /**
+     * Notify that an op changed for all packages in an uid.
+     *
+     * @param code The op that changed
+     * @param uid The uid the op was changed for
+     * @param onlyForeground Only notify watchers that watch for foreground changes
+     */
+    private void notifyOpChangedForAllPkgsInUid(int code, int uid, boolean onlyForeground,
+            @Nullable IAppOpsCallback callbackToIgnore) {
         String[] uidPackageNames = getPackagesForUid(uid);
         ArrayMap<ModeCallback, ArraySet<String>> callbackSpecs = null;
 
@@ -2023,6 +2077,10 @@
                 final int callbackCount = callbacks.size();
                 for (int i = 0; i < callbackCount; i++) {
                     ModeCallback callback = callbacks.valueAt(i);
+                    if (onlyForeground && (callback.mFlags & WATCH_FOREGROUND_CHANGES) == 0) {
+                        continue;
+                    }
+
                     ArraySet<String> changedPackages = new ArraySet<>();
                     Collections.addAll(changedPackages, uidPackageNames);
                     if (callbackSpecs == null) {
@@ -2041,6 +2099,10 @@
                     final int callbackCount = callbacks.size();
                     for (int i = 0; i < callbackCount; i++) {
                         ModeCallback callback = callbacks.valueAt(i);
+                        if (onlyForeground && (callback.mFlags & WATCH_FOREGROUND_CHANGES) == 0) {
+                            continue;
+                        }
+
                         ArraySet<String> changedPackages = callbackSpecs.get(callback);
                         if (changedPackages == null) {
                             changedPackages = new ArraySet<>();
@@ -2057,7 +2119,6 @@
         }
 
         if (callbackSpecs == null) {
-            notifyOpChangedSync(code, uid, null, mode);
             return;
         }
 
@@ -2079,23 +2140,24 @@
                 }
             }
         }
-
-        notifyOpChangedSync(code, uid, null, mode);
     }
 
     private void updatePermissionRevokedCompat(int uid, int switchCode, int mode) {
         PackageManager packageManager = mContext.getPackageManager();
+        if (packageManager == null) {
+            // This can only happen during early boot. At this time the permission state and appop
+            // state are in sync
+            return;
+        }
+
         String[] packageNames = packageManager.getPackagesForUid(uid);
         if (ArrayUtils.isEmpty(packageNames)) {
             return;
         }
         String packageName = packageNames[0];
 
-        List<Integer> ops = getSwitchOpToOps().get(switchCode);
-        int opsSize = CollectionUtils.size(ops);
-        for (int i = 0; i < opsSize; i++) {
-            int code = ops.get(i);
-
+        int[] ops = mSwitchedOps.get(switchCode);
+        for (int code : ops) {
             String permissionName = AppOpsManager.opToPermission(code);
             if (permissionName == null) {
                 continue;
@@ -2126,24 +2188,27 @@
             UserHandle user = UserHandle.getUserHandleForUid(uid);
             boolean isRevokedCompat;
             if (permissionInfo.backgroundPermission != null) {
-                boolean isBackgroundRevokedCompat = mode != AppOpsManager.MODE_ALLOWED;
+                if (packageManager.checkPermission(permissionInfo.backgroundPermission, packageName)
+                        == PackageManager.PERMISSION_GRANTED) {
+                    boolean isBackgroundRevokedCompat = mode != AppOpsManager.MODE_ALLOWED;
 
-                if (isBackgroundRevokedCompat && supportsRuntimePermissions) {
-                    Slog.w(TAG, "setUidMode() called with a mode inconsistent with runtime"
-                            + " permission state, this is discouraged and you should revoke the"
-                            + " runtime permission instead: uid=" + uid + ", switchCode="
-                            + switchCode + ", mode=" + mode + ", permission="
-                            + permissionInfo.backgroundPermission);
-                }
+                    if (isBackgroundRevokedCompat && supportsRuntimePermissions) {
+                        Slog.w(TAG, "setUidMode() called with a mode inconsistent with runtime"
+                                + " permission state, this is discouraged and you should revoke the"
+                                + " runtime permission instead: uid=" + uid + ", switchCode="
+                                + switchCode + ", mode=" + mode + ", permission="
+                                + permissionInfo.backgroundPermission);
+                    }
 
-                long identity = Binder.clearCallingIdentity();
-                try {
-                    packageManager.updatePermissionFlags(permissionInfo.backgroundPermission,
-                            packageName, PackageManager.FLAG_PERMISSION_REVOKED_COMPAT,
-                            isBackgroundRevokedCompat
-                                    ? PackageManager.FLAG_PERMISSION_REVOKED_COMPAT : 0, user);
-                } finally {
-                    Binder.restoreCallingIdentity(identity);
+                    long identity = Binder.clearCallingIdentity();
+                    try {
+                        packageManager.updatePermissionFlags(permissionInfo.backgroundPermission,
+                                packageName, PackageManager.FLAG_PERMISSION_REVOKED_COMPAT,
+                                isBackgroundRevokedCompat
+                                        ? PackageManager.FLAG_PERMISSION_REVOKED_COMPAT : 0, user);
+                    } finally {
+                        Binder.restoreCallingIdentity(identity);
+                    }
                 }
 
                 isRevokedCompat = mode != AppOpsManager.MODE_ALLOWED
@@ -2170,25 +2235,6 @@
         }
     }
 
-    @NonNull
-    private SparseArray<List<Integer>> getSwitchOpToOps() {
-        synchronized (this) {
-            if (mSwitchOpToOps == null) {
-                mSwitchOpToOps = new SparseArray<>();
-                for (int op = 0; op < _NUM_OP; op++) {
-                    int switchOp = AppOpsManager.opToSwitch(op);
-                    List<Integer> ops = mSwitchOpToOps.get(switchOp);
-                    if (ops == null) {
-                        ops = new ArrayList<>();
-                        mSwitchOpToOps.put(switchOp, ops);
-                    }
-                    ops.add(op);
-                }
-            }
-            return mSwitchOpToOps;
-        }
-    }
-
     private void notifyOpChangedSync(int code, int uid, @NonNull String packageName, int mode) {
         final StorageManagerInternal storageManagerInternal =
                 LocalServices.getService(StorageManagerInternal.class);
@@ -2289,16 +2335,29 @@
         if (uid != UID_ANY && callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
             return;
         }
-        // There are features watching for mode changes such as window manager
-        // and location manager which are in our process. The callbacks in these
-        // features may require permissions our remote caller does not have.
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            callback.mCallback.opChanged(code, uid, packageName);
-        } catch (RemoteException e) {
-            /* ignore */
-        } finally {
-            Binder.restoreCallingIdentity(identity);
+
+        // See CALL_BACK_ON_CHANGED_LISTENER_WITH_SWITCHED_OP_CHANGE
+        int[] switchedCodes;
+        if (callback.mWatchedOpCode == ALL_OPS) {
+            switchedCodes = mSwitchedOps.get(code);
+        } else if (callback.mWatchedOpCode == OP_NONE) {
+            switchedCodes = new int[]{code};
+        } else {
+            switchedCodes = new int[]{callback.mWatchedOpCode};
+        }
+
+        for (int switchedCode : switchedCodes) {
+            // There are features watching for mode changes such as window manager
+            // and location manager which are in our process. The callbacks in these
+            // features may require permissions our remote caller does not have.
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                callback.mCallback.opChanged(switchedCode, uid, packageName);
+            } catch (RemoteException e) {
+                /* ignore */
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
         }
     }
 
@@ -2493,17 +2552,32 @@
             return;
         }
         synchronized (this) {
-            op = (op != AppOpsManager.OP_NONE) ? AppOpsManager.opToSwitch(op) : op;
+            int switchOp = (op != AppOpsManager.OP_NONE) ? AppOpsManager.opToSwitch(op) : op;
+
+            // See CALL_BACK_ON_CHANGED_LISTENER_WITH_SWITCHED_OP_CHANGE
+            int notifiedOps;
+            if (Compatibility.isChangeEnabled(
+                    CALL_BACK_ON_CHANGED_LISTENER_WITH_SWITCHED_OP_CHANGE)) {
+                if (op == OP_NONE) {
+                    notifiedOps = ALL_OPS;
+                } else {
+                    notifiedOps = op;
+                }
+            } else {
+                notifiedOps = switchOp;
+            }
+
             ModeCallback cb = mModeWatchers.get(callback.asBinder());
             if (cb == null) {
-                cb = new ModeCallback(callback, watchedUid, flags, callingUid, callingPid);
+                cb = new ModeCallback(callback, watchedUid, flags, notifiedOps, callingUid,
+                        callingPid);
                 mModeWatchers.put(callback.asBinder(), cb);
             }
-            if (op != AppOpsManager.OP_NONE) {
-                ArraySet<ModeCallback> cbs = mOpModeWatchers.get(op);
+            if (switchOp != AppOpsManager.OP_NONE) {
+                ArraySet<ModeCallback> cbs = mOpModeWatchers.get(switchOp);
                 if (cbs == null) {
                     cbs = new ArraySet<>();
-                    mOpModeWatchers.put(op, cbs);
+                    mOpModeWatchers.put(switchOp, cbs);
                 }
                 cbs.add(cb);
             }
@@ -3322,6 +3396,18 @@
             uidState = new UidState(uid);
             mUidStates.put(uid, uidState);
         } else {
+            updatePendingStateIfNeededLocked(uidState);
+        }
+        return uidState;
+    }
+
+    /**
+     * Check if the pending state should be updated and do so if needed
+     *
+     * @param uidState The uidState that might have a pending state
+     */
+    private void updatePendingStateIfNeededLocked(@NonNull UidState uidState) {
+        if (uidState != null) {
             if (uidState.pendingStateCommitTime != 0) {
                 if (uidState.pendingStateCommitTime < mLastRealtime) {
                     commitUidPendingStateLocked(uidState);
@@ -3333,7 +3419,6 @@
                 }
             }
         }
-        return uidState;
     }
 
     private void commitUidPendingStateLocked(UidState uidState) {
@@ -3353,24 +3438,28 @@
                         && uidState.appWidgetVisible == uidState.pendingAppWidgetVisible) {
                     continue;
                 }
-                final ArraySet<ModeCallback> callbacks = mOpModeWatchers.get(code);
-                if (callbacks != null) {
-                    for (int cbi = callbacks.size() - 1; cbi >= 0; cbi--) {
-                        final ModeCallback callback = callbacks.valueAt(cbi);
-                        if ((callback.mFlags & AppOpsManager.WATCH_FOREGROUND_CHANGES) == 0
-                                || !callback.isWatchingUid(uidState.uid)) {
-                            continue;
-                        }
-                        boolean doAllPackages = uidState.opModes != null
-                                && uidState.opModes.indexOfKey(code) >= 0
-                                && uidState.opModes.get(code) == AppOpsManager.MODE_FOREGROUND;
-                        if (uidState.pkgOps != null) {
+
+                if (uidState.opModes != null
+                        && uidState.opModes.indexOfKey(code) >= 0
+                        && uidState.opModes.get(code) == AppOpsManager.MODE_FOREGROUND) {
+                    mHandler.sendMessage(PooledLambda.obtainMessage(
+                            AppOpsService::notifyOpChangedForAllPkgsInUid,
+                            this, code, uidState.uid, true, null));
+                } else {
+                    final ArraySet<ModeCallback> callbacks = mOpModeWatchers.get(code);
+                    if (callbacks != null) {
+                        for (int cbi = callbacks.size() - 1; cbi >= 0; cbi--) {
+                            final ModeCallback callback = callbacks.valueAt(cbi);
+                            if ((callback.mFlags & AppOpsManager.WATCH_FOREGROUND_CHANGES) == 0
+                                    || !callback.isWatchingUid(uidState.uid)) {
+                                continue;
+                            }
                             for (int pkgi = uidState.pkgOps.size() - 1; pkgi >= 0; pkgi--) {
                                 final Op op = uidState.pkgOps.valueAt(pkgi).get(code);
                                 if (op == null) {
                                     continue;
                                 }
-                                if (doAllPackages || op.mode == AppOpsManager.MODE_FOREGROUND) {
+                                if (op.mode == AppOpsManager.MODE_FOREGROUND) {
                                     mHandler.sendMessage(PooledLambda.obtainMessage(
                                             AppOpsService::notifyOpChanged,
                                             this, callback, code, uidState.uid,
@@ -3795,11 +3884,7 @@
             if (tagName.equals("op")) {
                 final int code = Integer.parseInt(parser.getAttributeValue(null, "n"));
                 final int mode = Integer.parseInt(parser.getAttributeValue(null, "m"));
-                UidState uidState = getUidStateLocked(uid, true);
-                if (uidState.opModes == null) {
-                    uidState.opModes = new SparseIntArray();
-                }
-                uidState.opModes.put(code, mode);
+                setUidMode(code, uid, mode);
             } else {
                 Slog.w(TAG, "Unknown element under <uid-ops>: "
                         + parser.getName());
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index b2548af..82a2f01 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -3406,13 +3406,11 @@
         }
 
         public void binderDied() {
-            int oldModeOwnerPid = 0;
+            int oldModeOwnerPid;
             int newModeOwnerPid = 0;
             synchronized (mDeviceBroker.mSetModeLock) {
                 Log.w(TAG, "setMode() client died");
-                if (!mSetModeDeathHandlers.isEmpty()) {
-                    oldModeOwnerPid = mSetModeDeathHandlers.get(0).getPid();
-                }
+                oldModeOwnerPid = getModeOwnerPid();
                 int index = mSetModeDeathHandlers.indexOf(this);
                 if (index < 0) {
                     Log.w(TAG, "unregistered setMode() client died");
@@ -3446,17 +3444,19 @@
 
     /** @see AudioManager#setMode(int) */
     public void setMode(int mode, IBinder cb, String callingPackage) {
-        if (DEBUG_MODE) { Log.v(TAG, "setMode(mode=" + mode + ", callingPackage=" + callingPackage + ")"); }
+        if (DEBUG_MODE) {
+            Log.v(TAG, "setMode(mode=" + mode + ", callingPackage=" + callingPackage + ")");
+        }
         if (!checkAudioSettingsPermission("setMode()")) {
             return;
         }
-
-        if ( (mode == AudioSystem.MODE_IN_CALL) &&
-                (mContext.checkCallingOrSelfPermission(
+        final boolean hasModifyPhoneStatePermission = mContext.checkCallingOrSelfPermission(
                         android.Manifest.permission.MODIFY_PHONE_STATE)
-                            != PackageManager.PERMISSION_GRANTED)) {
+                        == PackageManager.PERMISSION_GRANTED;
+        final int callingPid = Binder.getCallingPid();
+        if ((mode == AudioSystem.MODE_IN_CALL) && !hasModifyPhoneStatePermission) {
             Log.w(TAG, "MODIFY_PHONE_STATE Permission Denial: setMode(MODE_IN_CALL) from pid="
-                    + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
+                    + callingPid + ", uid=" + Binder.getCallingUid());
             return;
         }
 
@@ -3470,16 +3470,25 @@
             return;
         }
 
-        int oldModeOwnerPid = 0;
-        int newModeOwnerPid = 0;
+        int oldModeOwnerPid;
+        int newModeOwnerPid;
         synchronized (mDeviceBroker.mSetModeLock) {
-            if (!mSetModeDeathHandlers.isEmpty()) {
-                oldModeOwnerPid = mSetModeDeathHandlers.get(0).getPid();
-            }
             if (mode == AudioSystem.MODE_CURRENT) {
                 mode = mMode;
             }
-            newModeOwnerPid = setModeInt(mode, cb, Binder.getCallingPid(), callingPackage);
+            oldModeOwnerPid = getModeOwnerPid();
+            // Do not allow changing mode if a call is active and the requester
+            // does not have permission to modify phone state or is not the mode owner.
+            if (((mMode == AudioSystem.MODE_IN_CALL)
+                    || (mMode == AudioSystem.MODE_IN_COMMUNICATION))
+                    && !(hasModifyPhoneStatePermission || (oldModeOwnerPid == callingPid))) {
+                Log.w(TAG, "setMode(" + mode + ") from pid=" + callingPid
+                        + ", uid=" + Binder.getCallingUid()
+                        + ", cannot change mode from " + mMode
+                        + " without permission or being mode owner");
+                return;
+            }
+            newModeOwnerPid = setModeInt(mode, cb, callingPid, callingPackage);
         }
         // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
         // SCO connections not started by the application changing the mode when pid changes
@@ -3569,10 +3578,9 @@
 
         if (status == AudioSystem.AUDIO_STATUS_OK) {
             if (actualMode != AudioSystem.MODE_NORMAL) {
-                if (mSetModeDeathHandlers.isEmpty()) {
+                newModeOwnerPid = getModeOwnerPid();
+                if (newModeOwnerPid == 0) {
                     Log.e(TAG, "setMode() different from MODE_NORMAL with empty mode client stack");
-                } else {
-                    newModeOwnerPid = mSetModeDeathHandlers.get(0).getPid();
                 }
             }
             // Note: newModeOwnerPid is always 0 when actualMode is MODE_NORMAL
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index 0d88388..0f54984 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -99,6 +99,33 @@
         public String[] getConfiguration(Context context) {
             return context.getResources().getStringArray(R.array.config_biometric_sensors);
         }
+
+        /**
+         * Allows us to mock FingerprintService for testing
+         */
+        @VisibleForTesting
+        public IFingerprintService getFingerprintService() {
+            return IFingerprintService.Stub.asInterface(
+                    ServiceManager.getService(Context.FINGERPRINT_SERVICE));
+        }
+
+        /**
+         * Allows us to mock FaceService for testing
+         */
+        @VisibleForTesting
+        public IFaceService getFaceService() {
+            return IFaceService.Stub.asInterface(
+                    ServiceManager.getService(Context.FACE_SERVICE));
+        }
+
+        /**
+         * Allows us to mock IrisService for testing
+         */
+        @VisibleForTesting
+        public IIrisService getIrisService() {
+            return IIrisService.Stub.asInterface(
+                    ServiceManager.getService(Context.IRIS_SERVICE));
+        }
     }
 
     private final class AuthServiceImpl extends IAuthService.Stub {
@@ -178,7 +205,6 @@
 
         mInjector = injector;
         mImpl = new AuthServiceImpl();
-        final PackageManager pm = context.getPackageManager();
     }
 
     private void registerAuthenticator(SensorConfig config) throws RemoteException {
@@ -191,18 +217,36 @@
 
         switch (config.mModality) {
             case TYPE_FINGERPRINT:
-                authenticator = new FingerprintAuthenticator(IFingerprintService.Stub.asInterface(
-                        ServiceManager.getService(Context.FINGERPRINT_SERVICE)));
+                final IFingerprintService fingerprintService = mInjector.getFingerprintService();
+                if (fingerprintService == null) {
+                    Slog.e(TAG, "Attempting to register with null FingerprintService. Please check"
+                            + " your device configuration.");
+                    return;
+                }
+
+                authenticator = new FingerprintAuthenticator(fingerprintService);
                 break;
 
             case TYPE_FACE:
-                authenticator = new FaceAuthenticator(IFaceService.Stub.asInterface(
-                        ServiceManager.getService(Context.FACE_SERVICE)));
+                final IFaceService faceService = mInjector.getFaceService();
+                if (faceService == null) {
+                    Slog.e(TAG, "Attempting to register with null FaceService. Please check your"
+                            + " device configuration.");
+                    return;
+                }
+
+                authenticator = new FaceAuthenticator(faceService);
                 break;
 
             case TYPE_IRIS:
-                authenticator = new IrisAuthenticator(IIrisService.Stub.asInterface(
-                        ServiceManager.getService(Context.IRIS_SERVICE)));
+                final IIrisService irisService = mInjector.getIrisService();
+                if (irisService == null) {
+                    Slog.e(TAG, "Attempting to register with null IrisService. Please check your"
+                            + " device configuration.");
+                    return;
+                }
+
+                authenticator = new IrisAuthenticator(irisService);
                 break;
 
             default:
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/AuthenticationClient.java
index 766e5c4..7bbda9f 100644
--- a/services/core/java/com/android/server/biometrics/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/AuthenticationClient.java
@@ -20,11 +20,14 @@
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.IBiometricNativeHandle;
 import android.os.IBinder;
+import android.os.NativeHandle;
 import android.os.RemoteException;
 import android.security.KeyStore;
 import android.util.Slog;
 
+import java.io.IOException;
 import java.util.ArrayList;
 
 /**
@@ -41,6 +44,7 @@
     public static final int LOCKOUT_PERMANENT = 2;
 
     private final boolean mRequireConfirmation;
+    private final NativeHandle mWindowId;
 
     // We need to track this state since it's possible for applications to request for
     // authentication while the device is already locked out. In that case, the client is created
@@ -69,11 +73,25 @@
     public AuthenticationClient(Context context, Constants constants,
             BiometricServiceBase.DaemonWrapper daemon, long halDeviceId, IBinder token,
             BiometricServiceBase.ServiceListener listener, int targetUserId, int groupId, long opId,
-            boolean restricted, String owner, int cookie, boolean requireConfirmation) {
+            boolean restricted, String owner, int cookie, boolean requireConfirmation,
+            IBiometricNativeHandle windowId) {
         super(context, constants, daemon, halDeviceId, token, listener, targetUserId, groupId,
                 restricted, owner, cookie);
         mOpId = opId;
         mRequireConfirmation = requireConfirmation;
+        mWindowId = Utils.dupNativeHandle(windowId);
+    }
+
+    @Override
+    public void destroy() {
+        if (mWindowId != null && mWindowId.getFileDescriptors() != null) {
+            try {
+                mWindowId.close();
+            } catch (IOException e) {
+                Slog.e(getLogTag(), "Failed to close windowId NativeHandle: ", e);
+            }
+        }
+        super.destroy();
     }
 
     protected long getStartTimeMs() {
@@ -233,7 +251,7 @@
         onStart();
         try {
             mStartTimeMs = System.currentTimeMillis();
-            final int result = getDaemonWrapper().authenticate(mOpId, getGroupId());
+            final int result = getDaemonWrapper().authenticate(mOpId, getGroupId(), mWindowId);
             if (result != 0) {
                 Slog.w(getLogTag(), "startAuthentication failed, result=" + result);
                 mMetricsLogger.histogram(mConstants.tagAuthStartError(), result);
diff --git a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
index 687d935..0e70994 100644
--- a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
+++ b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
@@ -32,6 +32,7 @@
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.IBiometricNativeHandle;
 import android.hardware.biometrics.IBiometricService;
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
 import android.hardware.biometrics.IBiometricServiceReceiverInternal;
@@ -43,6 +44,7 @@
 import android.os.IBinder;
 import android.os.IHwBinder;
 import android.os.IRemoteCallback;
+import android.os.NativeHandle;
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
@@ -220,9 +222,10 @@
 
         public AuthenticationClientImpl(Context context, DaemonWrapper daemon, long halDeviceId,
                 IBinder token, ServiceListener listener, int targetUserId, int groupId, long opId,
-                boolean restricted, String owner, int cookie, boolean requireConfirmation) {
+                boolean restricted, String owner, int cookie, boolean requireConfirmation,
+                IBiometricNativeHandle windowId) {
             super(context, getConstants(), daemon, halDeviceId, token, listener, targetUserId,
-                    groupId, opId, restricted, owner, cookie, requireConfirmation);
+                    groupId, opId, restricted, owner, cookie, requireConfirmation, windowId);
         }
 
         @Override
@@ -283,10 +286,10 @@
         public EnrollClientImpl(Context context, DaemonWrapper daemon, long halDeviceId,
                 IBinder token, ServiceListener listener, int userId, int groupId,
                 byte[] cryptoToken, boolean restricted, String owner,
-                final int[] disabledFeatures, int timeoutSec) {
+                final int[] disabledFeatures, int timeoutSec, IBiometricNativeHandle windowId) {
             super(context, getConstants(), daemon, halDeviceId, token, listener,
                     userId, groupId, cryptoToken, restricted, owner, getBiometricUtils(),
-                    disabledFeatures, timeoutSec);
+                    disabledFeatures, timeoutSec, windowId);
         }
 
         @Override
@@ -472,12 +475,13 @@
      */
     protected interface DaemonWrapper {
         int ERROR_ESRCH = 3; // Likely HAL is dead. see errno.h.
-        int authenticate(long operationId, int groupId) throws RemoteException;
+        int authenticate(long operationId, int groupId, NativeHandle windowId)
+                throws RemoteException;
         int cancel() throws RemoteException;
         int remove(int groupId, int biometricId) throws RemoteException;
         int enumerate() throws RemoteException;
         int enroll(byte[] token, int groupId, int timeout,
-                ArrayList<Integer> disabledFeatures) throws RemoteException;
+                ArrayList<Integer> disabledFeatures, NativeHandle windowId) throws RemoteException;
         void resetLockout(byte[] token) throws RemoteException;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/EnrollClient.java b/services/core/java/com/android/server/biometrics/EnrollClient.java
index 7ebb7c0..684795e 100644
--- a/services/core/java/com/android/server/biometrics/EnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/EnrollClient.java
@@ -20,10 +20,13 @@
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.IBiometricNativeHandle;
 import android.os.IBinder;
+import android.os.NativeHandle;
 import android.os.RemoteException;
 import android.util.Slog;
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 
@@ -35,6 +38,7 @@
     private final BiometricUtils mBiometricUtils;
     private final int[] mDisabledFeatures;
     private final int mTimeoutSec;
+    private final NativeHandle mWindowId;
 
     private long mEnrollmentStartTimeMs;
 
@@ -44,13 +48,26 @@
             BiometricServiceBase.DaemonWrapper daemon, long halDeviceId, IBinder token,
             BiometricServiceBase.ServiceListener listener, int userId, int groupId,
             byte[] cryptoToken, boolean restricted, String owner, BiometricUtils utils,
-            final int[] disabledFeatures, int timeoutSec) {
+            final int[] disabledFeatures, int timeoutSec, IBiometricNativeHandle windowId) {
         super(context, constants, daemon, halDeviceId, token, listener, userId, groupId, restricted,
                 owner, 0 /* cookie */);
         mBiometricUtils = utils;
         mCryptoToken = Arrays.copyOf(cryptoToken, cryptoToken.length);
         mDisabledFeatures = Arrays.copyOf(disabledFeatures, disabledFeatures.length);
         mTimeoutSec = timeoutSec;
+        mWindowId = Utils.dupNativeHandle(windowId);
+    }
+
+    @Override
+    public void destroy() {
+        if (mWindowId != null && mWindowId.getFileDescriptors() != null) {
+            try {
+                mWindowId.close();
+            } catch (IOException e) {
+                Slog.e(getLogTag(), "Failed to close windowId NativeHandle: ", e);
+            }
+        }
+        super.destroy();
     }
 
     @Override
@@ -102,7 +119,7 @@
             }
 
             final int result = getDaemonWrapper().enroll(mCryptoToken, getGroupId(), mTimeoutSec,
-                    disabledFeatures);
+                    disabledFeatures, mWindowId);
             if (result != 0) {
                 Slog.w(getLogTag(), "startEnroll failed, result=" + result);
                 mMetricsLogger.histogram(mConstants.tagEnrollStartError(), result);
diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java
index 389763b..2d4ab63 100644
--- a/services/core/java/com/android/server/biometrics/Utils.java
+++ b/services/core/java/com/android/server/biometrics/Utils.java
@@ -23,12 +23,17 @@
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.BiometricPrompt;
 import android.hardware.biometrics.BiometricPrompt.AuthenticationResultType;
+import android.hardware.biometrics.IBiometricNativeHandle;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.NativeHandle;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.Slog;
 
+import java.io.FileDescriptor;
+import java.io.IOException;
+
 public class Utils {
     public static boolean isDebugEnabled(Context context, int targetUserId) {
         if (targetUserId == UserHandle.USER_NULL) {
@@ -237,4 +242,31 @@
                 throw new IllegalArgumentException("Unsupported dismissal reason: " + reason);
         }
     }
+
+    /**
+     * Converts an {@link IBiometricNativeHandle} to a {@link NativeHandle} by duplicating the
+     * the underlying file descriptors.
+     *
+     * Both the original and new handle must be closed after use.
+     *
+     * @param h {@link IBiometricNativeHandle} received as a binder call argument. Usually used to
+     *          identify a WindowManager window. Can be null.
+     * @return A {@link NativeHandle} representation of {@code h}. Will be null if either {@code h}
+     *          or its contents are null.
+     */
+    public static NativeHandle dupNativeHandle(IBiometricNativeHandle h) {
+        NativeHandle handle = null;
+        if (h != null && h.fds != null && h.ints != null) {
+            FileDescriptor[] fds = new FileDescriptor[h.fds.length];
+            for (int i = 0; i < h.fds.length; ++i) {
+                try {
+                    fds[i] = h.fds[i].dup().getFileDescriptor();
+                } catch (IOException e) {
+                    return null;
+                }
+            }
+            handle = new NativeHandle(fds, h.ints, true /* own */);
+        }
+        return handle;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java
index b512475..31c3d4d 100644
--- a/services/core/java/com/android/server/biometrics/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/face/FaceService.java
@@ -34,6 +34,7 @@
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.IBiometricNativeHandle;
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
 import android.hardware.biometrics.IBiometricServiceReceiverInternal;
 import android.hardware.biometrics.face.V1_0.IBiometricsFace;
@@ -214,9 +215,10 @@
         public FaceAuthClient(Context context,
                 DaemonWrapper daemon, long halDeviceId, IBinder token,
                 ServiceListener listener, int targetUserId, int groupId, long opId,
-                boolean restricted, String owner, int cookie, boolean requireConfirmation) {
+                boolean restricted, String owner, int cookie, boolean requireConfirmation,
+                IBiometricNativeHandle windowId) {
             super(context, daemon, halDeviceId, token, listener, targetUserId, groupId, opId,
-                    restricted, owner, cookie, requireConfirmation);
+                    restricted, owner, cookie, requireConfirmation, windowId);
         }
 
         @Override
@@ -373,7 +375,7 @@
         @Override // Binder call
         public void enroll(int userId, final IBinder token, final byte[] cryptoToken,
                 final IFaceServiceReceiver receiver, final String opPackageName,
-                final int[] disabledFeatures) {
+                final int[] disabledFeatures, IBiometricNativeHandle windowId) {
             checkPermission(MANAGE_BIOMETRIC);
             updateActiveGroup(userId, opPackageName);
 
@@ -384,7 +386,7 @@
             final EnrollClientImpl client = new EnrollClientImpl(getContext(), mDaemonWrapper,
                     mHalDeviceId, token, new ServiceListenerImpl(receiver), mCurrentUserId,
                     0 /* groupId */, cryptoToken, restricted, opPackageName, disabledFeatures,
-                    ENROLL_TIMEOUT_SEC) {
+                    ENROLL_TIMEOUT_SEC, windowId) {
 
                 @Override
                 public int[] getAcquireIgnorelist() {
@@ -411,6 +413,14 @@
         }
 
         @Override // Binder call
+        public void enrollRemotely(int userId, final IBinder token, final byte[] cryptoToken,
+                final IFaceServiceReceiver receiver, final String opPackageName,
+                final int[] disabledFeatures) {
+            checkPermission(MANAGE_BIOMETRIC);
+            // TODO(b/145027036): Implement this.
+        }
+
+        @Override // Binder call
         public void cancelEnrollment(final IBinder token) {
             checkPermission(MANAGE_BIOMETRIC);
             cancelEnrollmentInternal(token);
@@ -426,7 +436,7 @@
             final AuthenticationClientImpl client = new FaceAuthClient(getContext(),
                     mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver),
                     mCurrentUserId, 0 /* groupId */, opId, restricted, opPackageName,
-                    0 /* cookie */, false /* requireConfirmation */);
+                    0 /* cookie */, false /* requireConfirmation */, null /* windowId */);
             authenticateInternal(client, opId, opPackageName);
         }
 
@@ -442,7 +452,7 @@
                     mDaemonWrapper, mHalDeviceId, token,
                     new BiometricPromptServiceListenerImpl(wrapperReceiver),
                     mCurrentUserId, 0 /* groupId */, opId, restricted, opPackageName, cookie,
-                    requireConfirmation);
+                    requireConfirmation, null /* windowId */);
             authenticateInternal(client, opId, opPackageName, callingUid, callingPid,
                     callingUserId);
         }
@@ -985,7 +995,8 @@
      */
     private final DaemonWrapper mDaemonWrapper = new DaemonWrapper() {
         @Override
-        public int authenticate(long operationId, int groupId) throws RemoteException {
+        public int authenticate(long operationId, int groupId, NativeHandle windowId)
+                throws RemoteException {
             IBiometricsFace daemon = getFaceDaemon();
             if (daemon == null) {
                 Slog.w(TAG, "authenticate(): no face HAL!");
@@ -1026,7 +1037,7 @@
 
         @Override
         public int enroll(byte[] cryptoToken, int groupId, int timeout,
-                ArrayList<Integer> disabledFeatures) throws RemoteException {
+                ArrayList<Integer> disabledFeatures, NativeHandle windowId) throws RemoteException {
             IBiometricsFace daemon = getFaceDaemon();
             if (daemon == null) {
                 Slog.w(TAG, "enroll(): no face HAL!");
@@ -1036,7 +1047,17 @@
             for (int i = 0; i < cryptoToken.length; i++) {
                 token.add(cryptoToken[i]);
             }
-            return daemon.enroll(token, timeout, disabledFeatures);
+            android.hardware.biometrics.face.V1_1.IBiometricsFace daemon11 =
+                    android.hardware.biometrics.face.V1_1.IBiometricsFace.castFrom(
+                            daemon);
+            if (daemon11 != null) {
+                return daemon11.enroll_1_1(token, timeout, disabledFeatures, windowId);
+            } else if (windowId == null) {
+                return daemon.enroll(token, timeout, disabledFeatures);
+            } else {
+                Slog.e(TAG, "enroll(): windowId is only supported in @1.1 HAL");
+                return ERROR_ESRCH;
+            }
         }
 
         @Override
diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintAuthenticator.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintAuthenticator.java
index 6150de1..7a4e62e 100644
--- a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintAuthenticator.java
@@ -38,7 +38,7 @@
             String opPackageName, int cookie, int callingUid, int callingPid, int callingUserId)
             throws RemoteException {
         mFingerprintService.prepareForAuthentication(token, sessionId, userId, wrapperReceiver,
-                opPackageName, cookie, callingUid, callingPid, callingUserId);
+                opPackageName, cookie, callingUid, callingPid, callingUserId, null /* windowId */);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
index 44797ad..57d1867 100644
--- a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
@@ -37,6 +37,7 @@
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.IBiometricNativeHandle;
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
 import android.hardware.biometrics.IBiometricServiceReceiverInternal;
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
@@ -50,6 +51,7 @@
 import android.os.Build;
 import android.os.Environment;
 import android.os.IBinder;
+import android.os.NativeHandle;
 import android.os.RemoteException;
 import android.os.SELinux;
 import android.os.SystemClock;
@@ -132,9 +134,9 @@
                 DaemonWrapper daemon, long halDeviceId, IBinder token,
                 ServiceListener listener, int targetUserId, int groupId, long opId,
                 boolean restricted, String owner, int cookie,
-                boolean requireConfirmation) {
+                boolean requireConfirmation, IBiometricNativeHandle windowId) {
             super(context, daemon, halDeviceId, token, listener, targetUserId, groupId, opId,
-                    restricted, owner, cookie, requireConfirmation);
+                    restricted, owner, cookie, requireConfirmation, windowId);
         }
 
         @Override
@@ -198,7 +200,7 @@
         @Override // Binder call
         public void enroll(final IBinder token, final byte[] cryptoToken, final int userId,
                 final IFingerprintServiceReceiver receiver, final int flags,
-                final String opPackageName) {
+                final String opPackageName, IBiometricNativeHandle windowId) {
             checkPermission(MANAGE_FINGERPRINT);
 
             final boolean restricted = isRestricted();
@@ -206,7 +208,7 @@
             final EnrollClientImpl client = new EnrollClientImpl(getContext(), mDaemonWrapper,
                     mHalDeviceId, token, new ServiceListenerImpl(receiver), mCurrentUserId, groupId,
                     cryptoToken, restricted, opPackageName, new int[0] /* disabledFeatures */,
-                    ENROLL_TIMEOUT_SEC) {
+                    ENROLL_TIMEOUT_SEC, windowId) {
                 @Override
                 public boolean shouldVibrate() {
                     return true;
@@ -230,20 +232,22 @@
         @Override // Binder call
         public void authenticate(final IBinder token, final long opId, final int groupId,
                 final IFingerprintServiceReceiver receiver, final int flags,
-                final String opPackageName) {
+                final String opPackageName, IBiometricNativeHandle windowId) {
             updateActiveGroup(groupId, opPackageName);
             final boolean restricted = isRestricted();
             final AuthenticationClientImpl client = new FingerprintAuthClient(getContext(),
                     mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver),
                     mCurrentUserId, groupId, opId, restricted, opPackageName,
-                    0 /* cookie */, false /* requireConfirmation */);
+                    0 /* cookie */, false /* requireConfirmation */,
+                    windowId);
             authenticateInternal(client, opId, opPackageName);
         }
 
         @Override // Binder call
         public void prepareForAuthentication(IBinder token, long opId, int groupId,
                 IBiometricServiceReceiverInternal wrapperReceiver, String opPackageName,
-                int cookie, int callingUid, int callingPid, int callingUserId) {
+                int cookie, int callingUid, int callingPid, int callingUserId,
+                IBiometricNativeHandle windowId) {
             checkPermission(MANAGE_BIOMETRIC);
             updateActiveGroup(groupId, opPackageName);
             final boolean restricted = true; // BiometricPrompt is always restricted
@@ -251,7 +255,8 @@
                     mDaemonWrapper, mHalDeviceId, token,
                     new BiometricPromptServiceListenerImpl(wrapperReceiver),
                     mCurrentUserId, groupId, opId, restricted, opPackageName, cookie,
-                    false /* requireConfirmation */);
+                    false /* requireConfirmation */,
+                    windowId);
             authenticateInternal(client, opId, opPackageName, callingUid, callingPid,
                     callingUserId);
         }
@@ -654,13 +659,24 @@
      */
     private final DaemonWrapper mDaemonWrapper = new DaemonWrapper() {
         @Override
-        public int authenticate(long operationId, int groupId) throws RemoteException {
+        public int authenticate(long operationId, int groupId, NativeHandle windowId)
+                throws RemoteException {
             IBiometricsFingerprint daemon = getFingerprintDaemon();
             if (daemon == null) {
                 Slog.w(TAG, "authenticate(): no fingerprint HAL!");
                 return ERROR_ESRCH;
             }
-            return daemon.authenticate(operationId, groupId);
+            android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprint daemon22 =
+                    android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprint.castFrom(
+                            daemon);
+            if (daemon22 != null) {
+                return daemon22.authenticate_2_2(operationId, groupId, windowId);
+            } else if (windowId == null) {
+                return daemon.authenticate(operationId, groupId);
+            } else {
+                Slog.e(TAG, "authenticate(): windowId is only supported in @2.2 HAL");
+                return ERROR_ESRCH;
+            }
         }
 
         @Override
@@ -695,13 +711,27 @@
 
         @Override
         public int enroll(byte[] cryptoToken, int groupId, int timeout,
-                ArrayList<Integer> disabledFeatures) throws RemoteException {
+                ArrayList<Integer> disabledFeatures, NativeHandle windowId) throws RemoteException {
             IBiometricsFingerprint daemon = getFingerprintDaemon();
             if (daemon == null) {
                 Slog.w(TAG, "enroll(): no fingerprint HAL!");
                 return ERROR_ESRCH;
             }
-            return daemon.enroll(cryptoToken, groupId, timeout);
+            android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprint daemon22 =
+                    android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprint.castFrom(
+                            daemon);
+            if (daemon22 != null) {
+                ArrayList<Byte> cryptoTokenAsList = new ArrayList<>(cryptoToken.length);
+                for (byte b : cryptoToken) {
+                    cryptoTokenAsList.add(b);
+                }
+                return daemon22.enroll_2_2(cryptoTokenAsList, groupId, timeout, windowId);
+            } else if (windowId == null) {
+                return daemon.enroll(cryptoToken, groupId, timeout);
+            } else {
+                Slog.e(TAG, "enroll(): windowId is only supported in @2.2 HAL");
+                return ERROR_ESRCH;
+            }
         }
 
         @Override
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index 2fc9d04..af47430 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -16,6 +16,12 @@
 
 package com.android.server.compat;
 
+import static android.Manifest.permission.LOG_COMPAT_CHANGE;
+import static android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG;
+import static android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.IActivityManager;
 import android.content.Context;
@@ -67,12 +73,14 @@
 
     @Override
     public void reportChange(long changeId, ApplicationInfo appInfo) {
+        checkCompatChangeLogPermission();
         reportChange(changeId, appInfo.uid,
                 ChangeReporter.STATE_LOGGED);
     }
 
     @Override
     public void reportChangeByPackageName(long changeId, String packageName, int userId) {
+        checkCompatChangeLogPermission();
         ApplicationInfo appInfo = getApplicationInfo(packageName, userId);
         if (appInfo == null) {
             return;
@@ -82,11 +90,13 @@
 
     @Override
     public void reportChangeByUid(long changeId, int uid) {
+        checkCompatChangeLogPermission();
         reportChange(changeId, uid, ChangeReporter.STATE_LOGGED);
     }
 
     @Override
     public boolean isChangeEnabled(long changeId, ApplicationInfo appInfo) {
+        checkCompatChangeReadAndLogPermission();
         if (mCompatConfig.isChangeEnabled(changeId, appInfo)) {
             reportChange(changeId, appInfo.uid,
                     ChangeReporter.STATE_ENABLED);
@@ -98,7 +108,9 @@
     }
 
     @Override
-    public boolean isChangeEnabledByPackageName(long changeId, String packageName, int userId) {
+    public boolean isChangeEnabledByPackageName(long changeId, String packageName,
+            @UserIdInt int userId) {
+        checkCompatChangeReadAndLogPermission();
         ApplicationInfo appInfo = getApplicationInfo(packageName, userId);
         if (appInfo == null) {
             return true;
@@ -108,6 +120,7 @@
 
     @Override
     public boolean isChangeEnabledByUid(long changeId, int uid) {
+        checkCompatChangeReadAndLogPermission();
         String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
         if (packages == null || packages.length == 0) {
             return true;
@@ -140,6 +153,7 @@
     @Override
     public void setOverrides(CompatibilityChangeConfig overrides, String packageName)
             throws RemoteException, SecurityException {
+        checkCompatChangeOverridePermission();
         mCompatConfig.addOverrides(overrides, packageName);
         killPackage(packageName);
     }
@@ -147,11 +161,13 @@
     @Override
     public void setOverridesForTest(CompatibilityChangeConfig overrides, String packageName)
             throws RemoteException, SecurityException {
+        checkCompatChangeOverridePermission();
         mCompatConfig.addOverrides(overrides, packageName);
     }
 
     @Override
     public void clearOverrides(String packageName) throws RemoteException, SecurityException {
+        checkCompatChangeOverridePermission();
         mCompatConfig.removePackageOverrides(packageName);
         killPackage(packageName);
     }
@@ -159,12 +175,14 @@
     @Override
     public void clearOverridesForTest(String packageName)
             throws RemoteException, SecurityException {
+        checkCompatChangeOverridePermission();
         mCompatConfig.removePackageOverrides(packageName);
     }
 
     @Override
     public boolean clearOverride(long changeId, String packageName)
             throws RemoteException, SecurityException {
+        checkCompatChangeOverridePermission();
         boolean existed = mCompatConfig.removeOverride(changeId, packageName);
         killPackage(packageName);
         return existed;
@@ -172,11 +190,13 @@
 
     @Override
     public CompatibilityChangeConfig getAppConfig(ApplicationInfo appInfo) {
+        checkCompatChangeReadAndLogPermission();
         return mCompatConfig.getAppConfig(appInfo);
     }
 
     @Override
     public CompatibilityChangeInfo[] listAllChanges() {
+        checkCompatChangeReadPermission();
         return mCompatConfig.dumpChanges();
     }
 
@@ -215,6 +235,7 @@
 
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        checkCompatChangeReadAndLogPermission();
         if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, "platform_compat", pw)) return;
         mCompatConfig.dumpConfig(pw);
     }
@@ -272,4 +293,30 @@
             Binder.restoreCallingIdentity(identity);
         }
     }
+
+    private void checkCompatChangeLogPermission() throws SecurityException {
+        if (mContext.checkCallingOrSelfPermission(LOG_COMPAT_CHANGE)
+                != PERMISSION_GRANTED) {
+            throw new SecurityException("Cannot log compat change usage");
+        }
+    }
+
+    private void checkCompatChangeReadPermission() throws SecurityException {
+        if (mContext.checkCallingOrSelfPermission(READ_COMPAT_CHANGE_CONFIG)
+                != PERMISSION_GRANTED) {
+            throw new SecurityException("Cannot read compat change");
+        }
+    }
+
+    private void checkCompatChangeOverridePermission() throws SecurityException {
+        if (mContext.checkCallingOrSelfPermission(OVERRIDE_COMPAT_CHANGE_CONFIG)
+                != PERMISSION_GRANTED) {
+            throw new SecurityException("Cannot override compat change");
+        }
+    }
+
+    private void checkCompatChangeReadAndLogPermission() throws SecurityException {
+        checkCompatChangeReadPermission();
+        checkCompatChangeLogPermission();
+    }
 }
diff --git a/services/core/java/com/android/server/compat/PlatformCompatNative.java b/services/core/java/com/android/server/compat/PlatformCompatNative.java
index 85dfbf4..5d7af65 100644
--- a/services/core/java/com/android/server/compat/PlatformCompatNative.java
+++ b/services/core/java/com/android/server/compat/PlatformCompatNative.java
@@ -16,6 +16,8 @@
 
 package com.android.server.compat;
 
+import android.annotation.UserIdInt;
+
 import com.android.internal.compat.IPlatformCompatNative;
 
 /**
@@ -39,7 +41,8 @@
     }
 
     @Override
-    public boolean isChangeEnabledByPackageName(long changeId, String packageName, int userId) {
+    public boolean isChangeEnabledByPackageName(long changeId, String packageName,
+            @UserIdInt int userId) {
         return mPlatformCompat.isChangeEnabledByPackageName(changeId, packageName, userId);
     }
 
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 89af5b5..cb88c4e 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -49,6 +49,7 @@
 import android.net.ConnectivityManager;
 import android.net.INetworkManagementEventObserver;
 import android.net.IpPrefix;
+import android.net.IpSecManager;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.LocalSocket;
@@ -180,7 +181,10 @@
     private boolean mIsPackageTargetingAtLeastQ;
     private String mInterface;
     private Connection mConnection;
-    private LegacyVpnRunner mLegacyVpnRunner;
+
+    /** Tracks the runners for all VPN types managed by the platform (eg. LegacyVpn, PlatformVpn) */
+    private VpnRunner mVpnRunner;
+
     private PendingIntent mStatusIntent;
     private volatile boolean mEnableTeardown = true;
     private final INetworkManagementService mNetd;
@@ -762,7 +766,7 @@
                 mNetworkCapabilities.setUids(null);
             }
 
-            // Revoke the connection or stop LegacyVpnRunner.
+            // Revoke the connection or stop the VpnRunner.
             if (mConnection != null) {
                 try {
                     mConnection.mService.transact(IBinder.LAST_CALL_TRANSACTION,
@@ -772,9 +776,9 @@
                 }
                 mContext.unbindService(mConnection);
                 mConnection = null;
-            } else if (mLegacyVpnRunner != null) {
-                mLegacyVpnRunner.exit();
-                mLegacyVpnRunner = null;
+            } else if (mVpnRunner != null) {
+                mVpnRunner.exit();
+                mVpnRunner = null;
             }
 
             try {
@@ -1510,8 +1514,8 @@
         @Override
         public void interfaceStatusChanged(String interfaze, boolean up) {
             synchronized (Vpn.this) {
-                if (!up && mLegacyVpnRunner != null) {
-                    mLegacyVpnRunner.check(interfaze);
+                if (!up && mVpnRunner != null && mVpnRunner instanceof LegacyVpnRunner) {
+                    ((LegacyVpnRunner) mVpnRunner).exitIfOuterInterfaceIs(interfaze);
                 }
             }
         }
@@ -1528,9 +1532,10 @@
                         mContext.unbindService(mConnection);
                         mConnection = null;
                         agentDisconnect();
-                    } else if (mLegacyVpnRunner != null) {
-                        mLegacyVpnRunner.exit();
-                        mLegacyVpnRunner = null;
+                    } else if (mVpnRunner != null) {
+                        // agentDisconnect must be called from mVpnRunner.exit()
+                        mVpnRunner.exit();
+                        mVpnRunner = null;
                     }
                 }
             }
@@ -1913,23 +1918,40 @@
 
     private synchronized void startLegacyVpn(VpnConfig config, String[] racoon, String[] mtpd,
             VpnProfile profile) {
-        stopLegacyVpnPrivileged();
+        stopVpnRunnerPrivileged();
 
         // Prepare for the new request.
         prepareInternal(VpnConfig.LEGACY_VPN);
         updateState(DetailedState.CONNECTING, "startLegacyVpn");
 
         // Start a new LegacyVpnRunner and we are done!
-        mLegacyVpnRunner = new LegacyVpnRunner(config, racoon, mtpd, profile);
-        mLegacyVpnRunner.start();
+        mVpnRunner = new LegacyVpnRunner(config, racoon, mtpd, profile);
+        mVpnRunner.start();
     }
 
-    /** Stop legacy VPN. Permissions must be checked by callers. */
-    public synchronized void stopLegacyVpnPrivileged() {
-        if (mLegacyVpnRunner != null) {
-            mLegacyVpnRunner.exit();
-            mLegacyVpnRunner = null;
+    /**
+     * Checks if this the currently running VPN (if any) was started by the Settings app
+     *
+     * <p>This includes both Legacy VPNs and Platform VPNs.
+     */
+    private boolean isSettingsVpnLocked() {
+        return mVpnRunner != null && VpnConfig.LEGACY_VPN.equals(mPackage);
+    }
 
+    /** Stop VPN runner. Permissions must be checked by callers. */
+    public synchronized void stopVpnRunnerPrivileged() {
+        if (!isSettingsVpnLocked()) {
+            return;
+        }
+
+        final boolean isLegacyVpn = mVpnRunner instanceof LegacyVpnRunner;
+
+        mVpnRunner.exit();
+        mVpnRunner = null;
+
+        // LegacyVpn uses daemons that must be shut down before new ones are brought up.
+        // The same limitation does not apply to Platform VPNs.
+        if (isLegacyVpn) {
             synchronized (LegacyVpnRunner.TAG) {
                 // wait for old thread to completely finish before spinning up
                 // new instance, otherwise state updates can be out of order.
@@ -1951,7 +1973,7 @@
      * Callers are responsible for checking permissions if needed.
      */
     private synchronized LegacyVpnInfo getLegacyVpnInfoPrivileged() {
-        if (mLegacyVpnRunner == null) return null;
+        if (!isSettingsVpnLocked()) return null;
 
         final LegacyVpnInfo info = new LegacyVpnInfo();
         info.key = mConfig.user;
@@ -1962,14 +1984,53 @@
         return info;
     }
 
-    public VpnConfig getLegacyVpnConfig() {
-        if (mLegacyVpnRunner != null) {
+    public synchronized VpnConfig getLegacyVpnConfig() {
+        if (isSettingsVpnLocked()) {
             return mConfig;
         } else {
             return null;
         }
     }
 
+    /** This class represents the common interface for all VPN runners. */
+    private abstract class VpnRunner extends Thread {
+
+        protected VpnRunner(String name) {
+            super(name);
+        }
+
+        public abstract void run();
+
+        protected abstract void exit();
+    }
+
+    private class IkeV2VpnRunner extends VpnRunner {
+        private static final String TAG = "IkeV2VpnRunner";
+
+        private final IpSecManager mIpSecManager;
+        private final VpnProfile mProfile;
+
+        IkeV2VpnRunner(VpnProfile profile) {
+            super(TAG);
+            mProfile = profile;
+
+            // TODO: move this to startVpnRunnerPrivileged()
+            mConfig = new VpnConfig();
+            mIpSecManager = mContext.getSystemService(IpSecManager.class);
+        }
+
+        @Override
+        public void run() {
+            // TODO: Build IKE config, start IKE session
+        }
+
+        @Override
+        public void exit() {
+            // TODO: Teardown IKE session & any resources.
+            agentDisconnect();
+        }
+    }
+
     /**
      * Bringing up a VPN connection takes time, and that is all this thread
      * does. Here we have plenty of time. The only thing we need to take
@@ -1977,7 +2038,7 @@
      * requests will pile up. This could be done in a Handler as a state
      * machine, but it is much easier to read in the current form.
      */
-    private class LegacyVpnRunner extends Thread {
+    private class LegacyVpnRunner extends VpnRunner {
         private static final String TAG = "LegacyVpnRunner";
 
         private final String[] mDaemons;
@@ -2047,13 +2108,21 @@
             mContext.registerReceiver(mBroadcastReceiver, filter);
         }
 
-        public void check(String interfaze) {
+        /**
+         * Checks if the parameter matches the underlying interface
+         *
+         * <p>If the underlying interface is torn down, the LegacyVpnRunner also should be. It has
+         * no ability to migrate between interfaces (or Networks).
+         */
+        public void exitIfOuterInterfaceIs(String interfaze) {
             if (interfaze.equals(mOuterInterface)) {
                 Log.i(TAG, "Legacy VPN is going down with " + interfaze);
                 exit();
             }
         }
 
+        /** Tears down this LegacyVpn connection */
+        @Override
         public void exit() {
             // We assume that everything is reset after stopping the daemons.
             interrupt();
diff --git a/services/core/java/com/android/server/display/DisplayAdapter.java b/services/core/java/com/android/server/display/DisplayAdapter.java
index 6ba25a5..838dc84 100644
--- a/services/core/java/com/android/server/display/DisplayAdapter.java
+++ b/services/core/java/com/android/server/display/DisplayAdapter.java
@@ -109,24 +109,14 @@
      */
     protected final void sendDisplayDeviceEventLocked(
             final DisplayDevice device, final int event) {
-        mHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                mListener.onDisplayDeviceEvent(device, event);
-            }
-        });
+        mHandler.post(() -> mListener.onDisplayDeviceEvent(device, event));
     }
 
     /**
      * Sends a request to perform traversals.
      */
     protected final void sendTraversalRequestLocked() {
-        mHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                mListener.onTraversalRequested();
-            }
-        });
+        mHandler.post(() -> mListener.onTraversalRequested());
     }
 
     public static Display.Mode createMode(int width, int height, float refreshRate) {
@@ -135,7 +125,7 @@
     }
 
     public interface Listener {
-        public void onDisplayDeviceEvent(DisplayDevice device, int event);
-        public void onTraversalRequested();
+        void onDisplayDeviceEvent(DisplayDevice device, int event);
+        void onTraversalRequested();
     }
 }
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 87a5730..fc9542a 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -49,6 +49,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * A display adapter for the local displays managed by Surface Flinger.
@@ -64,8 +65,9 @@
 
     private static final String PROPERTY_EMULATOR_CIRCULAR = "ro.emulator.circular";
 
-    private final LongSparseArray<LocalDisplayDevice> mDevices =
-            new LongSparseArray<LocalDisplayDevice>();
+    private static final int NO_DISPLAY_MODE_ID = 0;
+
+    private final LongSparseArray<LocalDisplayDevice> mDevices = new LongSparseArray<>();
 
     @SuppressWarnings("unused")  // Becomes active at instantiation time.
     private PhysicalDisplayEventReceiver mPhysicalDisplayEventReceiver;
@@ -118,21 +120,22 @@
                         physicalDisplayId);
                 activeColorMode = Display.COLOR_MODE_INVALID;
             }
-            int[] colorModes = SurfaceControl.getDisplayColorModes(displayToken);
             SurfaceControl.DesiredDisplayConfigSpecs configSpecs =
                     SurfaceControl.getDesiredDisplayConfigSpecs(displayToken);
+            int[] colorModes = SurfaceControl.getDisplayColorModes(displayToken);
+            Display.HdrCapabilities hdrCapabilities =
+                    SurfaceControl.getHdrCapabilities(displayToken);
             LocalDisplayDevice device = mDevices.get(physicalDisplayId);
             if (device == null) {
                 // Display was added.
                 final boolean isInternal = mDevices.size() == 0;
                 device = new LocalDisplayDevice(displayToken, physicalDisplayId, info,
                         configs, activeConfig, configSpecs, colorModes, activeColorMode,
-                        isInternal);
+                        hdrCapabilities, isInternal);
                 mDevices.put(physicalDisplayId, device);
                 sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_ADDED);
-            } else if (device.updateDisplayConfigsLocked(configs, activeConfig, configSpecs,
-                        colorModes, activeColorMode)) {
-                // Display properties changed.
+            } else if (device.updateDisplayProperties(configs, activeConfig,
+                    configSpecs, colorModes, activeColorMode, hdrCapabilities)) {
                 sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_CHANGED);
             }
         } else {
@@ -202,16 +205,14 @@
         LocalDisplayDevice(IBinder displayToken, long physicalDisplayId,
                 SurfaceControl.DisplayInfo info, SurfaceControl.DisplayConfig[] configs,
                 int activeConfigId, SurfaceControl.DesiredDisplayConfigSpecs configSpecs,
-                int[] colorModes, int activeColorMode, boolean isInternal) {
+                int[] colorModes, int activeColorMode, Display.HdrCapabilities hdrCapabilities,
+                boolean isInternal) {
             super(LocalDisplayAdapter.this, displayToken, UNIQUE_ID_PREFIX + physicalDisplayId);
             mPhysicalDisplayId = physicalDisplayId;
             mIsInternal = isInternal;
             mDisplayInfo = info;
-
-            updateDisplayConfigsLocked(configs, activeConfigId, configSpecs, colorModes,
-                    activeColorMode);
-            updateColorModesLocked(colorModes, activeColorMode);
-
+            updateDisplayProperties(configs, activeConfigId, configSpecs, colorModes,
+                    activeColorMode, hdrCapabilities);
             mSidekickInternal = LocalServices.getService(SidekickInternal.class);
             if (mIsInternal) {
                 LightsManager lights = LocalServices.getService(LightsManager.class);
@@ -219,7 +220,6 @@
             } else {
                 mBacklight = null;
             }
-            mHdrCapabilities = SurfaceControl.getHdrCapabilities(displayToken);
             mAllmSupported = SurfaceControl.getAutoLowLatencyModeSupport(displayToken);
             mGameContentTypeSupported = SurfaceControl.getGameContentTypeSupport(displayToken);
             mHalBrightnessSupport = SurfaceControl.getDisplayBrightnessSupport(displayToken);
@@ -234,15 +234,25 @@
             return true;
         }
 
+        /**
+         * Returns true if there is a change.
+         **/
+        public boolean updateDisplayProperties(SurfaceControl.DisplayConfig[] configs,
+                int activeConfigId, SurfaceControl.DesiredDisplayConfigSpecs configSpecs,
+                int[] colorModes, int activeColorMode, Display.HdrCapabilities hdrCapabilities) {
+            boolean changed = updateDisplayConfigsLocked(configs, activeConfigId, configSpecs);
+            changed |= updateColorModesLocked(colorModes, activeColorMode);
+            changed |= updateHdrCapabilitiesLocked(hdrCapabilities);
+            return changed;
+        }
+
         public boolean updateDisplayConfigsLocked(
                 SurfaceControl.DisplayConfig[] configs, int activeConfigId,
-                SurfaceControl.DesiredDisplayConfigSpecs configSpecs, int[] colorModes,
-                int activeColorMode) {
+                SurfaceControl.DesiredDisplayConfigSpecs configSpecs) {
             mDisplayConfigs = Arrays.copyOf(configs, configs.length);
             mActiveConfigId = activeConfigId;
-
             // Build an updated list of all existing modes.
-            ArrayList<DisplayModeRecord> records = new ArrayList<DisplayModeRecord>();
+            ArrayList<DisplayModeRecord> records = new ArrayList<>();
             boolean modesAdded = false;
             for (int i = 0; i < configs.length; i++) {
                 SurfaceControl.DisplayConfig config = configs[i];
@@ -282,7 +292,7 @@
 
             // Check whether surface flinger spontaneously changed modes out from under us.
             // Schedule traversals to ensure that the correct state is reapplied if necessary.
-            if (mActiveModeId != 0
+            if (mActiveModeId != NO_DISPLAY_MODE_ID
                     && mActiveModeId != activeRecord.mMode.getModeId()) {
                 mActiveModeInvalid = true;
                 sendTraversalRequestLocked();
@@ -290,12 +300,12 @@
 
             // Check whether surface flinger spontaneously changed display config specs out from
             // under us. If so, schedule a traversal to reapply our display config specs.
-            if (mDisplayModeSpecs.baseModeId != 0) {
+            if (mDisplayModeSpecs.baseModeId != NO_DISPLAY_MODE_ID) {
                 int activeBaseMode = findMatchingModeIdLocked(configSpecs.defaultConfig);
                 // If we can't map the defaultConfig index to a mode, then the physical display
                 // configs must have changed, and the code below for handling changes to the
                 // list of available modes will take care of updating display config specs.
-                if (activeBaseMode != 0) {
+                if (activeBaseMode != NO_DISPLAY_MODE_ID) {
                     if (mDisplayModeSpecs.baseModeId != activeBaseMode
                             || mDisplayModeSpecs.refreshRateRange.min != configSpecs.minRefreshRate
                             || mDisplayModeSpecs.refreshRateRange.max
@@ -319,18 +329,23 @@
                 mSupportedModes.put(record.mMode.getModeId(), record);
             }
 
-            // Update the default mode, if needed.
-            if (findDisplayConfigIdLocked(mDefaultModeId) < 0) {
-                if (mDefaultModeId != 0) {
-                    Slog.w(TAG, "Default display mode no longer available, using currently"
-                            + " active mode as default.");
-                }
+            // For a new display, we need to initialize the default mode ID.
+            if (mDefaultModeId == NO_DISPLAY_MODE_ID) {
+                mDefaultModeId = activeRecord.mMode.getModeId();
+            } else if (modesAdded && mActiveModeId != activeRecord.mMode.getModeId()) {
+                Slog.d(TAG, "New display modes are added and the active mode has changed, "
+                        + "use active mode as default mode.");
+                mActiveModeId = activeRecord.mMode.getModeId();
+                mDefaultModeId = activeRecord.mMode.getModeId();
+            } else if (findDisplayConfigIdLocked(mDefaultModeId) < 0) {
+                Slog.w(TAG, "Default display mode no longer available, using currently"
+                        + " active mode as default.");
                 mDefaultModeId = activeRecord.mMode.getModeId();
             }
 
             // Determine whether the display mode specs' base mode is still there.
             if (mSupportedModes.indexOfKey(mDisplayModeSpecs.baseModeId) < 0) {
-                if (mDisplayModeSpecs.baseModeId != 0) {
+                if (mDisplayModeSpecs.baseModeId != NO_DISPLAY_MODE_ID) {
                     Slog.w(TAG,
                             "DisplayModeSpecs base mode no longer available, using currently"
                                     + " active mode.");
@@ -341,7 +356,7 @@
 
             // Determine whether the active mode is still there.
             if (mSupportedModes.indexOfKey(mActiveModeId) < 0) {
-                if (mActiveModeId != 0) {
+                if (mActiveModeId != NO_DISPLAY_MODE_ID) {
                     Slog.w(TAG, "Active display mode no longer available, reverting to default"
                             + " mode.");
                 }
@@ -389,14 +404,15 @@
             mSystemBrightnessToNits = sysToNits;
         }
 
-        private boolean updateColorModesLocked(int[] colorModes,
-                int activeColorMode) {
-            List<Integer> pendingColorModes = new ArrayList<>();
+        private boolean updateColorModesLocked(int[] colorModes, int activeColorMode) {
+            if (colorModes == null) {
+                return false;
+            }
 
-            if (colorModes == null) return false;
+            List<Integer> pendingColorModes = new ArrayList<>();
             // Build an updated list of all existing color modes.
             boolean colorModesAdded = false;
-            for (int colorMode: colorModes) {
+            for (int colorMode : colorModes) {
                 if (!mSupportedColorModes.contains(colorMode)) {
                     colorModesAdded = true;
                 }
@@ -441,6 +457,15 @@
             return true;
         }
 
+        private boolean updateHdrCapabilitiesLocked(Display.HdrCapabilities newHdrCapabilities) {
+            // If the HDR capabilities haven't changed, then we're done here.
+            if (Objects.equals(mHdrCapabilities, newHdrCapabilities)) {
+                return false;
+            }
+            mHdrCapabilities = newHdrCapabilities;
+            return true;
+        }
+
         private DisplayModeRecord findDisplayModeRecord(SurfaceControl.DisplayConfig config) {
             for (int i = 0; i < mSupportedModes.size(); i++) {
                 DisplayModeRecord record = mSupportedModes.valueAt(i);
@@ -783,7 +808,7 @@
             }
             mActiveConfigId = activeConfigId;
             mActiveModeId = findMatchingModeIdLocked(activeConfigId);
-            mActiveModeInvalid = mActiveModeId == 0;
+            mActiveModeInvalid = mActiveModeId == NO_DISPLAY_MODE_ID;
             if (mActiveModeInvalid) {
                 Slog.w(TAG, "In unknown mode after setting allowed configs"
                         + ", activeConfigId=" + mActiveConfigId);
@@ -908,7 +933,7 @@
                     return record.mMode.getModeId();
                 }
             }
-            return 0;
+            return NO_DISPLAY_MODE_ID;
         }
 
         private void updateDeviceInfoLocked() {
diff --git a/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java b/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java
index 5161a77..b0e2e64 100644
--- a/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java
+++ b/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java
@@ -113,11 +113,27 @@
             return ERROR_COMMAND_EXECUTION;
         }
 
-        final Map<String, ParcelFileDescriptor> dataLoaderDynamicArgs = getDataLoaderDynamicArgs();
-        if (dataLoaderDynamicArgs == null) {
+        final Map<String, ParcelFileDescriptor> fds = getShellFileDescriptors();
+        if (fds == null) {
             pw.println("File names and sizes don't match.");
             return ERROR_DATA_LOADER_INIT;
         }
+        // dup FDs before closing them
+        final Map<String, ParcelFileDescriptor> dataLoaderDynamicArgs = new HashMap<>();
+        for (Map.Entry<String, ParcelFileDescriptor> nfd : fds.entrySet()) {
+            try {
+                dataLoaderDynamicArgs.put(nfd.getKey(), nfd.getValue().dup());
+            } catch (IOException ignored) {
+                pw.println("Failed to dup shell file descriptor");
+                return ERROR_DATA_LOADER_INIT;
+            } finally {
+                try {
+                    nfd.getValue().close();
+                } catch (IOException ignored) {
+                }
+            }
+        }
+
         final DataLoaderParams params = DataLoaderParams.forIncremental(
                 new ComponentName(LOADER_PACKAGE_NAME, LOADER_CLASS_NAME), "",
                 dataLoaderDynamicArgs);
@@ -131,17 +147,9 @@
         try {
             int sessionId = packageInstaller.createSession(sessionParams);
             pw.println("Successfully opened session: sessionId = " + sessionId);
-        } catch (Exception ex) {
+        } catch (IOException ex) {
             pw.println("Failed to create session.");
             return ERROR_COMMAND_EXECUTION;
-        } finally {
-            try {
-                for (Map.Entry<String, ParcelFileDescriptor> nfd
-                        : dataLoaderDynamicArgs.entrySet()) {
-                    nfd.getValue().close();
-                }
-            } catch (IOException ignored) {
-            }
         }
         return 0;
     }
@@ -177,7 +185,8 @@
                 InstallationFile file = installationFiles.get(i);
                 final int location = file.getFileType() == FILE_TYPE_OBB ? LOCATION_MEDIA_OBB
                         : LOCATION_DATA_APP;
-                session.addFile(location, file.getName(), file.getSize(), file.getMetadata(), null);
+                session.addFile(location, file.getName(), file.getSize(), file.getMetadata(),
+                        null);
             }
             session.commit(localReceiver.getIntentSender());
             final Intent result = localReceiver.getResult();
@@ -212,7 +221,8 @@
 
         private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
             @Override
-            public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
+            public void send(int code, Intent intent, String resolvedType,
+                    IBinder whitelistToken,
                     IIntentReceiver finishedReceiver, String requiredPermission,
                     Bundle options) {
                 try {
@@ -237,14 +247,14 @@
     }
 
     /** Helpers. */
-    private Map<String, ParcelFileDescriptor> getDataLoaderDynamicArgs() {
-        Map<String, ParcelFileDescriptor> dataLoaderDynamicArgs = new HashMap<>();
+    private Map<String, ParcelFileDescriptor> getShellFileDescriptors() {
+        Map<String, ParcelFileDescriptor> fds = new HashMap<>();
         final FileDescriptor outFd = getOutFileDescriptor();
         final FileDescriptor inFd = getInFileDescriptor();
         try {
-            dataLoaderDynamicArgs.put("inFd", ParcelFileDescriptor.dup(inFd));
-            dataLoaderDynamicArgs.put("outFd", ParcelFileDescriptor.dup(outFd));
-            return dataLoaderDynamicArgs;
+            fds.put("inFd", ParcelFileDescriptor.dup(inFd));
+            fds.put("outFd", ParcelFileDescriptor.dup(outFd));
+            return fds;
         } catch (Exception ex) {
             Slog.e(TAG, "Failed to dup FDs");
             return null;
@@ -292,7 +302,8 @@
                         pw.println("Invalid file index in: " + fileArgs);
                         return null;
                     }
-                    final byte[] metadata = String.valueOf(index).getBytes(StandardCharsets.UTF_8);
+                    final byte[] metadata = String.valueOf(index).getBytes(
+                            StandardCharsets.UTF_8);
                     fileList.add(new InstallationFile(name, size, metadata));
                     break;
                 }
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 90358ca..a8f706c 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -223,7 +223,7 @@
             int displayId, InputApplicationHandle application);
     private static native void nativeSetFocusedDisplay(long ptr, int displayId);
     private static native boolean nativeTransferTouchFocus(long ptr,
-            InputChannel fromChannel, InputChannel toChannel);
+            IBinder fromChannelToken, IBinder toChannelToken);
     private static native void nativeSetPointerSpeed(long ptr, int speed);
     private static native void nativeSetShowTouches(long ptr, boolean enabled);
     private static native void nativeSetInteractive(long ptr, boolean interactive);
@@ -1554,14 +1554,29 @@
      * @return True if the transfer was successful.  False if the window with the
      * specified channel did not actually have touch focus at the time of the request.
      */
-    public boolean transferTouchFocus(InputChannel fromChannel, InputChannel toChannel) {
-        if (fromChannel == null) {
-            throw new IllegalArgumentException("fromChannel must not be null.");
-        }
-        if (toChannel == null) {
-            throw new IllegalArgumentException("toChannel must not be null.");
-        }
-        return nativeTransferTouchFocus(mPtr, fromChannel, toChannel);
+    public boolean transferTouchFocus(@NonNull InputChannel fromChannel,
+            @NonNull InputChannel toChannel) {
+        return nativeTransferTouchFocus(mPtr, fromChannel.getToken(), toChannel.getToken());
+    }
+
+    /**
+     * Atomically transfers touch focus from one window to another as identified by
+     * their input channels.  It is possible for multiple windows to have
+     * touch focus if they support split touch dispatch
+     * {@link android.view.WindowManager.LayoutParams#FLAG_SPLIT_TOUCH} but this
+     * method only transfers touch focus of the specified window without affecting
+     * other windows that may also have touch focus at the same time.
+     * @param fromChannelToken The channel token of a window that currently has touch focus.
+     * @param toChannelToken The channel token of the window that should receive touch focus in
+     * place of the first.
+     * @return True if the transfer was successful.  False if the window with the
+     * specified channel did not actually have touch focus at the time of the request.
+     */
+    public boolean transferTouchFocus(@NonNull IBinder fromChannelToken,
+            @NonNull IBinder toChannelToken) {
+        Objects.nonNull(fromChannelToken);
+        Objects.nonNull(toChannelToken);
+        return nativeTransferTouchFocus(mPtr, fromChannelToken, toChannelToken);
     }
 
     @Override // Binder call
diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
index 9754b6d..0450647 100644
--- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
+++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
@@ -39,13 +39,20 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
+import android.content.pm.PackageParser;
+import android.content.pm.PackageUserState;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.Signature;
+import android.content.pm.SigningInfo;
+import android.content.pm.parsing.ApkParseUtils;
+import android.content.pm.parsing.PackageInfoUtils;
+import android.content.pm.parsing.ParsedPackage;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.Slog;
 
@@ -67,6 +74,7 @@
 import java.security.cert.CertificateException;
 import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
@@ -121,11 +129,11 @@
                 IntegrityFileManager.getInstance(),
                 handlerThread.getThreadHandler(),
                 Settings.Global.getInt(
-                                    context.getContentResolver(),
-                                    Settings.Global.INTEGRITY_CHECK_INCLUDES_RULE_PROVIDER,
-                                    0)
-                            == 1
-                );
+                        context.getContentResolver(),
+                        Settings.Global.INTEGRITY_CHECK_INCLUDES_RULE_PROVIDER,
+                        0)
+                        == 1
+        );
     }
 
     @VisibleForTesting
@@ -260,16 +268,25 @@
                 return;
             }
 
-            String appCert = getCertificateFingerprint(packageInfo);
+            List<String> appCertificates = getCertificateFingerprint(packageInfo);
+            List<String> installerCertificates =
+                    getInstallerCertificateFingerprint(installerPackageName);
+
+            // TODO (b/148373316): Figure out what field contains which fields are populated for
+            // rotated and the multiple signers. Until then, return the first certificate.
+            String appCert = appCertificates.isEmpty() ? "" : appCertificates.get(0);
+            String installerCert =
+                    installerCertificates.isEmpty() ? "" : installerCertificates.get(0);
+
+            Slog.w(TAG, appCertificates.toString());
 
             AppInstallMetadata.Builder builder = new AppInstallMetadata.Builder();
 
             builder.setPackageName(getPackageNameNormalized(packageName));
-            builder.setAppCertificate(appCert == null ? "" : appCert);
+            builder.setAppCertificate(appCert);
             builder.setVersionCode(intent.getLongExtra(EXTRA_LONG_VERSION_CODE, -1));
             builder.setInstallerName(getPackageNameNormalized(installerPackageName));
-            builder.setInstallerCertificate(
-                    getInstallerCertificateFingerprint(installerPackageName));
+            builder.setInstallerCertificate(installerCert);
             builder.setIsPreInstalled(isSystemApp(packageName));
 
             AppInstallMetadata appInstallMetadata = builder.build();
@@ -320,7 +337,7 @@
      * Verify the UID and return the installer package name.
      *
      * @return the package name of the installer, or null if it cannot be determined or it is
-     *     installed via adb.
+     * installed via adb.
      */
     @Nullable
     private String getInstallerPackageName(Intent intent) {
@@ -399,25 +416,29 @@
         }
     }
 
-    private String getCertificateFingerprint(@NonNull PackageInfo packageInfo) {
-        return getFingerprint(getSignature(packageInfo));
-    }
-
-    private String getInstallerCertificateFingerprint(String installer) {
+    private List<String> getInstallerCertificateFingerprint(String installer) {
         if (installer.equals(ADB_INSTALLER) || installer.equals(UNKNOWN_INSTALLER)) {
-            return INSTALLER_CERT_NOT_APPLICABLE;
+            return Collections.emptyList();
         }
         try {
             PackageInfo installerInfo =
                     mContext.getPackageManager()
-                            .getPackageInfo(installer, PackageManager.GET_SIGNATURES);
+                            .getPackageInfo(installer, PackageManager.GET_SIGNING_CERTIFICATES);
             return getCertificateFingerprint(installerInfo);
         } catch (PackageManager.NameNotFoundException e) {
             Slog.i(TAG, "Installer package " + installer + " not found.");
-            return "";
+            return Collections.emptyList();
         }
     }
 
+    private List<String> getCertificateFingerprint(@NonNull PackageInfo packageInfo) {
+        ArrayList<String> certificateFingerprints = new ArrayList();
+        for (Signature signature : getSignatures(packageInfo)) {
+            certificateFingerprints.add(getFingerprint(signature));
+        }
+        return certificateFingerprints;
+    }
+
     /** Get the allowed installers and their associated certificate hashes from <meta-data> tag. */
     private Map<String, String> getAllowedInstallers(@NonNull PackageInfo packageInfo) {
         Map<String, String> packageCertMap = new HashMap<>();
@@ -445,12 +466,15 @@
         return packageCertMap;
     }
 
-    private static Signature getSignature(@NonNull PackageInfo packageInfo) {
-        if (packageInfo.signatures == null || packageInfo.signatures.length < 1) {
+    private static Signature[] getSignatures(@NonNull PackageInfo packageInfo) {
+        SigningInfo signingInfo = packageInfo.signingInfo;
+
+        if (signingInfo == null || signingInfo.getApkContentsSigners().length < 1) {
             throw new IllegalArgumentException("Package signature not found in " + packageInfo);
         }
-        // Only the first element is guaranteed to be present.
-        return packageInfo.signatures[0];
+
+        // We are only interested in evaluating the active signatures.
+        return signingInfo.getApkContentsSigners();
     }
 
     private static String getFingerprint(Signature cert) {
@@ -489,20 +513,14 @@
         if (installationPath == null) {
             throw new IllegalArgumentException("Installation path is null, package not found");
         }
-        PackageInfo packageInfo;
+
+        PackageParser parser = new PackageParser();
         try {
-            // The installation path will be a directory for a multi-apk install on L+
-            if (installationPath.isDirectory()) {
-                packageInfo = getMultiApkInfo(installationPath);
-            } else {
-                packageInfo =
-                        mContext.getPackageManager()
-                                .getPackageArchiveInfo(
-                                        installationPath.getPath(),
-                                        PackageManager.GET_SIGNATURES
-                                                | PackageManager.GET_META_DATA);
-            }
-            return packageInfo;
+            ParsedPackage pkg = parser.parseParsedPackage(installationPath, 0, false);
+            int flags = PackageManager.GET_SIGNING_CERTIFICATES | PackageManager.GET_META_DATA;
+            ApkParseUtils.collectCertificates(pkg, false);
+            return PackageInfoUtils.generate(pkg, null, flags, 0, 0, null, new PackageUserState(),
+                    UserHandle.getCallingUserId());
         } catch (Exception e) {
             throw new IllegalArgumentException("Exception reading " + dataUri, e);
         }
@@ -515,7 +533,8 @@
                 mContext.getPackageManager()
                         .getPackageArchiveInfo(
                                 baseFile.getAbsolutePath(),
-                                PackageManager.GET_SIGNATURES | PackageManager.GET_META_DATA);
+                                PackageManager.GET_SIGNING_CERTIFICATES
+                                        | PackageManager.GET_META_DATA);
 
         if (basePackageInfo == null) {
             for (File apkFile : multiApkDirectory.listFiles()) {
diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java
index 5123362..8358884 100644
--- a/services/core/java/com/android/server/media/MediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java
@@ -21,6 +21,7 @@
 import android.content.ComponentName;
 import android.content.Intent;
 import android.media.MediaRoute2ProviderInfo;
+import android.media.RouteDiscoveryPreference;
 import android.media.RoutingSessionInfo;
 import android.os.Bundle;
 
@@ -54,6 +55,7 @@
     public abstract void requestCreateSession(String packageName, String routeId, long requestId,
             @Nullable Bundle sessionHints);
     public abstract void releaseSession(String sessionId);
+    public abstract void updateDiscoveryPreference(RouteDiscoveryPreference discoveryPreference);
 
     public abstract void selectRoute(String sessionId, String routeId);
     public abstract void deselectRoute(String sessionId, String routeId);
@@ -61,7 +63,6 @@
 
     public abstract void sendControlRequest(String routeId, Intent request);
     public abstract void requestSetVolume(String routeId, int volume);
-    public abstract void requestUpdateVolume(String routeId, int delta);
 
     @NonNull
     public String getUniqueId() {
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
index e23542e..d08fb71 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
@@ -25,6 +25,7 @@
 import android.media.IMediaRoute2ProviderClient;
 import android.media.MediaRoute2ProviderInfo;
 import android.media.MediaRoute2ProviderService;
+import android.media.RouteDiscoveryPreference;
 import android.media.RoutingSessionInfo;
 import android.os.Bundle;
 import android.os.Handler;
@@ -93,6 +94,14 @@
     }
 
     @Override
+    public void updateDiscoveryPreference(RouteDiscoveryPreference discoveryPreference) {
+        if (mConnectionReady) {
+            mActiveConnection.updateDiscoveryPreference(discoveryPreference);
+            updateBinding();
+        }
+    }
+
+    @Override
     public void selectRoute(String sessionId, String routeId) {
         if (mConnectionReady) {
             mActiveConnection.selectRoute(sessionId, routeId);
@@ -129,14 +138,6 @@
         }
     }
 
-    @Override
-    public void requestUpdateVolume(String routeId, int delta) {
-        if (mConnectionReady) {
-            mActiveConnection.requestUpdateVolume(routeId, delta);
-            updateBinding();
-        }
-    }
-
     public boolean hasComponentName(String packageName, String className) {
         return mComponentName.getPackageName().equals(packageName)
                 && mComponentName.getClassName().equals(className);
@@ -461,6 +462,14 @@
             }
         }
 
+        public void updateDiscoveryPreference(RouteDiscoveryPreference discoveryPreference) {
+            try {
+                mProvider.updateDiscoveryPreference(discoveryPreference);
+            } catch (RemoteException ex) {
+                Slog.e(TAG, "updateDiscoveryPreference(): Failed to deliver request.");
+            }
+        }
+
         public void selectRoute(String sessionId, String routeId) {
             try {
                 mProvider.selectRoute(sessionId, routeId);
@@ -501,14 +510,6 @@
             }
         }
 
-        public void requestUpdateVolume(String routeId, int delta) {
-            try {
-                mProvider.requestUpdateVolume(routeId, delta);
-            } catch (RemoteException ex) {
-                Slog.e(TAG, "Failed to deliver request to request update volume.", ex);
-            }
-        }
-
         @Override
         public void binderDied() {
             mHandler.post(() -> onConnectionDied(Connection.this));
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 9594659..b113322 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -334,20 +334,6 @@
         }
     }
 
-    public void requestUpdateVolume2(IMediaRouter2Client client, MediaRoute2Info route, int delta) {
-        Objects.requireNonNull(client, "client must not be null");
-        Objects.requireNonNull(route, "route must not be null");
-
-        final long token = Binder.clearCallingIdentity();
-        try {
-            synchronized (mLock) {
-                requestUpdateVolumeLocked(client, route, delta);
-            }
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
     public void requestCreateClientSession(IMediaRouter2Manager manager, String packageName,
             MediaRoute2Info route, int requestId) {
         final long token = Binder.clearCallingIdentity();
@@ -375,21 +361,6 @@
         }
     }
 
-    public void requestUpdateVolume2Manager(IMediaRouter2Manager manager,
-            MediaRoute2Info route, int delta) {
-        Objects.requireNonNull(manager, "manager must not be null");
-        Objects.requireNonNull(route, "route must not be null");
-
-        final long token = Binder.clearCallingIdentity();
-        try {
-            synchronized (mLock) {
-                requestUpdateVolumeLocked(manager, route, delta);
-            }
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
     @NonNull
     public List<RoutingSessionInfo> getActiveSessions(IMediaRouter2Manager manager) {
         final long token = Binder.clearCallingIdentity();
@@ -598,6 +569,9 @@
             clientRecord.mUserRecord.mHandler.sendMessage(
                     obtainMessage(UserHandler::updateClientUsage,
                             clientRecord.mUserRecord.mHandler, clientRecord));
+            clientRecord.mUserRecord.mHandler.sendMessage(
+                    obtainMessage(UserHandler::updateDiscoveryPreference,
+                            clientRecord.mUserRecord.mHandler));
         }
     }
 
@@ -625,18 +599,6 @@
         }
     }
 
-    private void requestUpdateVolumeLocked(IMediaRouter2Client client, MediaRoute2Info route,
-            int delta) {
-        final IBinder binder = client.asBinder();
-        Client2Record clientRecord = mAllClientRecords.get(binder);
-
-        if (clientRecord != null) {
-            clientRecord.mUserRecord.mHandler.sendMessage(
-                    obtainMessage(UserHandler::requestUpdateVolume,
-                            clientRecord.mUserRecord.mHandler, route, delta));
-        }
-    }
-
     private void registerManagerLocked(IMediaRouter2Manager manager,
             int uid, int pid, String packageName, int userId, boolean trusted) {
         final IBinder binder = manager.asBinder();
@@ -710,18 +672,6 @@
         }
     }
 
-    private void requestUpdateVolumeLocked(IMediaRouter2Manager manager, MediaRoute2Info route,
-            int delta) {
-        final IBinder binder = manager.asBinder();
-        ManagerRecord managerRecord = mAllManagerRecords.get(binder);
-
-        if (managerRecord != null) {
-            managerRecord.mUserRecord.mHandler.sendMessage(
-                    obtainMessage(UserHandler::requestUpdateVolume,
-                            managerRecord.mUserRecord.mHandler, route, delta));
-        }
-    }
-
     private List<RoutingSessionInfo> getActiveSessionsLocked(IMediaRouter2Manager manager) {
         final IBinder binder = manager.asBinder();
         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
@@ -856,6 +806,7 @@
         //TODO: make records private for thread-safety
         final ArrayList<Client2Record> mClientRecords = new ArrayList<>();
         final ArrayList<ManagerRecord> mManagerRecords = new ArrayList<>();
+        RouteDiscoveryPreference mCompositeDiscoveryPreference = RouteDiscoveryPreference.EMPTY;
         final UserHandler mHandler;
 
         UserRecord(int userId) {
@@ -885,8 +836,6 @@
         public final int mClientId;
 
         public RouteDiscoveryPreference mDiscoveryPreference;
-        public boolean mIsManagerSelecting;
-        public MediaRoute2Info mSelectingRoute;
         public MediaRoute2Info mSelectedRoute;
 
         Client2Record(UserRecord userRecord, IMediaRouter2Client client,
@@ -1003,6 +952,7 @@
         public void onAddProvider(MediaRoute2ProviderProxy provider) {
             provider.setCallback(this);
             mMediaProviders.add(provider);
+            provider.updateDiscoveryPreference(mUserRecord.mCompositeDiscoveryPreference);
         }
 
         @Override
@@ -1456,13 +1406,6 @@
             }
         }
 
-        private void requestUpdateVolume(MediaRoute2Info route, int delta) {
-            final MediaRoute2Provider provider = findProvider(route.getProviderId());
-            if (provider != null) {
-                provider.requestUpdateVolume(route.getOriginalId(), delta);
-            }
-        }
-
         private List<IMediaRouter2Client> getClients() {
             final List<IMediaRouter2Client> clients = new ArrayList<>();
             MediaRouter2ServiceImpl service = mServiceRef.get();
@@ -1642,6 +1585,25 @@
             }
         }
 
+        private void updateDiscoveryPreference() {
+            MediaRouter2ServiceImpl service = mServiceRef.get();
+            if (service == null) {
+                return;
+            }
+            List<RouteDiscoveryPreference> discoveryPreferences = new ArrayList<>();
+            synchronized (service.mLock) {
+                for (Client2Record clientRecord : mUserRecord.mClientRecords) {
+                    discoveryPreferences.add(clientRecord.mDiscoveryPreference);
+                }
+                mUserRecord.mCompositeDiscoveryPreference =
+                        new RouteDiscoveryPreference.Builder(discoveryPreferences)
+                        .build();
+            }
+            for (MediaRoute2Provider provider : mMediaProviders) {
+                provider.updateDiscoveryPreference(mUserRecord.mCompositeDiscoveryPreference);
+            }
+        }
+
         private MediaRoute2Provider findProvider(String providerId) {
             for (MediaRoute2Provider provider : mMediaProviders) {
                 if (TextUtils.equals(provider.getUniqueId(), providerId)) {
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index e1d3803..57f0328 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -539,12 +539,6 @@
 
     // Binder call
     @Override
-    public void requestUpdateVolume2(IMediaRouter2Client client, MediaRoute2Info route, int delta) {
-        mService2.requestUpdateVolume2(client, route, delta);
-    }
-
-    // Binder call
-    @Override
     public void requestSetVolume2Manager(IMediaRouter2Manager manager,
             MediaRoute2Info route, int volume) {
         mService2.requestSetVolume2Manager(manager, route, volume);
@@ -552,13 +546,6 @@
 
     // Binder call
     @Override
-    public void requestUpdateVolume2Manager(IMediaRouter2Manager manager,
-            MediaRoute2Info route, int delta) {
-        mService2.requestUpdateVolume2Manager(manager, route, delta);
-    }
-
-    // Binder call
-    @Override
     public List<RoutingSessionInfo> getActiveSessions(IMediaRouter2Manager manager) {
         return mService2.getActiveSessions(manager);
     }
diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java
index 820731d..11b7a8a 100644
--- a/services/core/java/com/android/server/media/MediaSession2Record.java
+++ b/services/core/java/com/android/server/media/MediaSession2Record.java
@@ -52,6 +52,8 @@
     private boolean mIsConnected;
     @GuardedBy("mLock")
     private int mPolicies;
+    @GuardedBy("mLock")
+    private boolean mIsClosed;
 
     public MediaSession2Record(Session2Token sessionToken, MediaSessionService service,
             Looper handlerLooper, int policies) {
@@ -119,6 +121,7 @@
     @Override
     public void close() {
         synchronized (mLock) {
+            mIsClosed = true;
             // Call close regardless of the mIsAvailable. This may be called when it's not yet
             // connected.
             mController.close();
@@ -126,6 +129,13 @@
     }
 
     @Override
+    public boolean isClosed() {
+        synchronized (mLock) {
+            return mIsClosed;
+        }
+    }
+
+    @Override
     public boolean sendMediaButton(String packageName, int pid, int uid, boolean asSystemService,
             KeyEvent ke, int sequenceId, ResultReceiver cb) {
         // TODO(jaewan): Implement.
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 05f7e1d..7bcbcd4 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -420,6 +420,13 @@
         }
     }
 
+    @Override
+    public boolean isClosed() {
+        synchronized (mLock) {
+            return mDestroyed;
+        }
+    }
+
     /**
      * Sends media button.
      *
diff --git a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
index 6e10880..74e6ded 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
@@ -154,4 +154,9 @@
      */
     @Override
     void close();
+
+    /**
+     * Returns whether {@link #close()} is called before.
+     */
+    boolean isClosed();
 }
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index d0efef0..88b884e 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -434,9 +434,14 @@
         if (DEBUG) {
             Log.d(TAG, "Destroying " + session);
         }
+        if (session.isClosed()) {
+            Log.w(TAG, "Destroying already destroyed session. Ignoring.");
+            return;
+        }
+
         FullUserRecord user = getFullUserRecordLocked(session.getUserId());
 
-        if (user != null) {
+        if (user != null && session instanceof MediaSessionRecord) {
             final int uid = session.getUid();
             final int sessionCount = user.mUidToSessionCount.get(uid, 0);
             if (sessionCount <= 0) {
@@ -570,6 +575,13 @@
                 throw new RuntimeException("Session request from invalid user.");
             }
 
+            final int sessionCount = user.mUidToSessionCount.get(callerUid, 0);
+            if (sessionCount >= SESSION_CREATION_LIMIT_PER_UID
+                    && !hasMediaControlPermission(callerPid, callerUid)) {
+                throw new RuntimeException("Created too many sessions. count="
+                        + sessionCount + ")");
+            }
+
             final MediaSessionRecord session;
             try {
                 session = new MediaSessionRecord(callerPid, callerUid, userId,
@@ -579,12 +591,6 @@
                 throw new RuntimeException("Media Session owner died prematurely.", e);
             }
 
-            final int sessionCount = user.mUidToSessionCount.get(callerUid, 0);
-            if (sessionCount >= SESSION_CREATION_LIMIT_PER_UID
-                    && !hasMediaControlPermission(callerPid, callerUid)) {
-                throw new RuntimeException("Created too many sessions. count="
-                        + sessionCount + ")");
-            }
             user.mUidToSessionCount.put(callerUid, sessionCount + 1);
 
             user.mPriorityStack.addSession(session);
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 924a9b7..888f7ce 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -29,6 +29,7 @@
 import android.media.IAudioService;
 import android.media.MediaRoute2Info;
 import android.media.MediaRoute2ProviderInfo;
+import android.media.RouteDiscoveryPreference;
 import android.media.RoutingSessionInfo;
 import android.os.Bundle;
 import android.os.Handler;
@@ -118,6 +119,10 @@
     public void releaseSession(String sessionId) {
         // Do nothing
     }
+    @Override
+    public void updateDiscoveryPreference(RouteDiscoveryPreference discoveryPreference) {
+        // Do nothing
+    }
 
     @Override
     public void selectRoute(String sessionId, String routeId) {
@@ -148,11 +153,6 @@
     public void requestSetVolume(String routeId, int volume) {
     }
 
-    //TODO: implement method
-    @Override
-    public void requestUpdateVolume(String routeId, int delta) {
-    }
-
     private void initializeDefaultRoute() {
         mDefaultRoute = new MediaRoute2Info.Builder(
                 DEFAULT_ROUTE_ID,
diff --git a/services/core/java/com/android/server/net/LockdownVpnTracker.java b/services/core/java/com/android/server/net/LockdownVpnTracker.java
index ef8f647..3cafaff 100644
--- a/services/core/java/com/android/server/net/LockdownVpnTracker.java
+++ b/services/core/java/com/android/server/net/LockdownVpnTracker.java
@@ -138,7 +138,7 @@
 
         if (egressDisconnected || egressChanged) {
             mAcceptedEgressIface = null;
-            mVpn.stopLegacyVpnPrivileged();
+            mVpn.stopVpnRunnerPrivileged();
         }
         if (egressDisconnected) {
             hideNotification();
@@ -218,7 +218,7 @@
         mAcceptedEgressIface = null;
         mErrorCount = 0;
 
-        mVpn.stopLegacyVpnPrivileged();
+        mVpn.stopVpnRunnerPrivileged();
         mVpn.setLockdown(false);
         hideNotification();
 
diff --git a/services/core/java/com/android/server/notification/BadgeExtractor.java b/services/core/java/com/android/server/notification/BadgeExtractor.java
index d91d541..af8baa5 100644
--- a/services/core/java/com/android/server/notification/BadgeExtractor.java
+++ b/services/core/java/com/android/server/notification/BadgeExtractor.java
@@ -43,9 +43,9 @@
             if (DBG) Slog.d(TAG, "missing config");
             return null;
         }
-        boolean userWantsBadges = mConfig.badgingEnabled(record.sbn.getUser());
+        boolean userWantsBadges = mConfig.badgingEnabled(record.getSbn().getUser());
         boolean appCanShowBadge =
-                mConfig.canShowBadge(record.sbn.getPackageName(), record.sbn.getUid());
+                mConfig.canShowBadge(record.getSbn().getPackageName(), record.getSbn().getUid());
         if (!userWantsBadges || !appCanShowBadge) {
             record.setShowBadge(false);
         } else {
diff --git a/services/core/java/com/android/server/notification/BubbleExtractor.java b/services/core/java/com/android/server/notification/BubbleExtractor.java
index e59bf16..c9c8042 100644
--- a/services/core/java/com/android/server/notification/BubbleExtractor.java
+++ b/services/core/java/com/android/server/notification/BubbleExtractor.java
@@ -42,7 +42,7 @@
             return null;
         }
         boolean appCanShowBubble =
-                mConfig.areBubblesAllowed(record.sbn.getPackageName(), record.sbn.getUid());
+                mConfig.areBubblesAllowed(record.getSbn().getPackageName(), record.getSbn().getUid());
         if (!mConfig.bubblesEnabled() || !appCanShowBubble) {
             record.setAllowBubble(false);
         } else {
diff --git a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
index 0e14364..83ca699 100644
--- a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
+++ b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
@@ -15,10 +15,8 @@
 */
 package com.android.server.notification;
 
-import android.app.Notification;
 import android.app.NotificationChannel;
 import android.content.Context;
-import android.util.FeatureFlagUtils;
 import android.util.Slog;
 
 /**
@@ -47,9 +45,9 @@
             return null;
         }
         NotificationChannel updatedChannel = mConfig.getConversationNotificationChannel(
-                record.sbn.getPackageName(),
-                record.sbn.getUid(), record.getChannel().getId(),
-                record.sbn.getShortcutId(mContext), true, false);
+                record.getSbn().getPackageName(),
+                record.getSbn().getUid(), record.getChannel().getId(),
+                record.getSbn().getShortcutId(mContext), true, false);
         record.updateNotificationChannel(updatedChannel);
 
         return null;
diff --git a/services/core/java/com/android/server/notification/NotificationComparator.java b/services/core/java/com/android/server/notification/NotificationComparator.java
index a7e40cb..29b5e81 100644
--- a/services/core/java/com/android/server/notification/NotificationComparator.java
+++ b/services/core/java/com/android/server/notification/NotificationComparator.java
@@ -122,8 +122,8 @@
             return -1 * Integer.compare(leftPackagePriority, rightPackagePriority);
         }
 
-        final int leftPriority = left.sbn.getNotification().priority;
-        final int rightPriority = right.sbn.getNotification().priority;
+        final int leftPriority = left.getSbn().getNotification().priority;
+        final int rightPriority = right.getSbn().getNotification().priority;
         if (leftPriority != rightPriority) {
             // by priority, high to low
             return -1 * Integer.compare(leftPriority, rightPriority);
@@ -169,7 +169,7 @@
     }
 
     protected boolean isImportantMessaging(NotificationRecord record) {
-        return mMessagingUtil.isImportantMessaging(record.sbn, record.getImportance());
+        return mMessagingUtil.isImportantMessaging(record.getSbn(), record.getImportance());
     }
 
     private boolean isOngoing(NotificationRecord record) {
@@ -183,7 +183,7 @@
 
     private boolean isCall(NotificationRecord record) {
         return record.isCategory(Notification.CATEGORY_CALL)
-                && isDefaultPhoneApp(record.sbn.getPackageName());
+                && isDefaultPhoneApp(record.getSbn().getPackageName());
     }
 
     private boolean isDefaultPhoneApp(String pkg) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 68cc014..f071135 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -684,14 +684,14 @@
         if (summary == null) {
             return;
         }
-        int oldFlags = summary.sbn.getNotification().flags;
+        int oldFlags = summary.getSbn().getNotification().flags;
         if (needsOngoingFlag) {
-            summary.sbn.getNotification().flags |= FLAG_ONGOING_EVENT;
+            summary.getSbn().getNotification().flags |= FLAG_ONGOING_EVENT;
         } else {
-            summary.sbn.getNotification().flags &= ~FLAG_ONGOING_EVENT;
+            summary.getSbn().getNotification().flags &= ~FLAG_ONGOING_EVENT;
         }
 
-        if (summary.sbn.getNotification().flags != oldFlags) {
+        if (summary.getSbn().getNotification().flags != oldFlags) {
             mHandler.post(new EnqueueNotificationRunnable(userId, summary, isAppForeground));
         }
     }
@@ -917,7 +917,7 @@
                         r.getLifespanMs(now), r.getFreshnessMs(now), r.getExposureMs(now),
                         nv.rank, nv.count);
 
-                StatusBarNotification sbn = r.sbn;
+                StatusBarNotification sbn = r.getSbn();
                 cancelNotification(callingUid, callingPid, sbn.getPackageName(), sbn.getTag(),
                         sbn.getId(), Notification.FLAG_AUTO_CANCEL,
                         FLAG_FOREGROUND_SERVICE, false, r.getUserId(),
@@ -959,7 +959,7 @@
                 nv.recycle();
                 reportUserInteraction(r);
                 mAssistants.notifyAssistantActionClicked(
-                        r.sbn, actionIndex, action, generatedByAssistant);
+                        r.getSbn(), actionIndex, action, generatedByAssistant);
             }
         }
 
@@ -1044,7 +1044,7 @@
                         reportSeen(r);
                     }
                     r.setVisibility(true, nv.rank, nv.count);
-                    mAssistants.notifyAssistantVisibilityChangedLocked(r.sbn, true);
+                    mAssistants.notifyAssistantVisibilityChangedLocked(r.getSbn(), true);
                     boolean isHun = (nv.location
                             == NotificationVisibility.NotificationLocation.LOCATION_FIRST_HEADS_UP);
                     // hasBeenVisiblyExpanded must be called after updating the expansion state of
@@ -1063,7 +1063,7 @@
                     NotificationRecord r = mNotificationsByKey.get(nv.key);
                     if (r == null) continue;
                     r.setVisibility(false, nv.rank, nv.count);
-                    mAssistants.notifyAssistantVisibilityChangedLocked(r.sbn, false);
+                    mAssistants.notifyAssistantVisibilityChangedLocked(r.getSbn(), false);
                     nv.recycle();
                 }
             }
@@ -1090,7 +1090,8 @@
                         r.recordExpanded();
                         reportUserInteraction(r);
                     }
-                    mAssistants.notifyAssistantExpansionChangedLocked(r.sbn, userAction, expanded);
+                    mAssistants.notifyAssistantExpansionChangedLocked(
+                            r.getSbn(), userAction, expanded);
                 }
             }
         }
@@ -1106,7 +1107,7 @@
                             .setCategory(MetricsEvent.NOTIFICATION_DIRECT_REPLY_ACTION)
                             .setType(MetricsEvent.TYPE_ACTION));
                     reportUserInteraction(r);
-                    mAssistants.notifyAssistantNotificationDirectReplyLocked(r.sbn);
+                    mAssistants.notifyAssistantNotificationDirectReplyLocked(r.getSbn());
                 }
             }
         }
@@ -1150,7 +1151,7 @@
                     // Treat clicking on a smart reply as a user interaction.
                     reportUserInteraction(r);
                     mAssistants.notifyAssistantSuggestedReplySent(
-                            r.sbn, reply, r.getSuggestionsGeneratedByAssistant());
+                            r.getSbn(), reply, r.getSuggestionsGeneratedByAssistant());
                 }
             }
         }
@@ -1170,7 +1171,7 @@
             synchronized (mNotificationLock) {
                 NotificationRecord r = mNotificationsByKey.get(key);
                 if (r != null) {
-                    final StatusBarNotification n = r.sbn;
+                    final StatusBarNotification n = r.getSbn();
                     final int callingUid = n.getUid();
                     final String pkg = n.getPackageName();
                     applyFlagBubble(r, pkg, callingUid, null /* oldEntry */, isBubble);
@@ -1372,9 +1373,9 @@
                     record = findNotificationByKeyLocked(intent.getStringExtra(EXTRA_KEY));
                 }
                 if (record != null) {
-                    cancelNotification(record.sbn.getUid(), record.sbn.getInitialPid(),
-                            record.sbn.getPackageName(), record.sbn.getTag(),
-                            record.sbn.getId(), 0,
+                    cancelNotification(record.getSbn().getUid(), record.getSbn().getInitialPid(),
+                            record.getSbn().getPackageName(), record.getSbn().getTag(),
+                            record.getSbn().getId(), 0,
                             FLAG_FOREGROUND_SERVICE, true, record.getUserId(),
                             REASON_TIMEOUT, null);
                 }
@@ -1637,7 +1638,7 @@
                 synchronized (mNotificationLock) {
                     NotificationRecord r = mNotificationsByKey.get(bubbleKey);
                     if (r != null) {
-                        final StatusBarNotification n = r.sbn;
+                        final StatusBarNotification n = r.getSbn();
                         final int callingUid = n.getUid();
                         final String pkg = n.getPackageName();
                         applyFlagBubble(r, pkg, callingUid, null /* oldEntry */, isAppForeground);
@@ -1780,7 +1781,7 @@
                 if (mNotificationsByKey.containsKey(posted.getKey())) {
                     count--;
                 }
-                if (posted.sbn.isGroup() && posted.getNotification().isGroupSummary()) {
+                if (posted.getSbn().isGroup() && posted.getNotification().isGroupSummary()) {
                     count--;
                 }
             }
@@ -1802,8 +1803,8 @@
     @VisibleForTesting
     void addNotification(NotificationRecord r) {
         mNotificationList.add(r);
-        mNotificationsByKey.put(r.sbn.getKey(), r);
-        if (r.sbn.isGroup()) {
+        mNotificationsByKey.put(r.getSbn().getKey(), r);
+        if (r.getSbn().isGroup()) {
             mSummaryByGroupKey.put(r.getGroupKey(), r);
         }
     }
@@ -2059,9 +2060,9 @@
                     if (DBG) {
                         Slog.d(TAG, "Reposting " + r.getKey());
                     }
-                    enqueueNotificationInternal(r.sbn.getPackageName(), r.sbn.getOpPkg(),
-                            r.sbn.getUid(), r.sbn.getInitialPid(), r.sbn.getTag(), r.sbn.getId(),
-                            r.sbn.getNotification(), userId);
+                    enqueueNotificationInternal(r.getSbn().getPackageName(), r.getSbn().getOpPkg(),
+                            r.getSbn().getUid(), r.getSbn().getInitialPid(), r.getSbn().getTag(),
+                            r.getSbn().getId(),  r.getSbn().getNotification(), userId);
                 } catch (Exception e) {
                     Slog.e(TAG, "Cannot un-snooze notification", e);
                 }
@@ -2201,7 +2202,7 @@
                 String pkg;
                 synchronized (mNotificationLock) {
                     NotificationRecord r = mNotificationsByKey.get(key);
-                    pkg = r != null && r.sbn != null ? r.sbn.getPackageName() : null;
+                    pkg = r != null && r.getSbn() != null ? r.getSbn().getPackageName() : null;
                 }
                 boolean isAppForeground = pkg != null
                         && mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
@@ -2209,7 +2210,7 @@
                     NotificationRecord r = mNotificationsByKey.get(key);
                     if (r == null) return;
                     updateAutobundledSummaryFlags(r.getUser().getIdentifier(),
-                            r.sbn.getPackageName(), needsOngoingFlag, isAppForeground);
+                            r.getSbn().getPackageName(), needsOngoingFlag, isAppForeground);
                 }
             }
         });
@@ -2280,7 +2281,8 @@
         final long updatedSuppressedEffects = calculateSuppressedEffects();
         if (updatedSuppressedEffects == mZenModeHelper.getSuppressedEffects()) return;
         final List<ComponentName> suppressors = getSuppressors();
-        ZenLog.traceEffectsSuppressorChanged(mEffectsSuppressors, suppressors, updatedSuppressedEffects);
+        ZenLog.traceEffectsSuppressorChanged(
+                mEffectsSuppressors, suppressors, updatedSuppressedEffects);
         mEffectsSuppressors = suppressors;
         mZenModeHelper.setSuppressedEffects(updatedSuppressedEffects);
         sendRegisteredOnlyBroadcast(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED);
@@ -2486,6 +2488,18 @@
         scheduleInterruptionFilterChanged(interruptionFilter);
     }
 
+    int correctCategory(int requestedCategoryList, int categoryType,
+            int currentCategoryList) {
+        if ((requestedCategoryList & categoryType) != 0
+                && (currentCategoryList & categoryType) == 0) {
+            requestedCategoryList &= ~categoryType;
+        } else if ((requestedCategoryList & categoryType) == 0
+                && (currentCategoryList & categoryType) != 0){
+            requestedCategoryList |= categoryType;
+        }
+        return requestedCategoryList;
+    }
+
     @VisibleForTesting
     INotificationManager getBinderService() {
         return INotificationManager.Stub.asInterface(mService);
@@ -2498,8 +2512,8 @@
     @GuardedBy("mNotificationLock")
     protected void reportSeen(NotificationRecord r) {
         if (!r.isProxied()) {
-            mAppUsageStats.reportEvent(r.sbn.getPackageName(),
-                    getRealUserId(r.sbn.getUserId()),
+            mAppUsageStats.reportEvent(r.getSbn().getPackageName(),
+                    getRealUserId(r.getSbn().getUserId()),
                     UsageEvents.Event.NOTIFICATION_SEEN);
         }
     }
@@ -2574,18 +2588,18 @@
     @GuardedBy("mNotificationLock")
     protected void maybeRecordInterruptionLocked(NotificationRecord r) {
         if (r.isInterruptive() && !r.hasRecordedInterruption()) {
-            mAppUsageStats.reportInterruptiveNotification(r.sbn.getPackageName(),
+            mAppUsageStats.reportInterruptiveNotification(r.getSbn().getPackageName(),
                     r.getChannel().getId(),
-                    getRealUserId(r.sbn.getUserId()));
+                    getRealUserId(r.getSbn().getUserId()));
             mHistoryManager.addNotification(new HistoricalNotification.Builder()
-                    .setPackage(r.sbn.getPackageName())
-                    .setUid(r.sbn.getUid())
+                    .setPackage(r.getSbn().getPackageName())
+                    .setUid(r.getSbn().getUid())
                     .setChannelId(r.getChannel().getId())
                     .setChannelName(r.getChannel().getName().toString())
                     .setPostedTimeMs(System.currentTimeMillis())
                     .setTitle(getHistoryTitle(r.getNotification()))
                     .setText(getHistoryText(
-                            r.sbn.getPackageContext(getContext()), r.getNotification()))
+                            r.getSbn().getPackageContext(getContext()), r.getNotification()))
                     .setIcon(r.getNotification().getSmallIcon())
                     .build());
             r.setRecordedInterruption(true);
@@ -2632,8 +2646,8 @@
      * @param r notification record
      */
     protected void reportUserInteraction(NotificationRecord r) {
-        mAppUsageStats.reportEvent(r.sbn.getPackageName(),
-                getRealUserId(r.sbn.getUserId()),
+        mAppUsageStats.reportEvent(r.getSbn().getPackageName(),
+                getRealUserId(r.getSbn().getUserId()),
                 UsageEvents.Event.USER_INTERACTION);
     }
 
@@ -2667,18 +2681,24 @@
         @Override
         public void enqueueTextToast(String pkg, IBinder token, CharSequence text, int duration,
                 int displayId, @Nullable ITransientNotificationCallback callback) {
-            enqueueToast(pkg, token, text, null, duration, displayId, callback);
+            enqueueToast(pkg, token, text, null, duration, displayId, callback, false);
         }
 
         @Override
         public void enqueueToast(String pkg, IBinder token, ITransientNotification callback,
                 int duration, int displayId) {
-            enqueueToast(pkg, token, null, callback, duration, displayId, null);
+            enqueueToast(pkg, token, null, callback, duration, displayId, null, true);
+        }
+
+        @Override
+        public void enqueueTextOrCustomToast(String pkg, IBinder token,
+                ITransientNotification callback, int duration, int displayId, boolean isCustom) {
+            enqueueToast(pkg, token, null, callback, duration, displayId, null, isCustom);
         }
 
         private void enqueueToast(String pkg, IBinder token, @Nullable CharSequence text,
                 @Nullable ITransientNotification callback, int duration, int displayId,
-                @Nullable ITransientNotificationCallback textCallback) {
+                @Nullable ITransientNotificationCallback textCallback, boolean isCustom) {
             if (DBG) {
                 Slog.i(TAG, "enqueueToast pkg=" + pkg + " token=" + token
                         + " duration=" + duration + " displayId=" + displayId);
@@ -2716,7 +2736,7 @@
                 return;
             }
 
-            if (callback != null && !appIsForeground && !isSystemToast) {
+            if (callback != null && !appIsForeground && !isSystemToast && isCustom) {
                 boolean block;
                 long id = Binder.clearCallingIdentity();
                 try {
@@ -3509,7 +3529,7 @@
                     tmp = new StatusBarNotification[mNotificationList.size()];
                     final int N = mNotificationList.size();
                     for (int i=0; i<N; i++) {
-                        tmp[i] = mNotificationList.get(i).sbn;
+                        tmp[i] = mNotificationList.get(i).getSbn();
                     }
                 }
             }
@@ -3541,13 +3561,13 @@
                 final int N = mNotificationList.size();
                 for (int i = 0; i < N; i++) {
                     StatusBarNotification sbn = sanitizeSbn(pkg, userId,
-                            mNotificationList.get(i).sbn);
+                            mNotificationList.get(i).getSbn());
                     if (sbn != null) {
                         map.put(sbn.getKey(), sbn);
                     }
                 }
                 for(NotificationRecord snoozed: mSnoozeHelper.getSnoozed(userId, pkg)) {
-                    StatusBarNotification sbn = sanitizeSbn(pkg, userId, snoozed.sbn);
+                    StatusBarNotification sbn = sanitizeSbn(pkg, userId, snoozed.getSbn());
                     if (sbn != null) {
                         map.put(sbn.getKey(), sbn);
                     }
@@ -3555,7 +3575,7 @@
                 final int M = mEnqueuedNotifications.size();
                 for (int i = 0; i < M; i++) {
                     StatusBarNotification sbn = sanitizeSbn(pkg, userId,
-                            mEnqueuedNotifications.get(i).sbn);
+                            mEnqueuedNotifications.get(i).getSbn());
                     if (sbn != null) {
                         map.put(sbn.getKey(), sbn); // pending update overwrites existing post here
                     }
@@ -3673,15 +3693,15 @@
                         for (int i = 0; i < N; i++) {
                             NotificationRecord r = mNotificationsByKey.get(keys[i]);
                             if (r == null) continue;
-                            final int userId = r.sbn.getUserId();
+                            final int userId = r.getSbn().getUserId();
                             if (userId != info.userid && userId != UserHandle.USER_ALL &&
                                     !mUserProfiles.isCurrentProfile(userId)) {
                                 throw new SecurityException("Disallowed call from listener: "
                                         + info.service);
                             }
                             cancelNotificationFromListenerLocked(info, callingUid, callingPid,
-                                    r.sbn.getPackageName(), r.sbn.getTag(), r.sbn.getId(),
-                                    userId);
+                                    r.getSbn().getPackageName(), r.getSbn().getTag(),
+                                    r.getSbn().getId(), userId);
                         }
                     } else {
                         cancelAllLocked(callingUid, callingPid, info.userid,
@@ -3741,7 +3761,7 @@
                     for (int i = 0; i < n; i++) {
                         NotificationRecord r = mNotificationsByKey.get(keys[i]);
                         if (r == null) continue;
-                        final int userId = r.sbn.getUserId();
+                        final int userId = r.getSbn().getUserId();
                         if (userId != info.userid && userId != UserHandle.USER_ALL
                                 && !mUserProfiles.isCurrentProfile(userId)) {
                             throw new SecurityException("Disallowed call from listener: "
@@ -3891,7 +3911,7 @@
                             ? mNotificationsByKey.get(keys[i])
                             : mNotificationList.get(i);
                     if (r == null) continue;
-                    StatusBarNotification sbn = r.sbn;
+                    StatusBarNotification sbn = r.getSbn();
                     if (!isVisibleToListener(sbn, info)) continue;
                     StatusBarNotification sbnToSend =
                             (trim == TRIM_FULL) ? sbn : sbn.cloneLight();
@@ -3921,7 +3941,7 @@
                 for (int i=0; i < N; i++) {
                     final NotificationRecord r = snoozedRecords.get(i);
                     if (r == null) continue;
-                    StatusBarNotification sbn = r.sbn;
+                    StatusBarNotification sbn = r.getSbn();
                     if (!isVisibleToListener(sbn, info)) continue;
                     StatusBarNotification sbnToSend =
                             (trim == TRIM_FULL) ? sbn : sbn.cloneLight();
@@ -4096,7 +4116,8 @@
             Objects.requireNonNull(packageName, "Package name is null");
             enforceSystemOrSystemUI("removeAutomaticZenRules");
 
-            return mZenModeHelper.removeAutomaticZenRules(packageName, "removeAutomaticZenRules");
+            return mZenModeHelper.removeAutomaticZenRules(packageName,
+                    packageName + "|removeAutomaticZenRules");
         }
 
         @Override
@@ -4411,11 +4432,20 @@
                             policy.priorityCallSenders, policy.priorityMessageSenders,
                             policy.suppressedVisualEffects);
                 }
+                if (applicationInfo.targetSdkVersion < Build.VERSION_CODES.R) {
+                    int priorityCategories = correctCategory(policy.priorityCategories,
+                            Policy.PRIORITY_CATEGORY_CONVERSATIONS,
+                            currPolicy.priorityCategories);
+
+                    policy = new Policy(priorityCategories,
+                            policy.priorityCallSenders, policy.priorityMessageSenders,
+                            policy.suppressedVisualEffects, currPolicy.priorityConversationSenders);
+                }
                 int newVisualEffects = calculateSuppressedVisualEffects(
                             policy, currPolicy, applicationInfo.targetSdkVersion);
                 policy = new Policy(policy.priorityCategories,
                         policy.priorityCallSenders, policy.priorityMessageSenders,
-                        newVisualEffects);
+                        newVisualEffects, policy.priorityConversationSenders);
                 ZenLog.traceSetNotificationPolicy(pkg, applicationInfo.targetSdkVersion, policy);
                 mZenModeHelper.setNotificationPolicy(policy);
             } catch (RemoteException e) {
@@ -4424,6 +4454,8 @@
             }
         }
 
+
+
         @Override
         public List<String> getEnabledNotificationListenerPackages() {
             checkCallerIsSystem();
@@ -4637,8 +4669,8 @@
             Objects.requireNonNull(user);
             verifyPrivilegedListener(token, user, true);
 
-            return mPreferencesHelper.getNotificationChannels(pkg, getUidForPackageAndUser(pkg, user),
-                    false /* includeDeleted */);
+            return mPreferencesHelper.getNotificationChannels(pkg,
+                    getUidForPackageAndUser(pkg, user), false /* includeDeleted */);
         }
 
         @Override
@@ -4830,7 +4862,7 @@
         if (r == null) {
             return;
         }
-        if (r.sbn.getOverrideGroupKey() == null) {
+        if (r.getSbn().getOverrideGroupKey() == null) {
             addAutoGroupAdjustment(r, GroupHelper.AUTOGROUP_KEY);
             EventLogTags.writeNotificationAutogrouped(key);
             mRankingHandler.requestSort();
@@ -4843,7 +4875,7 @@
         if (r == null) {
             return;
         }
-        if (r.sbn.getOverrideGroupKey() != null) {
+        if (r.getSbn().getOverrideGroupKey() != null) {
             addAutoGroupAdjustment(r, null);
             EventLogTags.writeNotificationUnautogrouped(key);
             mRankingHandler.requestSort();
@@ -4853,8 +4885,8 @@
     private void addAutoGroupAdjustment(NotificationRecord r, String overrideGroupKey) {
         Bundle signals = new Bundle();
         signals.putString(Adjustment.KEY_GROUP_KEY, overrideGroupKey);
-        Adjustment adjustment =
-                new Adjustment(r.sbn.getPackageName(), r.getKey(), signals, "", r.sbn.getUserId());
+        Adjustment adjustment = new Adjustment(r.getSbn().getPackageName(), r.getKey(), signals, "",
+                r.getSbn().getUserId());
         r.addAdjustment(adjustment);
     }
 
@@ -4890,7 +4922,7 @@
                 // adjustment will post a summary if needed.
                 return;
             }
-            final StatusBarNotification adjustedSbn = notificationRecord.sbn;
+            final StatusBarNotification adjustedSbn = notificationRecord.getSbn();
             userId = adjustedSbn.getUser().getIdentifier();
             ArrayMap<String, String> summaries = mAutobundledSummaries.get(userId);
             if (summaries == null) {
@@ -4938,7 +4970,8 @@
             }
         }
         if (summaryRecord != null && checkDisqualifyingFeatures(userId, MY_UID,
-                summaryRecord.sbn.getId(), summaryRecord.sbn.getTag(), summaryRecord, true)) {
+                summaryRecord.getSbn().getId(), summaryRecord.getSbn().getTag(), summaryRecord,
+                true)) {
             mHandler.post(new EnqueueNotificationRunnable(userId, summaryRecord, isAppForeground));
         }
     }
@@ -4999,14 +5032,14 @@
             int N = mNotificationList.size();
             for (int i = 0; i < N; i++) {
                 final NotificationRecord nr = mNotificationList.get(i);
-                if (filter.filtered && !filter.matches(nr.sbn)) continue;
+                if (filter.filtered && !filter.matches(nr.getSbn())) continue;
                 nr.dump(proto, NotificationServiceDumpProto.RECORDS, filter.redact,
                         NotificationRecordProto.POSTED);
             }
             N = mEnqueuedNotifications.size();
             for (int i = 0; i < N; i++) {
                 final NotificationRecord nr = mEnqueuedNotifications.get(i);
-                if (filter.filtered && !filter.matches(nr.sbn)) continue;
+                if (filter.filtered && !filter.matches(nr.getSbn())) continue;
                 nr.dump(proto, NotificationServiceDumpProto.RECORDS, filter.redact,
                         NotificationRecordProto.ENQUEUED);
             }
@@ -5014,7 +5047,7 @@
             N = snoozed.size();
             for (int i = 0; i < N; i++) {
                 final NotificationRecord nr = snoozed.get(i);
-                if (filter.filtered && !filter.matches(nr.sbn)) continue;
+                if (filter.filtered && !filter.matches(nr.getSbn())) continue;
                 nr.dump(proto, NotificationServiceDumpProto.RECORDS, filter.redact,
                         NotificationRecordProto.SNOOZED);
             }
@@ -5075,7 +5108,7 @@
                 pw.println("  Notification List:");
                 for (int i = 0; i < N; i++) {
                     final NotificationRecord nr = mNotificationList.get(i);
-                    if (filter.filtered && !filter.matches(nr.sbn)) continue;
+                    if (filter.filtered && !filter.matches(nr.getSbn())) continue;
                     nr.dump(pw, "    ", getContext(), filter.redact);
                 }
                 pw.println("  ");
@@ -5155,7 +5188,7 @@
                         pw.println("  Enqueued Notification List:");
                         for (int i = 0; i < N; i++) {
                             final NotificationRecord nr = mEnqueuedNotifications.get(i);
-                            if (filter.filtered && !filter.matches(nr.sbn)) continue;
+                            if (filter.filtered && !filter.matches(nr.getSbn())) continue;
                             nr.dump(pw, "    ", getContext(), filter.redact);
                         }
                         pw.println("  ");
@@ -5281,7 +5314,7 @@
             if (r == null) {
                 return;
             }
-            StatusBarNotification sbn = r.sbn;
+            StatusBarNotification sbn = r.getSbn();
             // NoMan adds flags FLAG_NO_CLEAR and FLAG_ONGOING_EVENT when it sees
             // FLAG_FOREGROUND_SERVICE. Hence it's not enough to remove
             // FLAG_FOREGROUND_SERVICE, we have to revert to the flags we received
@@ -5312,7 +5345,7 @@
                 // Look for the notification, searching both the posted and enqueued lists.
                 NotificationRecord r = findNotificationLocked(pkg, tag, id, userId);
                 if (r != null) {
-                    if (!Objects.equals(opPkg, r.sbn.getOpPkg())) {
+                    if (!Objects.equals(opPkg, r.getSbn().getOpPkg())) {
                         throw new SecurityException(opPkg + " does not have permission to "
                                 + "cancel a notification they did not post " + tag + " " + id);
                     }
@@ -5360,8 +5393,8 @@
         try {
             fixNotification(notification, pkg, tag, id, userId);
 
-        } catch (NameNotFoundException e) {
-            Slog.e(TAG, "Cannot create a context for sending app", e);
+        } catch (Exception e) {
+            Slog.e(TAG, "Cannot fix notification", e);
             return;
         }
 
@@ -5442,7 +5475,7 @@
         }
 
         if (!checkDisqualifyingFeatures(userId, notificationUid, id, tag, r,
-                r.sbn.getOverrideGroupKey() != null)) {
+                r.getSbn().getOverrideGroupKey() != null)) {
             return;
         }
 
@@ -5586,12 +5619,12 @@
             if (shortcutId != null) {
                 // Must track shortcut based bubbles in case the shortcut is removed
                 HashMap<String, String> packageBubbles = mActiveShortcutBubbles.get(
-                        r.sbn.getPackageName());
+                        r.getSbn().getPackageName());
                 if (packageBubbles == null) {
                     packageBubbles = new HashMap<>();
                 }
                 packageBubbles.put(shortcutId, r.getKey());
-                mActiveShortcutBubbles.put(r.sbn.getPackageName(), packageBubbles);
+                mActiveShortcutBubbles.put(r.getSbn().getPackageName(), packageBubbles);
                 if (!mLauncherAppsCallbackRegistered) {
                     mLauncherAppsService.registerCallback(mLauncherAppsCallback, mHandler);
                     mLauncherAppsCallbackRegistered = true;
@@ -5602,12 +5635,12 @@
             if (shortcutId != null) {
                 // No longer track shortcut
                 HashMap<String, String> packageBubbles = mActiveShortcutBubbles.get(
-                        r.sbn.getPackageName());
+                        r.getSbn().getPackageName());
                 if (packageBubbles != null) {
                     packageBubbles.remove(shortcutId);
                 }
                 if (packageBubbles != null && packageBubbles.isEmpty()) {
-                    mActiveShortcutBubbles.remove(r.sbn.getPackageName());
+                    mActiveShortcutBubbles.remove(r.getSbn().getPackageName());
                 }
                 if (mLauncherAppsCallbackRegistered && mActiveShortcutBubbles.isEmpty()) {
                     mLauncherAppsService.unregisterCallback(mLauncherAppsCallback);
@@ -5853,7 +5886,7 @@
      */
     private boolean checkDisqualifyingFeatures(int userId, int uid, int id, String tag,
             NotificationRecord r, boolean isAutogroup) {
-        final String pkg = r.sbn.getPackageName();
+        final String pkg = r.getSbn().getPackageName();
         final boolean isSystemNotification =
                 isUidSystemOrPhone(uid) || ("android".equals(pkg));
         final boolean isNotificationFromListener = mListeners.isListenerPackage(pkg);
@@ -5863,7 +5896,7 @@
         if (!isSystemNotification && !isNotificationFromListener) {
             synchronized (mNotificationLock) {
                 final int callingUid = Binder.getCallingUid();
-                if (mNotificationsByKey.get(r.sbn.getKey()) == null
+                if (mNotificationsByKey.get(r.getSbn().getKey()) == null
                         && isCallerInstantApp(callingUid, userId)) {
                     // Ephemeral apps have some special constraints for notifications.
                     // They are not allowed to create new notifications however they are allowed to
@@ -5874,7 +5907,7 @@
                 }
 
                 // rate limit updates that aren't completed progress notifications
-                if (mNotificationsByKey.get(r.sbn.getKey()) != null
+                if (mNotificationsByKey.get(r.getSbn().getKey()) != null
                         && !r.getNotification().hasCompletedProgress()
                         && !isAutogroup) {
 
@@ -5884,7 +5917,7 @@
                         final long now = SystemClock.elapsedRealtime();
                         if ((now - mLastOverRateLogTime) > MIN_PACKAGE_OVERRATE_LOG_INTERVAL) {
                             Slog.e(TAG, "Package enqueue rate is " + appEnqueueRate
-                                    + ". Shedding " + r.sbn.getKey() + ". package=" + pkg);
+                                    + ". Shedding " + r.getSbn().getKey() + ". package=" + pkg);
                             mLastOverRateLogTime = now;
                         }
                         return false;
@@ -5933,10 +5966,10 @@
         final int N = mNotificationList.size();
         for (int i = 0; i < N; i++) {
             final NotificationRecord existing = mNotificationList.get(i);
-            if (existing.sbn.getPackageName().equals(pkg)
-                    && existing.sbn.getUserId() == userId) {
-                if (existing.sbn.getId() == excludedId
-                        && TextUtils.equals(existing.sbn.getTag(), excludedTag)) {
+            if (existing.getSbn().getPackageName().equals(pkg)
+                    && existing.getSbn().getUserId() == userId) {
+                if (existing.getSbn().getId() == excludedId
+                        && TextUtils.equals(existing.getSbn().getTag(), excludedTag)) {
                     continue;
                 }
                 count++;
@@ -5945,8 +5978,8 @@
         final int M = mEnqueuedNotifications.size();
         for (int i = 0; i < M; i++) {
             final NotificationRecord existing = mEnqueuedNotifications.get(i);
-            if (existing.sbn.getPackageName().equals(pkg)
-                    && existing.sbn.getUserId() == userId) {
+            if (existing.getSbn().getPackageName().equals(pkg)
+                    && existing.getSbn().getUserId() == userId) {
                 count++;
             }
         }
@@ -5965,8 +5998,8 @@
     }
 
     private boolean isBlocked(NotificationRecord r) {
-        final String pkg = r.sbn.getPackageName();
-        final int callingUid = r.sbn.getUid();
+        final String pkg = r.getSbn().getPackageName();
+        final int callingUid = r.getSbn().getUid();
         return mPreferencesHelper.isGroupBlocked(pkg, callingUid, r.getChannel().getGroup())
                 || mPreferencesHelper.getImportance(pkg, callingUid)
                 == NotificationManager.IMPORTANCE_NONE
@@ -5996,10 +6029,11 @@
 
         @GuardedBy("mNotificationLock")
         void snoozeLocked(NotificationRecord r) {
-            if (r.sbn.isGroup()) {
+            if (r.getSbn().isGroup()) {
                 final List<NotificationRecord> groupNotifications =
                         findCurrentAndSnoozedGroupNotificationsLocked(
-                        r.sbn.getPackageName(), r.sbn.getGroupKey(), r.sbn.getUserId());
+                        r.getSbn().getPackageName(),
+                                r.getSbn().getGroupKey(), r.getSbn().getUserId());
                 if (r.getNotification().isGroupSummary()) {
                     // snooze all children
                     for (int i = 0; i < groupNotifications.size(); i++) {
@@ -6010,7 +6044,7 @@
                 } else {
                     // if there is a valid summary for this group, and we are snoozing the only
                     // child, also snooze the summary
-                    if (mSummaryByGroupKey.containsKey(r.sbn.getGroupKey())) {
+                    if (mSummaryByGroupKey.containsKey(r.getSbn().getGroupKey())) {
                         if (groupNotifications.size() == 2) {
                             // snooze summary and the one child
                             for (int i = 0; i < groupNotifications.size(); i++) {
@@ -6041,7 +6075,7 @@
             cancelNotificationLocked(r, false, REASON_SNOOZED, wasPosted, null);
             updateLightsLocked();
             if (mSnoozeCriterionId != null) {
-                mAssistants.notifyAssistantSnoozedLocked(r.sbn, mSnoozeCriterionId);
+                mAssistants.notifyAssistantSnoozedLocked(r.getSbn(), mSnoozeCriterionId);
                 mSnoozeHelper.snooze(r, mSnoozeCriterionId);
             } else {
                 mSnoozeHelper.snooze(r, mDuration);
@@ -6172,10 +6206,10 @@
                 final Long snoozeAt =
                         mSnoozeHelper.getSnoozeTimeForUnpostedNotification(
                                 r.getUser().getIdentifier(),
-                                r.sbn.getPackageName(), r.sbn.getKey());
+                                r.getSbn().getPackageName(), r.getSbn().getKey());
                 final long currentTime = System.currentTimeMillis();
                 if (snoozeAt.longValue() > currentTime) {
-                    (new SnoozeNotificationRunnable(r.sbn.getKey(),
+                    (new SnoozeNotificationRunnable(r.getSbn().getKey(),
                             snoozeAt.longValue() - currentTime, null)).snoozeLocked(r);
                     return;
                 }
@@ -6183,9 +6217,9 @@
                 final String contextId =
                         mSnoozeHelper.getSnoozeContextForUnpostedNotification(
                                 r.getUser().getIdentifier(),
-                                r.sbn.getPackageName(), r.sbn.getKey());
+                                r.getSbn().getPackageName(), r.getSbn().getKey());
                 if (contextId != null) {
-                    (new SnoozeNotificationRunnable(r.sbn.getKey(),
+                    (new SnoozeNotificationRunnable(r.getSbn().getKey(),
                             0, contextId)).snoozeLocked(r);
                     return;
                 }
@@ -6193,7 +6227,7 @@
                 mEnqueuedNotifications.add(r);
                 scheduleTimeoutLocked(r);
 
-                final StatusBarNotification n = r.sbn;
+                final StatusBarNotification n = r.getSbn();
                 if (DBG) Slog.d(TAG, "EnqueueNotificationRunnable.run for: " + n.getKey());
                 NotificationRecord old = mNotificationsByKey.get(n.getKey());
                 if (old != null) {
@@ -6291,20 +6325,20 @@
                     }
 
                     final boolean isPackageSuspended =
-                            isPackagePausedOrSuspended(r.sbn.getPackageName(), r.getUid());
+                            isPackagePausedOrSuspended(r.getSbn().getPackageName(), r.getUid());
                     r.setHidden(isPackageSuspended);
                     if (isPackageSuspended) {
                         mUsageStats.registerSuspendedByAdmin(r);
                     }
                     NotificationRecord old = mNotificationsByKey.get(key);
-                    final StatusBarNotification n = r.sbn;
+                    final StatusBarNotification n = r.getSbn();
                     final Notification notification = n.getNotification();
 
                     // Make sure the SBN has an instance ID for statsd logging.
-                    if (old == null || old.sbn.getInstanceId() == null) {
+                    if (old == null || old.getSbn().getInstanceId() == null) {
                         n.setInstanceId(mNotificationInstanceIdSequence.newInstanceId());
                     } else {
-                        n.setInstanceId(old.sbn.getInstanceId());
+                        n.setInstanceId(old.getSbn().getInstanceId());
                     }
 
                     int index = indexOfNotificationLocked(n.getKey());
@@ -6344,7 +6378,7 @@
                     }
 
                     if (notification.getSmallIcon() != null) {
-                        StatusBarNotification oldSbn = (old != null) ? old.sbn : null;
+                        StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null;
                         mListeners.notifyPostedLocked(r, old);
                         if ((oldSbn == null || !Objects.equals(oldSbn.getGroup(), n.getGroup()))
                                 && !isCritical(r)) {
@@ -6358,7 +6392,7 @@
                         } else if (oldSbn != null) {
                             final NotificationRecord finalRecord = r;
                             mHandler.post(() -> mGroupHelper.onNotificationUpdated(
-                                    finalRecord.sbn, hasAutoGroupSummaryLocked(n)));
+                                    finalRecord.getSbn(), hasAutoGroupSummaryLocked(n)));
                         }
                     } else {
                         Slog.e(TAG, "Not posting notification without small icon: " + notification);
@@ -6405,7 +6439,7 @@
     @VisibleForTesting
     protected boolean isVisuallyInterruptive(NotificationRecord old, NotificationRecord r) {
         // Ignore summary updates because we don't display most of the information.
-        if (r.sbn.isGroup() && r.sbn.getNotification().isGroupSummary()) {
+        if (r.getSbn().isGroup() && r.getSbn().getNotification().isGroupSummary()) {
             if (DEBUG_INTERRUPTIVENESS) {
                 Slog.v(TAG, "INTERRUPTIVENESS: "
                         +  r.getKey() + " is not interruptive: summary");
@@ -6429,8 +6463,8 @@
             return false;
         }
 
-        Notification oldN = old.sbn.getNotification();
-        Notification newN = r.sbn.getNotification();
+        Notification oldN = old.getSbn().getNotification();
+        Notification newN = r.getSbn().getNotification();
         if (oldN.extras == null || newN.extras == null) {
             if (DEBUG_INTERRUPTIVENESS) {
                 Slog.v(TAG, "INTERRUPTIVENESS: "
@@ -6441,7 +6475,7 @@
 
         // Ignore visual interruptions from foreground services because users
         // consider them one 'session'. Count them for everything else.
-        if ((r.sbn.getNotification().flags & FLAG_FOREGROUND_SERVICE) != 0) {
+        if ((r.getSbn().getNotification().flags & FLAG_FOREGROUND_SERVICE) != 0) {
             if (DEBUG_INTERRUPTIVENESS) {
                 Slog.v(TAG, "INTERRUPTIVENESS: "
                         +  r.getKey() + " is not interruptive: foreground service");
@@ -6555,7 +6589,7 @@
     @GuardedBy("mNotificationLock")
     private void handleGroupedNotificationLocked(NotificationRecord r, NotificationRecord old,
             int callingUid, int callingPid) {
-        StatusBarNotification sbn = r.sbn;
+        StatusBarNotification sbn = r.getSbn();
         Notification n = sbn.getNotification();
         if (n.isGroupSummary() && !sbn.isAppGroup())  {
             // notifications without a group shouldn't be a summary, otherwise autobundling can
@@ -6566,8 +6600,8 @@
         String group = sbn.getGroupKey();
         boolean isSummary = n.isGroupSummary();
 
-        Notification oldN = old != null ? old.sbn.getNotification() : null;
-        String oldGroup = old != null ? old.sbn.getGroupKey() : null;
+        Notification oldN = old != null ? old.getSbn().getNotification() : null;
+        String oldGroup = old != null ? old.getSbn().getGroupKey() : null;
         boolean oldIsSummary = old != null && oldN.isGroupSummary();
 
         if (oldIsSummary) {
@@ -6624,7 +6658,7 @@
         boolean beep = false;
         boolean blink = false;
 
-        final Notification notification = record.sbn.getNotification();
+        final Notification notification = record.getSbn().getNotification();
         final String key = record.getKey();
 
         // Should this notification make noise, vibe, or use the LED?
@@ -6642,7 +6676,7 @@
         // If the notification will appear in the status bar, it should send an accessibility
         // event
         if (!record.isUpdate && record.getImportance() > IMPORTANCE_MIN) {
-            sendAccessibilityEvent(notification, record.sbn.getPackageName());
+            sendAccessibilityEvent(notification, record.getSbn().getPackageName());
             sentAccessibilityEvent = true;
         }
 
@@ -6664,7 +6698,7 @@
                 boolean hasAudibleAlert = hasValidSound || hasValidVibrate;
                 if (hasAudibleAlert && !shouldMuteNotificationLocked(record)) {
                     if (!sentAccessibilityEvent) {
-                        sendAccessibilityEvent(notification, record.sbn.getPackageName());
+                        sendAccessibilityEvent(notification, record.getSbn().getPackageName());
                         sentAccessibilityEvent = true;
                     }
                     if (DBG) Slog.v(TAG, "Interrupting!");
@@ -6719,7 +6753,7 @@
         final int buzzBeepBlink = (buzz ? 1 : 0) | (beep ? 2 : 0) | (blink ? 4 : 0);
         if (buzzBeepBlink > 0) {
             // Ignore summary updates because we don't display most of the information.
-            if (record.sbn.isGroup() && record.sbn.getNotification().isGroupSummary()) {
+            if (record.getSbn().isGroup() && record.getSbn().getNotification().isGroupSummary()) {
                 if (DEBUG_INTERRUPTIVENESS) {
                     Slog.v(TAG, "INTERRUPTIVENESS: "
                             + record.getKey() + " is not interruptive: summary");
@@ -6774,7 +6808,7 @@
             return false;
         }
         // Suppressed because another notification in its group handles alerting
-        if (record.sbn.isGroup() && record.getNotification().suppressAlertingDueToGrouping()) {
+        if (record.getSbn().isGroup() && record.getNotification().suppressAlertingDueToGrouping()) {
             return false;
         }
         // not if in call or the screen's on
@@ -6806,14 +6840,14 @@
         }
 
         // Suppressed because another notification in its group handles alerting
-        if (record.sbn.isGroup()) {
+        if (record.getSbn().isGroup()) {
             if (notification.suppressAlertingDueToGrouping()) {
                 return true;
             }
         }
 
         // Suppressed for being too recently noisy
-        final String pkg = record.sbn.getPackageName();
+        final String pkg = record.getSbn().getPackageName();
         if (mUsageStats.isAlertRateLimited(pkg)) {
             Slog.e(TAG, "Muting recently noisy " + record.getKey());
             return true;
@@ -6854,7 +6888,7 @@
                 if (player != null) {
                     if (DBG) Slog.v(TAG, "Playing sound " + soundUri
                             + " with attributes " + record.getAudioAttributes());
-                    player.playAsync(soundUri, record.sbn.getUser(), looping,
+                    player.playAsync(soundUri, record.getSbn().getUser(), looping,
                             record.getAudioAttributes());
                     return true;
                 }
@@ -6897,15 +6931,16 @@
                     // so need to check the notification still valide for vibrate.
                     synchronized (mNotificationLock) {
                         if (mNotificationsByKey.get(record.getKey()) != null) {
-                            mVibrator.vibrate(record.sbn.getUid(), record.sbn.getOpPkg(),
+                            mVibrator.vibrate(record.getSbn().getUid(), record.getSbn().getOpPkg(),
                                     effect, "Notification (delayed)", record.getAudioAttributes());
                         } else {
-                            Slog.e(TAG, "No vibration for canceled notification : " + record.getKey());
+                            Slog.e(TAG, "No vibration for canceled notification : "
+                                    + record.getKey());
                         }
                     }
                 }).start();
             } else {
-                mVibrator.vibrate(record.sbn.getUid(), record.sbn.getPackageName(),
+                mVibrator.vibrate(record.getSbn().getUid(), record.getSbn().getPackageName(),
                         effect, "Notification", record.getAudioAttributes());
             }
             return true;
@@ -7388,7 +7423,7 @@
         if ((recordInList = findNotificationByListLocked(mNotificationList, r.getKey()))
                 != null) {
             mNotificationList.remove(recordInList);
-            mNotificationsByKey.remove(recordInList.sbn.getKey());
+            mNotificationsByKey.remove(recordInList.getSbn().getKey());
             wasPosted = true;
         }
         while ((recordInList = findNotificationByListLocked(mEnqueuedNotifications, r.getKey()))
@@ -7429,7 +7464,7 @@
                 } catch (PendingIntent.CanceledException ex) {
                     // do nothing - there's no relevant way to recover, and
                     //     no reason to let this propagate
-                    Slog.w(TAG, "canceled PendingIntent for " + r.sbn.getPackageName(), ex);
+                    Slog.w(TAG, "canceled PendingIntent for " + r.getSbn().getPackageName(), ex);
                 }
             }
         }
@@ -7445,7 +7480,7 @@
                 mHandler.post(new Runnable() {
                     @Override
                     public void run() {
-                        mGroupHelper.onNotificationRemoved(r.sbn);
+                        mGroupHelper.onNotificationRemoved(r.getSbn());
                     }
                 });
             }
@@ -7501,13 +7536,15 @@
         if (groupSummary != null && groupSummary.getKey().equals(canceledKey)) {
             mSummaryByGroupKey.remove(groupKey);
         }
-        final ArrayMap<String, String> summaries = mAutobundledSummaries.get(r.sbn.getUserId());
-        if (summaries != null && r.sbn.getKey().equals(summaries.get(r.sbn.getPackageName()))) {
-            summaries.remove(r.sbn.getPackageName());
+        final ArrayMap<String, String> summaries =
+                mAutobundledSummaries.get(r.getSbn().getUserId());
+        if (summaries != null && r.getSbn().getKey().equals(
+                summaries.get(r.getSbn().getPackageName()))) {
+            summaries.remove(r.getSbn().getPackageName());
         }
 
         // Save it for users of getHistoricalNotifications()
-        mArchive.record(r.sbn);
+        mArchive.record(r.getSbn());
 
         final long now = System.currentTimeMillis();
         final LogMaker logMaker = r.getItemLogMaker()
@@ -7563,7 +7600,7 @@
             for (int i = 0; i < newUris.size(); i++) {
                 final Uri uri = newUris.valueAt(i);
                 if (oldUris == null || !oldUris.contains(uri)) {
-                    if (DBG) Slog.d(TAG, key + ": granting " + uri);
+                    Slog.d(TAG, key + ": granting " + uri);
                     grantUriPermission(permissionOwner, uri, newRecord.getUid(), targetPkg,
                             targetUserId);
                 }
@@ -7600,6 +7637,8 @@
                     targetUserId);
         } catch (RemoteException ignored) {
             // Ignored because we're in same process
+        } catch (SecurityException e) {
+            Slog.e(TAG, "Cannot grant uri access; " + sourceUid + " does not own " + uri);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -7756,7 +7795,7 @@
             if (!flagChecker.apply(r.getFlags())) {
                 continue;
             }
-            if (pkg != null && !r.sbn.getPackageName().equals(pkg)) {
+            if (pkg != null && !r.getSbn().getPackageName().equals(pkg)) {
                 continue;
             }
             if (channelId != null && !channelId.equals(r.getChannel().getId())) {
@@ -7852,7 +7891,7 @@
             return;
         }
 
-        String pkg = r.sbn.getPackageName();
+        String pkg = r.getSbn().getPackageName();
 
         if (pkg == null) {
             if (DBG) Slog.e(TAG, "No package for group summary: " + r.getKey());
@@ -7869,12 +7908,12 @@
     private void cancelGroupChildrenByListLocked(ArrayList<NotificationRecord> notificationList,
             NotificationRecord parentNotification, int callingUid, int callingPid,
             String listenerName, boolean sendDelete, boolean wasPosted, FlagChecker flagChecker) {
-        final String pkg = parentNotification.sbn.getPackageName();
+        final String pkg = parentNotification.getSbn().getPackageName();
         final int userId = parentNotification.getUserId();
         final int reason = REASON_GROUP_SUMMARY_CANCELED;
         for (int i = notificationList.size() - 1; i >= 0; i--) {
             final NotificationRecord childR = notificationList.get(i);
-            final StatusBarNotification childSbn = childR.sbn;
+            final StatusBarNotification childSbn = childR.getSbn();
             if ((childSbn.isGroup() && !childSbn.getNotification().isGroupSummary()) &&
                     childR.getGroupKey().equals(parentNotification.getGroupKey())
                     && (childR.getFlags() & FLAG_FOREGROUND_SERVICE) == 0
@@ -7952,7 +7991,7 @@
         for (int i = 0; i < len; i++) {
             NotificationRecord r = list.get(i);
             if (notificationMatchesUserId(r, userId) && r.getGroupKey().equals(groupKey)
-                    && r.sbn.getPackageName().equals(pkg)) {
+                    && r.getSbn().getPackageName().equals(pkg)) {
                 records.add(r);
             }
         }
@@ -7993,8 +8032,9 @@
         final int len = list.size();
         for (int i = 0; i < len; i++) {
             NotificationRecord r = list.get(i);
-            if (notificationMatchesUserId(r, userId) && r.sbn.getId() == id &&
-                    TextUtils.equals(r.sbn.getTag(), tag) && r.sbn.getPackageName().equals(pkg)) {
+            if (notificationMatchesUserId(r, userId) && r.getSbn().getId() == id &&
+                    TextUtils.equals(r.getSbn().getTag(), tag)
+                    && r.getSbn().getPackageName().equals(pkg)) {
                 return r;
             }
         }
@@ -8008,8 +8048,9 @@
         final int len = list.size();
         for (int i = 0; i < len; i++) {
             NotificationRecord r = list.get(i);
-            if (notificationMatchesUserId(r, userId) && r.sbn.getId() == id &&
-                    TextUtils.equals(r.sbn.getTag(), tag) && r.sbn.getPackageName().equals(pkg)) {
+            if (notificationMatchesUserId(r, userId) && r.getSbn().getId() == id &&
+                    TextUtils.equals(r.getSbn().getTag(), tag)
+                    && r.getSbn().getPackageName().equals(pkg)) {
                 matching.add(r);
             }
         }
@@ -8047,7 +8088,7 @@
             int numNotifications = mNotificationList.size();
             for (int i = 0; i < numNotifications; i++) {
                 NotificationRecord rec = mNotificationList.get(i);
-                if (pkgList.contains(rec.sbn.getPackageName())) {
+                if (pkgList.contains(rec.getSbn().getPackageName())) {
                     rec.setHidden(true);
                     changedNotifications.add(rec);
                 }
@@ -8065,7 +8106,7 @@
             int numNotifications = mNotificationList.size();
             for (int i = 0; i < numNotifications; i++) {
                 NotificationRecord rec = mNotificationList.get(i);
-                if (pkgList.contains(rec.sbn.getPackageName())) {
+                if (pkgList.contains(rec.getSbn().getPackageName())) {
                     rec.setHidden(false);
                     changedNotifications.add(rec);
                 }
@@ -8264,10 +8305,10 @@
 
         for (int i = 0; i < N; i++) {
             NotificationRecord record = mNotificationList.get(i);
-            if (!isVisibleToListener(record.sbn, info)) {
+            if (!isVisibleToListener(record.getSbn(), info)) {
                 continue;
             }
-            final String key = record.sbn.getKey();
+            final String key = record.getSbn().getKey();
             final NotificationListenerService.Ranking ranking =
                     new NotificationListenerService.Ranking();
             ranking.populate(
@@ -8278,7 +8319,7 @@
                     record.getSuppressedVisualEffects(),
                     record.getImportance(),
                     record.getImportanceExplanation(),
-                    record.sbn.getOverrideGroupKey(),
+                    record.getSbn().getOverrideGroupKey(),
                     record.getChannel(),
                     record.getPeopleOverride(),
                     record.getSnoozeCriteria(),
@@ -8550,7 +8591,7 @@
             for (final ManagedServiceInfo info : NotificationAssistants.this.getServices()) {
                 ArrayList<String> keys = new ArrayList<>(records.size());
                 for (NotificationRecord r : records) {
-                    boolean sbnVisible = isVisibleToListener(r.sbn, info)
+                    boolean sbnVisible = isVisibleToListener(r.getSbn(), info)
                             && info.isSameUser(r.getUserId());
                     if (sbnVisible) {
                         keys.add(r.getKey());
@@ -8638,7 +8679,7 @@
             if (debug) {
                 Slog.v(TAG, "onNotificationEnqueuedLocked() called with: r = [" + r + "]");
             }
-            final StatusBarNotification sbn = r.sbn;
+            final StatusBarNotification sbn = r.getSbn();
             notifyAssistantLocked(
                     sbn,
                     true /* sameUserOnly */,
@@ -8977,59 +9018,64 @@
         @GuardedBy("mNotificationLock")
         private void notifyPostedLocked(NotificationRecord r, NotificationRecord old,
                 boolean notifyAllListeners) {
-            // Lazily initialized snapshots of the notification.
-            StatusBarNotification sbn = r.sbn;
-            StatusBarNotification oldSbn = (old != null) ? old.sbn : null;
-            TrimCache trimCache = new TrimCache(sbn);
+            try {
+                // Lazily initialized snapshots of the notification.
+                StatusBarNotification sbn = r.getSbn();
+                StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null;
+                TrimCache trimCache = new TrimCache(sbn);
 
-            for (final ManagedServiceInfo info : getServices()) {
-                boolean sbnVisible = isVisibleToListener(sbn, info);
-                boolean oldSbnVisible = oldSbn != null ? isVisibleToListener(oldSbn, info) : false;
-                // This notification hasn't been and still isn't visible -> ignore.
-                if (!oldSbnVisible && !sbnVisible) {
-                    continue;
-                }
-                // If the notification is hidden, don't notifyPosted listeners targeting < P.
-                // Instead, those listeners will receive notifyPosted when the notification is
-                // unhidden.
-                if (r.isHidden() && info.targetSdkVersion < Build.VERSION_CODES.P) {
-                    continue;
-                }
+                for (final ManagedServiceInfo info : getServices()) {
+                    boolean sbnVisible = isVisibleToListener(sbn, info);
+                    boolean oldSbnVisible = oldSbn != null ? isVisibleToListener(oldSbn, info)
+                            : false;
+                    // This notification hasn't been and still isn't visible -> ignore.
+                    if (!oldSbnVisible && !sbnVisible) {
+                        continue;
+                    }
+                    // If the notification is hidden, don't notifyPosted listeners targeting < P.
+                    // Instead, those listeners will receive notifyPosted when the notification is
+                    // unhidden.
+                    if (r.isHidden() && info.targetSdkVersion < Build.VERSION_CODES.P) {
+                        continue;
+                    }
 
-                // If we shouldn't notify all listeners, this means the hidden state of
-                // a notification was changed.  Don't notifyPosted listeners targeting >= P.
-                // Instead, those listeners will receive notifyRankingUpdate.
-                if (!notifyAllListeners && info.targetSdkVersion >= Build.VERSION_CODES.P) {
-                    continue;
-                }
+                    // If we shouldn't notify all listeners, this means the hidden state of
+                    // a notification was changed.  Don't notifyPosted listeners targeting >= P.
+                    // Instead, those listeners will receive notifyRankingUpdate.
+                    if (!notifyAllListeners && info.targetSdkVersion >= Build.VERSION_CODES.P) {
+                        continue;
+                    }
 
-                final NotificationRankingUpdate update = makeRankingUpdateLocked(info);
+                    final NotificationRankingUpdate update = makeRankingUpdateLocked(info);
 
-                // This notification became invisible -> remove the old one.
-                if (oldSbnVisible && !sbnVisible) {
-                    final StatusBarNotification oldSbnLightClone = oldSbn.cloneLight();
+                    // This notification became invisible -> remove the old one.
+                    if (oldSbnVisible && !sbnVisible) {
+                        final StatusBarNotification oldSbnLightClone = oldSbn.cloneLight();
+                        mHandler.post(new Runnable() {
+                            @Override
+                            public void run() {
+                                notifyRemoved(
+                                        info, oldSbnLightClone, update, null, REASON_USER_STOPPED);
+                            }
+                        });
+                        continue;
+                    }
+
+                    // Grant access before listener is notified
+                    final int targetUserId = (info.userid == UserHandle.USER_ALL)
+                            ? UserHandle.USER_SYSTEM : info.userid;
+                    updateUriPermissions(r, old, info.component.getPackageName(), targetUserId);
+
+                    final StatusBarNotification sbnToPost = trimCache.ForListener(info);
                     mHandler.post(new Runnable() {
                         @Override
                         public void run() {
-                            notifyRemoved(
-                                    info, oldSbnLightClone, update, null, REASON_USER_STOPPED);
+                            notifyPosted(info, sbnToPost, update);
                         }
                     });
-                    continue;
                 }
-
-                // Grant access before listener is notified
-                final int targetUserId = (info.userid == UserHandle.USER_ALL)
-                        ? UserHandle.USER_SYSTEM : info.userid;
-                updateUriPermissions(r, old, info.component.getPackageName(), targetUserId);
-
-                final StatusBarNotification sbnToPost = trimCache.ForListener(info);
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        notifyPosted(info, sbnToPost, update);
-                    }
-                });
+            } catch (Exception e) {
+                Slog.e(TAG, "Could not notify listeners for " + r.getKey(), e);
             }
         }
 
@@ -9039,7 +9085,7 @@
         @GuardedBy("mNotificationLock")
         public void notifyRemovedLocked(NotificationRecord r, int reason,
                 NotificationStats notificationStats) {
-            final StatusBarNotification sbn = r.sbn;
+            final StatusBarNotification sbn = r.getSbn();
 
             // make a copy in case changes are made to the underlying Notification object
             // NOTE: this copy is lightweight: it doesn't include heavyweight parts of the
@@ -9103,7 +9149,7 @@
                 if (isHiddenRankingUpdate && serviceInfo.targetSdkVersion >=
                         Build.VERSION_CODES.P) {
                     for (NotificationRecord rec : changedHiddenNotifications) {
-                        if (isVisibleToListener(rec.sbn, serviceInfo)) {
+                        if (isVisibleToListener(rec.getSbn(), serviceInfo)) {
                             notifyThisListener = true;
                             break;
                         }
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 660d574..4785da9 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -91,7 +91,7 @@
     static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
     // the period after which a notification is updated where it can make sound
     private static final int MAX_SOUND_DELAY_MS = 2000;
-    final StatusBarNotification sbn;
+    private final StatusBarNotification sbn;
     IActivityManager mAm;
     UriGrantsManagerInternal mUgmInternal;
     final int mTargetSdkVersion;
@@ -229,7 +229,7 @@
     }
 
     private Uri calculateSound() {
-        final Notification n = sbn.getNotification();
+        final Notification n = getSbn().getNotification();
 
         // No notification sounds on tv
         if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
@@ -265,7 +265,7 @@
         if (mPreChannelsNotification
                 && (getChannel().getUserLockedFields()
                 & NotificationChannel.USER_LOCKED_LIGHTS) == 0) {
-            final Notification notification = sbn.getNotification();
+            final Notification notification = getSbn().getNotification();
             if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0) {
                 light = new Light(notification.ledARGB, notification.ledOnMS,
                         notification.ledOffMS);
@@ -296,7 +296,7 @@
         if (mPreChannelsNotification
                 && (getChannel().getUserLockedFields()
                 & NotificationChannel.USER_LOCKED_VIBRATION) == 0) {
-            final Notification notification = sbn.getNotification();
+            final Notification notification = getSbn().getNotification();
             final boolean useDefaultVibrate =
                     (notification.defaults & Notification.DEFAULT_VIBRATE) != 0;
             if (useDefaultVibrate) {
@@ -309,7 +309,7 @@
     }
 
     private AudioAttributes calculateAttributes() {
-        final Notification n = sbn.getNotification();
+        final Notification n = getSbn().getNotification();
         AudioAttributes attributes = getChannel().getAudioAttributes();
         if (attributes == null) {
             attributes = Notification.AUDIO_ATTRIBUTES_DEFAULT;
@@ -335,7 +335,7 @@
     }
 
     private int calculateInitialImportance() {
-        final Notification n = sbn.getNotification();
+        final Notification n = getSbn().getNotification();
         int importance = getChannel().getImportance();  // Post-channels notifications use this
         mInitialImportanceExplanationCode = getChannel().hasUserSetImportance()
                 ? MetricsEvent.IMPORTANCE_EXPLANATION_USER
@@ -406,31 +406,31 @@
         mRankingTimeMs = calculateRankingTimeMs(previous.getRankingTimeMs());
         mCreationTimeMs = previous.mCreationTimeMs;
         mVisibleSinceMs = previous.mVisibleSinceMs;
-        if (previous.sbn.getOverrideGroupKey() != null && !sbn.isAppGroup()) {
-            sbn.setOverrideGroupKey(previous.sbn.getOverrideGroupKey());
+        if (previous.getSbn().getOverrideGroupKey() != null && !getSbn().isAppGroup()) {
+            getSbn().setOverrideGroupKey(previous.getSbn().getOverrideGroupKey());
         }
         // Don't copy importance information or mGlobalSortKey, recompute them.
     }
 
-    public Notification getNotification() { return sbn.getNotification(); }
-    public int getFlags() { return sbn.getNotification().flags; }
-    public UserHandle getUser() { return sbn.getUser(); }
-    public String getKey() { return sbn.getKey(); }
+    public Notification getNotification() { return getSbn().getNotification(); }
+    public int getFlags() { return getSbn().getNotification().flags; }
+    public UserHandle getUser() { return getSbn().getUser(); }
+    public String getKey() { return getSbn().getKey(); }
     /** @deprecated Use {@link #getUser()} instead. */
-    public int getUserId() { return sbn.getUserId(); }
-    public int getUid() { return sbn.getUid(); }
+    public int getUserId() { return getSbn().getUserId(); }
+    public int getUid() { return getSbn().getUid(); }
 
     void dump(ProtoOutputStream proto, long fieldId, boolean redact, int state) {
         final long token = proto.start(fieldId);
 
-        proto.write(NotificationRecordProto.KEY, sbn.getKey());
+        proto.write(NotificationRecordProto.KEY, getSbn().getKey());
         proto.write(NotificationRecordProto.STATE, state);
         if (getChannel() != null) {
             proto.write(NotificationRecordProto.CHANNEL_ID, getChannel().getId());
         }
         proto.write(NotificationRecordProto.CAN_SHOW_LIGHT, getLight() != null);
         proto.write(NotificationRecordProto.CAN_VIBRATE, getVibration() != null);
-        proto.write(NotificationRecordProto.FLAGS, sbn.getNotification().flags);
+        proto.write(NotificationRecordProto.FLAGS, getSbn().getNotification().flags);
         proto.write(NotificationRecordProto.GROUP_KEY, getGroupKey());
         proto.write(NotificationRecordProto.IMPORTANCE, getImportance());
         if (getSound() != null) {
@@ -439,8 +439,8 @@
         if (getAudioAttributes() != null) {
             getAudioAttributes().dumpDebug(proto, NotificationRecordProto.AUDIO_ATTRIBUTES);
         }
-        proto.write(NotificationRecordProto.PACKAGE, sbn.getPackageName());
-        proto.write(NotificationRecordProto.DELEGATE_PACKAGE, sbn.getOpPkg());
+        proto.write(NotificationRecordProto.PACKAGE, getSbn().getPackageName());
+        proto.write(NotificationRecordProto.DELEGATE_PACKAGE, getSbn().getOpPkg());
 
         proto.end(token);
     }
@@ -452,15 +452,15 @@
     }
 
     void dump(PrintWriter pw, String prefix, Context baseContext, boolean redact) {
-        final Notification notification = sbn.getNotification();
+        final Notification notification = getSbn().getNotification();
         pw.println(prefix + this);
         prefix = prefix + "  ";
-        pw.println(prefix + "uid=" + sbn.getUid() + " userId=" + sbn.getUserId());
-        pw.println(prefix + "opPkg=" + sbn.getOpPkg());
+        pw.println(prefix + "uid=" + getSbn().getUid() + " userId=" + getSbn().getUserId());
+        pw.println(prefix + "opPkg=" + getSbn().getOpPkg());
         pw.println(prefix + "icon=" + notification.getSmallIcon());
         pw.println(prefix + "flags=0x" + Integer.toHexString(notification.flags));
         pw.println(prefix + "pri=" + notification.priority);
-        pw.println(prefix + "key=" + sbn.getKey());
+        pw.println(prefix + "key=" + getSbn().getKey());
         pw.println(prefix + "seen=" + mStats.hasSeen());
         pw.println(prefix + "groupKey=" + getGroupKey());
         pw.println(prefix + "fullscreenIntent=" + notification.fullScreenIntent);
@@ -594,9 +594,9 @@
                 "NotificationRecord(0x%08x: pkg=%s user=%s id=%d tag=%s importance=%d key=%s" +
                         ": %s)",
                 System.identityHashCode(this),
-                this.sbn.getPackageName(), this.sbn.getUser(), this.sbn.getId(),
-                this.sbn.getTag(), this.mImportance, this.sbn.getKey(),
-                this.sbn.getNotification());
+                this.getSbn().getPackageName(), this.getSbn().getUser(), this.getSbn().getId(),
+                this.getSbn().getTag(), this.mImportance, this.getSbn().getKey(),
+                this.getSbn().getNotification());
     }
 
     public boolean hasAdjustment(String key) {
@@ -936,7 +936,7 @@
     private long calculateRankingTimeMs(long previousRankingTimeMs) {
         Notification n = getNotification();
         // Take developer provided 'when', unless it's in the future.
-        if (n.when != 0 && n.when <= sbn.getPostTime()) {
+        if (n.when != 0 && n.when <= getSbn().getPostTime()) {
             return n.when;
         }
         // If we've ranked a previous instance with a timestamp, inherit it. This case is
@@ -944,7 +944,7 @@
         if (previousRankingTimeMs > 0) {
             return previousRankingTimeMs;
         }
-        return sbn.getPostTime();
+        return getSbn().getPostTime();
     }
 
     public void setGlobalSortKey(String globalSortKey) {
@@ -977,11 +977,11 @@
     }
 
     public String getGroupKey() {
-        return sbn.getGroupKey();
+        return getSbn().getGroupKey();
     }
 
     public void setOverrideGroupKey(String overrideGroupKey) {
-        sbn.setOverrideGroupKey(overrideGroupKey);
+        getSbn().setOverrideGroupKey(overrideGroupKey);
     }
 
     public NotificationChannel getChannel() {
@@ -1202,7 +1202,7 @@
      * Returns whether this notification was posted by a secondary app
      */
     public boolean isProxied() {
-        return !Objects.equals(sbn.getPackageName(), sbn.getOpPkg());
+        return !Objects.equals(getSbn().getPackageName(), getSbn().getOpPkg());
     }
 
     /**
@@ -1245,7 +1245,7 @@
         if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return;
 
         // We can't grant Uri permissions from system
-        final int sourceUid = sbn.getUid();
+        final int sourceUid = getSbn().getUid();
         if (sourceUid == android.os.Process.SYSTEM_UID) return;
 
         final long ident = Binder.clearCallingIdentity();
@@ -1274,7 +1274,7 @@
     }
 
     public LogMaker getLogMaker(long now) {
-        LogMaker lm = sbn.getLogMaker()
+        LogMaker lm = getSbn().getLogMaker()
                 .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_CHANNEL_IMPORTANCE, mImportance)
                 .addTaggedData(MetricsEvent.NOTIFICATION_SINCE_CREATE_MILLIS, getLifespanMs(now))
                 .addTaggedData(MetricsEvent.NOTIFICATION_SINCE_UPDATE_MILLIS, getFreshnessMs(now))
@@ -1351,6 +1351,10 @@
         return true;
     }
 
+    StatusBarNotification getSbn() {
+        return sbn;
+    }
+
     @VisibleForTesting
     static final class Light {
         public final int color;
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLogger.java b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
index 9bbc3924..8d8511f 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLogger.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
@@ -88,12 +88,12 @@
                 return true;
             }
 
-            return !(Objects.equals(r.sbn.getChannelIdLogTag(), old.sbn.getChannelIdLogTag())
-                    && Objects.equals(r.sbn.getGroupLogTag(), old.sbn.getGroupLogTag())
-                    && (r.sbn.getNotification().isGroupSummary()
-                        == old.sbn.getNotification().isGroupSummary())
-                    && Objects.equals(r.sbn.getNotification().category,
-                        old.sbn.getNotification().category)
+            return !(Objects.equals(r.getSbn().getChannelIdLogTag(), old.getSbn().getChannelIdLogTag())
+                    && Objects.equals(r.getSbn().getGroupLogTag(), old.getSbn().getGroupLogTag())
+                    && (r.getSbn().getNotification().isGroupSummary()
+                        == old.getSbn().getNotification().isGroupSummary())
+                    && Objects.equals(r.getSbn().getNotification().category,
+                        old.getSbn().getNotification().category)
                     && (r.getImportance() == old.getImportance()));
         }
 
@@ -106,7 +106,7 @@
          * @return hash code for the notification style class, or 0 if none exists.
          */
         public int getStyle() {
-            return getStyle(r.sbn.getNotification().extras);
+            return getStyle(r.getSbn().getNotification().extras);
         }
 
         private int getStyle(@Nullable Bundle extras) {
@@ -120,7 +120,7 @@
         }
 
         int getNumPeople() {
-            return getNumPeople(r.sbn.getNotification().extras);
+            return getNumPeople(r.getSbn().getNotification().extras);
         }
 
         private int getNumPeople(@Nullable Bundle extras) {
@@ -140,7 +140,7 @@
         }
 
         int getInstanceId() {
-            return (r.sbn.getInstanceId() == null ? 0 : r.sbn.getInstanceId().getId());
+            return (r.getSbn().getInstanceId() == null ? 0 : r.getSbn().getInstanceId().getId());
         }
     }
 }
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
index d613799..4974c30 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
@@ -34,15 +34,15 @@
         FrameworkStatsLog.write(FrameworkStatsLog.NOTIFICATION_REPORTED,
                 /* int32 event_id = 1 */ p.getUiEvent().getId(),
                 /* int32 uid = 2 */ r.getUid(),
-                /* string package_name = 3 */ r.sbn.getPackageName(),
+                /* string package_name = 3 */ r.getSbn().getPackageName(),
                 /* int32 instance_id = 4 */ p.getInstanceId(),
-                /* int32 notification_id = 5 */ r.sbn.getId(),
-                /* string notification_tag = 6 */ r.sbn.getTag(),
-                /* string channel_id = 7 */ r.sbn.getChannelIdLogTag(),
-                /* string group_id = 8 */ r.sbn.getGroupLogTag(),
+                /* int32 notification_id = 5 */ r.getSbn().getId(),
+                /* string notification_tag = 6 */ r.getSbn().getTag(),
+                /* string channel_id = 7 */ r.getSbn().getChannelIdLogTag(),
+                /* string group_id = 8 */ r.getSbn().getGroupLogTag(),
                 /* int32 group_instance_id = 9 */ 0, // TODO generate and fill instance ids
-                /* bool is_group_summary = 10 */ r.sbn.getNotification().isGroupSummary(),
-                /* string category = 11 */ r.sbn.getNotification().category,
+                /* bool is_group_summary = 10 */ r.getSbn().getNotification().isGroupSummary(),
+                /* string category = 11 */ r.getSbn().getNotification().category,
                 /* int32 style = 12 */ p.getStyle(),
                 /* int32 num_people = 13 */ p.getNumPeople(),
                 /* int32 position = 14 */ position,
diff --git a/services/core/java/com/android/server/notification/NotificationUsageStats.java b/services/core/java/com/android/server/notification/NotificationUsageStats.java
index d1fe0d9..b42fe92 100644
--- a/services/core/java/com/android/server/notification/NotificationUsageStats.java
+++ b/services/core/java/com/android/server/notification/NotificationUsageStats.java
@@ -276,7 +276,7 @@
 
     // Locked by this.
     private AggregatedStats[] getAggregatedStatsLocked(NotificationRecord record) {
-        return getAggregatedStatsLocked(record.sbn.getPackageName());
+        return getAggregatedStatsLocked(record.getSbn().getPackageName());
     }
 
     // Locked by this.
@@ -1142,7 +1142,7 @@
                     long nowMs = System.currentTimeMillis();
                     switch (msg.what) {
                         case MSG_POST:
-                            writeEvent(r.sbn.getPostTime(), EVENT_TYPE_POST, r);
+                            writeEvent(r.getSbn().getPostTime(), EVENT_TYPE_POST, r);
                             break;
                         case MSG_CLICK:
                             writeEvent(nowMs, EVENT_TYPE_CLICK, r);
@@ -1287,7 +1287,7 @@
 
         private void writeEvent(long eventTimeMs, int eventType, NotificationRecord r) {
             ContentValues cv = new ContentValues();
-            cv.put(COL_EVENT_USER_ID, r.sbn.getUser().getIdentifier());
+            cv.put(COL_EVENT_USER_ID, r.getSbn().getUser().getIdentifier());
             cv.put(COL_EVENT_TIME, eventTimeMs);
             cv.put(COL_EVENT_TYPE, eventType);
             putNotificationIdentifiers(r, cv);
@@ -1324,16 +1324,16 @@
         }
 
         private static void putNotificationIdentifiers(NotificationRecord r, ContentValues outCv) {
-            outCv.put(COL_KEY, r.sbn.getKey());
-            outCv.put(COL_PKG, r.sbn.getPackageName());
+            outCv.put(COL_KEY, r.getSbn().getKey());
+            outCv.put(COL_PKG, r.getSbn().getPackageName());
         }
 
         private static void putNotificationDetails(NotificationRecord r, ContentValues outCv) {
-            outCv.put(COL_NOTIFICATION_ID, r.sbn.getId());
-            if (r.sbn.getTag() != null) {
-                outCv.put(COL_TAG, r.sbn.getTag());
+            outCv.put(COL_NOTIFICATION_ID, r.getSbn().getId());
+            if (r.getSbn().getTag() != null) {
+                outCv.put(COL_TAG, r.getSbn().getTag());
             }
-            outCv.put(COL_WHEN_MS, r.sbn.getPostTime());
+            outCv.put(COL_WHEN_MS, r.getSbn().getPostTime());
             outCv.put(COL_FLAGS, r.getNotification().flags);
             final int before = r.stats.requestedImportance;
             final int after = r.getImportance();
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index b0c1863..fe39322 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -1380,7 +1380,8 @@
                 policy.priorityCategories, policy.priorityCallSenders,
                 policy.priorityMessageSenders, policy.suppressedVisualEffects,
                 (areChannelsBypassingDnd ? NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND
-                        : 0)));
+                        : 0),
+                policy.priorityConversationSenders));
     }
 
     public boolean areChannelsBypassingDnd() {
diff --git a/services/core/java/com/android/server/notification/SnoozeHelper.java b/services/core/java/com/android/server/notification/SnoozeHelper.java
index 9e32d0e..661297a 100644
--- a/services/core/java/com/android/server/notification/SnoozeHelper.java
+++ b/services/core/java/com/android/server/notification/SnoozeHelper.java
@@ -166,7 +166,7 @@
             ArrayMap<String, NotificationRecord> packages =
                     mSnoozedNotifications.get(userId).get(pkg);
             for (int i = 0; i < packages.size(); i++) {
-                String currentGroupKey = packages.valueAt(i).sbn.getGroup();
+                String currentGroupKey = packages.valueAt(i).getSbn().getGroup();
                 if (currentGroupKey.equals(groupKey)) {
                     records.add(packages.valueAt(i));
                 }
@@ -223,7 +223,7 @@
      * Snoozes a notification and schedules an alarm to repost at that time.
      */
     protected void snooze(NotificationRecord record, long duration) {
-        String pkg = record.sbn.getPackageName();
+        String pkg = record.getSbn().getPackageName();
         String key = record.getKey();
         int userId = record.getUser().getIdentifier();
 
@@ -242,7 +242,7 @@
         int userId = record.getUser().getIdentifier();
         if (contextId != null) {
             synchronized (mPersistedSnoozedNotificationsWithContext) {
-                storeRecord(record.sbn.getPackageName(), record.getKey(),
+                storeRecord(record.getSbn().getPackageName(), record.getKey(),
                         userId, mPersistedSnoozedNotificationsWithContext, contextId);
             }
         }
@@ -254,9 +254,9 @@
         if (DEBUG) {
             Slog.d(TAG, "Snoozing " + record.getKey());
         }
-        storeRecord(record.sbn.getPackageName(), record.getKey(),
+        storeRecord(record.getSbn().getPackageName(), record.getKey(),
                 userId, mSnoozedNotifications, record);
-        mPackages.put(record.getKey(), record.sbn.getPackageName());
+        mPackages.put(record.getKey(), record.getSbn().getPackageName());
         mUsers.put(record.getKey(), userId);
     }
 
@@ -308,7 +308,7 @@
             if (recordsForPkg != null) {
                 final Set<Map.Entry<String, NotificationRecord>> records = recordsForPkg.entrySet();
                 for (Map.Entry<String, NotificationRecord> record : records) {
-                    final StatusBarNotification sbn = record.getValue().sbn;
+                    final StatusBarNotification sbn = record.getValue().getSbn();
                     if (Objects.equals(sbn.getTag(), tag) && sbn.getId() == id) {
                         record.getValue().isCanceled = true;
                         return true;
@@ -369,7 +369,7 @@
         if (records == null) {
             return;
         }
-        ArrayMap<String, NotificationRecord> pkgRecords = records.get(record.sbn.getPackageName());
+        ArrayMap<String, NotificationRecord> pkgRecords = records.get(record.getSbn().getPackageName());
         if (pkgRecords == null) {
             return;
         }
@@ -420,7 +420,7 @@
                     int N = recordsByKey.size();
                     for (int i = 0; i < N; i++) {
                         final NotificationRecord potentialGroupSummary = recordsByKey.valueAt(i);
-                        if (potentialGroupSummary.sbn.isGroup()
+                        if (potentialGroupSummary.getSbn().isGroup()
                                 && potentialGroupSummary.getNotification().isGroupSummary()
                                 && groupKey.equals(potentialGroupSummary.getGroupKey())) {
                             groupSummaryKey = potentialGroupSummary.getKey();
diff --git a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
index 639cc70..90fc59a 100644
--- a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
+++ b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
@@ -38,6 +38,8 @@
 import android.util.LruCache;
 import android.util.Slog;
 
+import libcore.util.EmptyArray;
+
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.LinkedList;
@@ -301,7 +303,7 @@
         for (String person: second) {
             people.add(person);
         }
-        return (String[]) people.toArray();
+        return people.toArray(EmptyArray.STRING);
     }
 
     @Nullable
diff --git a/services/core/java/com/android/server/notification/ZenModeFiltering.java b/services/core/java/com/android/server/notification/ZenModeFiltering.java
index 6045f6c..4d19855 100644
--- a/services/core/java/com/android/server/notification/ZenModeFiltering.java
+++ b/services/core/java/com/android/server/notification/ZenModeFiltering.java
@@ -17,6 +17,7 @@
 package com.android.server.notification;
 
 import static android.provider.Settings.Global.ZEN_MODE_OFF;
+import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_ANYONE;
 
 import android.app.Notification;
 import android.app.NotificationManager;
@@ -106,8 +107,8 @@
     }
 
     private static Bundle extras(NotificationRecord record) {
-        return record != null && record.sbn != null && record.sbn.getNotification() != null
-                ? record.sbn.getNotification().extras : null;
+        return record != null && record.getSbn() != null && record.getSbn().getNotification() != null
+                ? record.getSbn().getNotification().extras : null;
     }
 
     protected void recordCall(NotificationRecord record) {
@@ -125,8 +126,8 @@
         }
         // Make an exception to policy for the notification saying that policy has changed
         if (NotificationManager.Policy.areAllVisualEffectsSuppressed(policy.suppressedVisualEffects)
-                && "android".equals(record.sbn.getPackageName())
-                && SystemMessageProto.SystemMessage.NOTE_ZEN_UPGRADE == record.sbn.getId()) {
+                && "android".equals(record.getSbn().getPackageName())
+                && SystemMessageProto.SystemMessage.NOTE_ZEN_UPGRADE == record.getSbn().getId()) {
             ZenLog.traceNotIntercepted(record, "systemDndChangedNotification");
             return false;
         }
@@ -156,25 +157,6 @@
                     }
                     return false;
                 }
-                if (isCall(record)) {
-                    if (policy.allowRepeatCallers()
-                            && REPEAT_CALLERS.isRepeat(mContext, extras(record))) {
-                        ZenLog.traceNotIntercepted(record, "repeatCaller");
-                        return false;
-                    }
-                    if (!policy.allowCalls()) {
-                        ZenLog.traceIntercepted(record, "!allowCalls");
-                        return true;
-                    }
-                    return shouldInterceptAudience(policy.allowCallsFrom(), record);
-                }
-                if (isMessage(record)) {
-                    if (!policy.allowMessages()) {
-                        ZenLog.traceIntercepted(record, "!allowMessages");
-                        return true;
-                    }
-                    return shouldInterceptAudience(policy.allowMessagesFrom(), record);
-                }
                 if (isEvent(record)) {
                     if (!policy.allowEvents()) {
                         ZenLog.traceIntercepted(record, "!allowEvents");
@@ -203,6 +185,41 @@
                     }
                     return false;
                 }
+                if (isConversation(record)) {
+                    if (policy.allowConversations()) {
+                        if (policy.priorityConversationSenders == CONVERSATION_SENDERS_ANYONE) {
+                            ZenLog.traceNotIntercepted(record, "conversationAnyone");
+                            return false;
+                        } else if (policy.priorityConversationSenders
+                                == NotificationManager.Policy.CONVERSATION_SENDERS_IMPORTANT
+                                && record.getChannel().isImportantConversation()) {
+                            ZenLog.traceNotIntercepted(record, "conversationMatches");
+                            return false;
+                        }
+                    }
+                    // if conversations aren't allowed record might still be allowed thanks
+                    // to call or message metadata, so don't return yet
+                }
+                if (isCall(record)) {
+                    if (policy.allowRepeatCallers()
+                            && REPEAT_CALLERS.isRepeat(mContext, extras(record))) {
+                        ZenLog.traceNotIntercepted(record, "repeatCaller");
+                        return false;
+                    }
+                    if (!policy.allowCalls()) {
+                        ZenLog.traceIntercepted(record, "!allowCalls");
+                        return true;
+                    }
+                    return shouldInterceptAudience(policy.allowCallsFrom(), record);
+                }
+                if (isMessage(record)) {
+                    if (!policy.allowMessages()) {
+                        ZenLog.traceIntercepted(record, "!allowMessages");
+                        return true;
+                    }
+                    return shouldInterceptAudience(policy.allowMessagesFrom(), record);
+                }
+
                 ZenLog.traceIntercepted(record, "!priority");
                 return true;
             default:
@@ -245,7 +262,7 @@
     }
 
     public boolean isCall(NotificationRecord record) {
-        return record != null && (isDefaultPhoneApp(record.sbn.getPackageName())
+        return record != null && (isDefaultPhoneApp(record.getSbn().getPackageName())
                 || record.isCategory(Notification.CATEGORY_CALL));
     }
 
@@ -273,7 +290,11 @@
     }
 
     protected boolean isMessage(NotificationRecord record) {
-        return mMessagingUtil.isMessaging(record.sbn);
+        return mMessagingUtil.isMessaging(record.getSbn());
+    }
+
+    protected boolean isConversation(NotificationRecord record) {
+        return record.isConversation();
     }
 
     private static boolean audienceMatches(int source, float contactAffinity) {
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 696d2ea..3b564c3 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -641,9 +641,11 @@
     }
 
     public void dump(PrintWriter pw, String prefix) {
-        pw.print(prefix); pw.print("mZenMode=");
+        pw.print(prefix);
+        pw.print("mZenMode=");
         pw.println(Global.zenModeToString(mZenMode));
-        pw.print("mConsolidatedPolicy=" + mConsolidatedPolicy.toString());
+        pw.print(prefix);
+        pw.println("mConsolidatedPolicy=" + mConsolidatedPolicy.toString());
         final int N = mConfigs.size();
         for (int i = 0; i < N; i++) {
             dump(pw, prefix, "mConfigs[u=" + mConfigs.keyAt(i) + "]", mConfigs.valueAt(i));
@@ -665,13 +667,17 @@
             return;
         }
         pw.printf("allow(alarms=%b,media=%b,system=%b,calls=%b,callsFrom=%s,repeatCallers=%b,"
-                + "messages=%b,messagesFrom=%s,events=%b,reminders=%b)\n",
+                + "messages=%b,messagesFrom=%s,conversations=%b,conversationsFrom=%s,"
+                        + "events=%b,reminders=%b)\n",
                 config.allowAlarms, config.allowMedia, config.allowSystem,
                 config.allowCalls, ZenModeConfig.sourceToString(config.allowCallsFrom),
                 config.allowRepeatCallers, config.allowMessages,
                 ZenModeConfig.sourceToString(config.allowMessagesFrom),
+                config.allowConversations,
+                ZenPolicy.conversationTypeToString(config.allowConversationsFrom),
                 config.allowEvents, config.allowReminders);
-        pw.printf(" disallow(visualEffects=%s)\n", config.suppressedVisualEffects);
+        pw.print(prefix);
+        pw.printf("  disallow(visualEffects=%s)\n", config.suppressedVisualEffects);
         pw.print(prefix); pw.print("  manualRule="); pw.println(config.manualRule);
         if (config.automaticRules.isEmpty()) return;
         final int N = config.automaticRules.size();
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 40ea6cf..b98bb08 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -234,12 +234,12 @@
     }
 
     public void moveCompleteApp(String fromUuid, String toUuid, String packageName,
-            String dataAppName, int appId, String seInfo, int targetSdkVersion)
-            throws InstallerException {
+            String dataAppName, int appId, String seInfo, int targetSdkVersion,
+            String fromCodePath) throws InstallerException {
         if (!checkBeforeRemote()) return;
         try {
             mInstalld.moveCompleteApp(fromUuid, toUuid, packageName, dataAppName, appId, seInfo,
-                    targetSdkVersion);
+                    targetSdkVersion, fromCodePath);
         } catch (Exception e) {
             throw InstallerException.from(e);
         }
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 7bb782b..3e64e98 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -29,12 +29,14 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentSender;
+import android.content.LocusId;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ILauncherApps;
 import android.content.pm.IOnAppsChangedListener;
 import android.content.pm.IPackageInstallerCallback;
 import android.content.pm.IPackageManager;
+import android.content.pm.IShortcutChangeCallback;
 import android.content.pm.LauncherApps;
 import android.content.pm.LauncherApps.ShortcutQuery;
 import android.content.pm.PackageInfo;
@@ -661,8 +663,8 @@
 
         @Override
         public ParceledListSlice getShortcuts(String callingPackage, long changedSince,
-                String packageName, List shortcutIds, ComponentName componentName, int flags,
-                UserHandle targetUser) {
+                String packageName, List shortcutIds, List<LocusId> locusIds,
+                ComponentName componentName, int flags, UserHandle targetUser) {
             ensureShortcutPermission(callingPackage);
             if (!canAccessProfile(targetUser.getIdentifier(), "Cannot get shortcuts")) {
                 return new ParceledListSlice<>(Collections.EMPTY_LIST);
@@ -671,16 +673,31 @@
                 throw new IllegalArgumentException(
                         "To query by shortcut ID, package name must also be set");
             }
+            if (locusIds != null && packageName == null) {
+                throw new IllegalArgumentException(
+                        "To query by locus ID, package name must also be set");
+            }
 
             // TODO(b/29399275): Eclipse compiler requires explicit List<ShortcutInfo> cast below.
             return new ParceledListSlice<>((List<ShortcutInfo>)
                     mShortcutServiceInternal.getShortcuts(getCallingUserId(),
-                            callingPackage, changedSince, packageName, shortcutIds,
+                            callingPackage, changedSince, packageName, shortcutIds, locusIds,
                             componentName, flags, targetUser.getIdentifier(),
                             injectBinderCallingPid(), injectBinderCallingUid()));
         }
 
         @Override
+        public void registerShortcutChangeCallback(String callingPackage, long changedSince,
+                String packageName, List shortcutIds, List<LocusId> locusIds,
+                ComponentName componentName, int flags, IShortcutChangeCallback callback,
+                int callbackId) {
+        }
+
+        @Override
+        public void unregisterShortcutChangeCallback(String callingPackage, int callbackId) {
+        }
+
+        @Override
         public void pinShortcuts(String callingPackage, String packageName, List<String> ids,
                 UserHandle targetUser) {
             ensureShortcutPermission(callingPackage);
@@ -1137,7 +1154,7 @@
                                 mShortcutServiceInternal.getShortcuts(launcherUserId,
                                         cookie.packageName,
                                         /* changedSince= */ 0, packageName, /* shortcutIds=*/ null,
-                                        /* component= */ null,
+                                        /* locusIds=*/ null, /* component= */ null,
                                         ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY
                                         | ShortcutQuery.FLAG_MATCH_ALL_KINDS_WITH_ALL_PINNED
                                         , userId, cookie.callingPid, cookie.callingUid);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index bf7bebd..0cf8b42 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -129,6 +129,7 @@
 import com.android.server.security.VerityUtils;
 
 import libcore.io.IoUtils;
+import libcore.util.EmptyArray;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -212,7 +213,8 @@
     private static final String ATTR_SIGNATURE = "signature";
 
     private static final String PROPERTY_NAME_INHERIT_NATIVE = "pi.inherit_native_on_dont_kill";
-    private static final int[] EMPTY_CHILD_SESSION_ARRAY = {};
+    private static final int[] EMPTY_CHILD_SESSION_ARRAY = EmptyArray.INT;
+    private static final FileInfo[] EMPTY_FILE_INFO_ARRAY = {};
 
     private static final String SYSTEM_DATA_LOADER_PACKAGE = "android";
 
@@ -375,8 +377,6 @@
     // TODO(b/146080380): merge file list with Callback installation.
     private IncrementalFileStorages mIncrementalFileStorages;
 
-    private static final String[] EMPTY_STRING_ARRAY = new String[]{};
-
     private static final FileFilter sAddedApkFilter = new FileFilter() {
         @Override
         public boolean accept(File file) {
@@ -558,10 +558,22 @@
         mStagedSessionErrorMessage =
                 stagedSessionErrorMessage != null ? stagedSessionErrorMessage : "";
 
-        if (isStreamingInstallation()
-                && this.params.dataLoaderParams.getComponentName().getPackageName()
-                == SYSTEM_DATA_LOADER_PACKAGE) {
-            assertShellOrSystemCalling("System data loaders");
+        if (isDataLoaderInstallation()) {
+            if (isApexInstallation()) {
+                throw new IllegalArgumentException(
+                        "DataLoader installation of APEX modules is not allowed.");
+            }
+        }
+
+        if (isStreamingInstallation()) {
+            if (!isIncrementalInstallationAllowed(mPackageName)) {
+                throw new IllegalArgumentException(
+                        "Incremental installation of this package is not allowed.");
+            }
+            if (this.params.dataLoaderParams.getComponentName().getPackageName()
+                    == SYSTEM_DATA_LOADER_PACKAGE) {
+                assertShellOrSystemCalling("System data loaders");
+            }
         }
     }
 
@@ -719,7 +731,7 @@
         if (!isDataLoaderInstallation()) {
             String[] result = stageDir.list();
             if (result == null) {
-                result = EMPTY_STRING_ARRAY;
+                result = EmptyArray.STRING;
             }
             return result;
         }
@@ -1174,6 +1186,19 @@
     }
 
     /**
+     * Checks if the package can be installed on IncFs.
+     */
+    private static boolean isIncrementalInstallationAllowed(String packageName) {
+        final PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
+        final AndroidPackage existingPackage = pmi.getPackage(packageName);
+        if (existingPackage == null) {
+            return true;
+        }
+
+        return !PackageManagerService.isSystemApp(existingPackage);
+    }
+
+    /**
      * If this was not already called, the session will be sealed.
      *
      * This method may be called multiple times to update the status receiver validate caller
@@ -1362,14 +1387,10 @@
                     return false;
                 }
 
-                final PackageInfo pkgInfo = mPm.getPackageInfo(
-                        params.appPackageName, PackageManager.GET_SIGNATURES
-                                | PackageManager.MATCH_STATIC_SHARED_LIBRARIES /*flags*/, userId);
-
                 if (isApexInstallation()) {
                     validateApexInstallLocked();
                 } else {
-                    validateApkInstallLocked(pkgInfo);
+                    validateApkInstallLocked();
                 }
             }
 
@@ -1786,8 +1807,7 @@
      * {@link PackageManagerService}.
      */
     @GuardedBy("mLock")
-    private void validateApkInstallLocked(@Nullable PackageInfo pkgInfo)
-            throws PackageManagerException {
+    private void validateApkInstallLocked() throws PackageManagerException {
         ApkLite baseApk = null;
         mPackageName = null;
         mVersionCode = -1;
@@ -1797,6 +1817,10 @@
         mResolvedStagedFiles.clear();
         mResolvedInheritedFiles.clear();
 
+        final PackageInfo pkgInfo = mPm.getPackageInfo(
+                params.appPackageName, PackageManager.GET_SIGNATURES
+                        | PackageManager.MATCH_STATIC_SHARED_LIBRARIES /*flags*/, userId);
+
         // Partial installs must be consistent with existing install
         if (params.mode == SessionParams.MODE_INHERIT_EXISTING
                 && (pkgInfo == null || pkgInfo.applicationInfo == null)) {
@@ -3096,7 +3120,8 @@
         }
 
         if (grantedRuntimePermissions.size() > 0) {
-            params.grantedRuntimePermissions = (String[]) grantedRuntimePermissions.toArray();
+            params.grantedRuntimePermissions =
+                    grantedRuntimePermissions.toArray(EmptyArray.STRING);
         }
 
         if (whitelistedRestrictedPermissions.size() > 0) {
@@ -3115,7 +3140,7 @@
 
         FileInfo[] fileInfosArray = null;
         if (!files.isEmpty()) {
-            fileInfosArray = (FileInfo[]) files.toArray();
+            fileInfosArray = files.toArray(EMPTY_FILE_INFO_ARRAY);
         }
 
         InstallSource installSource = InstallSource.create(installInitiatingPackageName,
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 79cf23f..c8585907 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -150,6 +150,7 @@
 import android.content.pm.AuxiliaryResolveInfo;
 import android.content.pm.ChangedPackages;
 import android.content.pm.ComponentInfo;
+import android.content.pm.DataLoaderType;
 import android.content.pm.FallbackCategoryProvider;
 import android.content.pm.FeatureInfo;
 import android.content.pm.IDexModuleRegisterCallback;
@@ -1537,15 +1538,16 @@
     final @NonNull String mRequiredPermissionControllerPackage;
     final @Nullable String mSetupWizardPackage;
     final @Nullable String mStorageManagerPackage;
-    final @Nullable String mSystemTextClassifierPackage;
+    final @Nullable String mDefaultTextClassifierPackage;
+    final @Nullable String mSystemTextClassifierPackageName;
     final @Nullable String mWellbeingPackage;
     final @Nullable String mDocumenterPackage;
     final @Nullable String mConfiguratorPackage;
     final @Nullable String mAppPredictionServicePackage;
     final @Nullable String mIncidentReportApproverPackage;
     final @Nullable String[] mTelephonyPackages;
-    final @NonNull String mServicesExtensionPackageName;
-    final @NonNull String mSharedSystemSharedLibraryPackageName;
+    final @Nullable String mServicesExtensionPackageName;
+    final @Nullable String mSharedSystemSharedLibraryPackageName;
     final @Nullable String mRetailDemoPackage;
 
     private final PackageUsage mPackageUsage = new PackageUsage();
@@ -1657,7 +1659,8 @@
                         handlePackagePostInstall(parentRes, grantPermissions,
                                 killApp, virtualPreload, grantedPermissions,
                                 whitelistedRestrictedPermissions, didRestore,
-                                args.installSource.installerPackageName, args.observer);
+                                args.installSource.installerPackageName, args.observer,
+                                args.mDataLoaderType);
 
                         // Handle the child packages
                         final int childCount = (parentRes.addedChildPackages != null)
@@ -1667,7 +1670,8 @@
                             handlePackagePostInstall(childRes, grantPermissions,
                                     killApp, virtualPreload, grantedPermissions,
                                     whitelistedRestrictedPermissions, false /*didRestore*/,
-                                    args.installSource.installerPackageName, args.observer);
+                                    args.installSource.installerPackageName, args.observer,
+                                    args.mDataLoaderType);
                         }
 
                         // Log tracing if needed
@@ -1994,7 +1998,7 @@
             boolean killApp, boolean virtualPreload,
             String[] grantedPermissions, List<String> whitelistedRestrictedPermissions,
             boolean launchedForRestore, String installerPackage,
-            IPackageInstallObserver2 installObserver) {
+            IPackageInstallObserver2 installObserver, int dataLoaderType) {
         final boolean succeeded = res.returnCode == PackageManager.INSTALL_SUCCEEDED;
         final boolean update = res.removedInfo != null && res.removedInfo.removedPackage != null;
 
@@ -2095,11 +2099,14 @@
                 if (update) {
                     extras.putBoolean(Intent.EXTRA_REPLACING, true);
                 }
+                extras.putInt(PackageInstaller.EXTRA_DATA_LOADER_TYPE, dataLoaderType);
+                // Send to all running apps.
                 sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
                         extras, 0 /*flags*/,
                         null /*targetPackage*/, null /*finishedReceiver*/,
                         updateUserIds, instantUserIds);
                 if (installerPackageName != null) {
+                    // Send to the installer, even if it's not running.
                     sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
                             extras, 0 /*flags*/,
                             installerPackageName, null /*finishedReceiver*/,
@@ -3155,8 +3162,8 @@
             mSetupWizardPackage = getSetupWizardPackageNameImpl();
             mComponentResolver.fixProtectedFilterPriorities();
 
-            mSystemTextClassifierPackage = getSystemTextClassifierPackageName();
-
+            mDefaultTextClassifierPackage = getDefaultTextClassifierPackageName();
+            mSystemTextClassifierPackageName = getSystemTextClassifierPackageName();
             mWellbeingPackage = getWellbeingPackageName();
             mDocumenterPackage = getDocumenterPackageName();
             mConfiguratorPackage = getDeviceConfiguratorPackageName();
@@ -3351,6 +3358,7 @@
                 mServicesExtensionPackageName = null;
                 mSharedSystemSharedLibraryPackageName = null;
             }
+
             // PermissionController hosts default permission granting and role management, so it's a
             // critical part of the core system.
             mRequiredPermissionControllerPackage = getRequiredPermissionControllerLPr();
@@ -13976,9 +13984,11 @@
         final int appId;
         final String seinfo;
         final int targetSdkVersion;
+        final String fromCodePath;
 
         public MoveInfo(int moveId, String fromUuid, String toUuid, String packageName,
-                String dataAppName, int appId, String seinfo, int targetSdkVersion) {
+                String dataAppName, int appId, String seinfo, int targetSdkVersion,
+                String fromCodePath) {
             this.moveId = moveId;
             this.fromUuid = fromUuid;
             this.toUuid = toUuid;
@@ -13987,6 +13997,7 @@
             this.appId = appId;
             this.seinfo = seinfo;
             this.targetSdkVersion = targetSdkVersion;
+            this.fromCodePath = fromCodePath;
         }
     }
 
@@ -14112,13 +14123,14 @@
         MultiPackageInstallParams mParentInstallParams;
         final long requiredInstalledVersionCode;
         final boolean forceQueryableOverride;
+        final int mDataLoaderType;
 
         InstallParams(OriginInfo origin, MoveInfo move, IPackageInstallObserver2 observer,
                 int installFlags, InstallSource installSource, String volumeUuid,
                 VerificationInfo verificationInfo, UserHandle user, String packageAbiOverride,
                 String[] grantedPermissions, List<String> whitelistedRestrictedPermissions,
                 SigningDetails signingDetails, int installReason,
-                long requiredInstalledVersionCode) {
+                long requiredInstalledVersionCode, int dataLoaderType) {
             super(user);
             this.origin = origin;
             this.move = move;
@@ -14134,40 +14146,42 @@
             this.installReason = installReason;
             this.requiredInstalledVersionCode = requiredInstalledVersionCode;
             this.forceQueryableOverride = false;
+            this.mDataLoaderType = dataLoaderType;
         }
 
         InstallParams(ActiveInstallSession activeInstallSession) {
             super(activeInstallSession.getUser());
+            final PackageInstaller.SessionParams sessionParams =
+                    activeInstallSession.getSessionParams();
             if (DEBUG_INSTANT) {
-                if ((activeInstallSession.getSessionParams().installFlags
+                if ((sessionParams.installFlags
                         & PackageManager.INSTALL_INSTANT_APP) != 0) {
                     Slog.d(TAG, "Ephemeral install of " + activeInstallSession.getPackageName());
                 }
             }
             verificationInfo = new VerificationInfo(
-                    activeInstallSession.getSessionParams().originatingUri,
-                    activeInstallSession.getSessionParams().referrerUri,
-                    activeInstallSession.getSessionParams().originatingUid,
+                    sessionParams.originatingUri,
+                    sessionParams.referrerUri,
+                    sessionParams.originatingUid,
                     activeInstallSession.getInstallerUid());
             origin = OriginInfo.fromStagedFile(activeInstallSession.getStagedDir());
             move = null;
             installReason = fixUpInstallReason(
                     activeInstallSession.getInstallSource().installerPackageName,
                     activeInstallSession.getInstallerUid(),
-                    activeInstallSession.getSessionParams().installReason);
+                    sessionParams.installReason);
             observer = activeInstallSession.getObserver();
-            installFlags = activeInstallSession.getSessionParams().installFlags;
+            installFlags = sessionParams.installFlags;
             installSource = activeInstallSession.getInstallSource();
-            volumeUuid = activeInstallSession.getSessionParams().volumeUuid;
-            packageAbiOverride = activeInstallSession.getSessionParams().abiOverride;
-            grantedRuntimePermissions = activeInstallSession.getSessionParams()
-                    .grantedRuntimePermissions;
-            whitelistedRestrictedPermissions = activeInstallSession.getSessionParams()
-                    .whitelistedRestrictedPermissions;
+            volumeUuid = sessionParams.volumeUuid;
+            packageAbiOverride = sessionParams.abiOverride;
+            grantedRuntimePermissions = sessionParams.grantedRuntimePermissions;
+            whitelistedRestrictedPermissions = sessionParams.whitelistedRestrictedPermissions;
             signingDetails = activeInstallSession.getSigningDetails();
-            requiredInstalledVersionCode = activeInstallSession.getSessionParams()
-                    .requiredInstalledVersionCode;
-            forceQueryableOverride = activeInstallSession.getSessionParams().forceQueryableOverride;
+            requiredInstalledVersionCode = sessionParams.requiredInstalledVersionCode;
+            forceQueryableOverride = sessionParams.forceQueryableOverride;
+            mDataLoaderType = (sessionParams.dataLoaderParams != null)
+                    ? sessionParams.dataLoaderParams.getType() : DataLoaderType.NONE;
         }
 
         @Override
@@ -14770,6 +14784,7 @@
         final int installReason;
         final boolean forceQueryableOverride;
         @Nullable final MultiPackageInstallParams mMultiPackageInstallParams;
+        final int mDataLoaderType;
 
         // The list of instruction sets supported by this app. This is currently
         // only used during the rmdex() phase to clean up resources. We can get rid of this
@@ -14783,7 +14798,7 @@
                 List<String> whitelistedRestrictedPermissions,
                 String traceMethod, int traceCookie, SigningDetails signingDetails,
                 int installReason, boolean forceQueryableOverride,
-                MultiPackageInstallParams multiPackageInstallParams) {
+                MultiPackageInstallParams multiPackageInstallParams, int dataLoaderType) {
             this.origin = origin;
             this.move = move;
             this.installFlags = installFlags;
@@ -14801,6 +14816,7 @@
             this.installReason = installReason;
             this.forceQueryableOverride = forceQueryableOverride;
             this.mMultiPackageInstallParams = multiPackageInstallParams;
+            this.mDataLoaderType = dataLoaderType;
         }
 
         /** New install */
@@ -14810,7 +14826,8 @@
                     params.getUser(), null /*instructionSets*/, params.packageAbiOverride,
                     params.grantedRuntimePermissions, params.whitelistedRestrictedPermissions,
                     params.traceMethod, params.traceCookie, params.signingDetails,
-                    params.installReason, params.forceQueryableOverride, params.mParentInstallParams);
+                    params.installReason, params.forceQueryableOverride,
+                    params.mParentInstallParams, params.mDataLoaderType);
         }
 
         abstract int copyApk();
@@ -14901,7 +14918,8 @@
             super(OriginInfo.fromNothing(), null, null, 0, InstallSource.EMPTY,
                     null, null, instructionSets, null, null, null, null, 0,
                     PackageParser.SigningDetails.UNKNOWN,
-                    PackageManager.INSTALL_REASON_UNKNOWN, false, null /* parent */);
+                    PackageManager.INSTALL_REASON_UNKNOWN, false, null /* parent */,
+                    DataLoaderType.NONE);
             this.codeFile = (codePath != null) ? new File(codePath) : null;
             this.resourceFile = (resourcePath != null) ? new File(resourcePath) : null;
         }
@@ -14981,9 +14999,7 @@
             try {
                 makeDirRecursive(afterCodeFile.getParentFile(), 0775);
                 if (onIncremental) {
-                    // TODO(b/147371381): fix incremental installation
-                    mIncrementalManager.rename(beforeCodeFile.getAbsolutePath(),
-                            afterCodeFile.getAbsolutePath());
+                    mIncrementalManager.renameCodePath(beforeCodeFile, afterCodeFile);
                 } else {
                     Os.rename(beforeCodeFile.getAbsolutePath(), afterCodeFile.getAbsolutePath());
                 }
@@ -15102,7 +15118,8 @@
             synchronized (mInstaller) {
                 try {
                     mInstaller.moveCompleteApp(move.fromUuid, move.toUuid, move.packageName,
-                            move.dataAppName, move.appId, move.seinfo, move.targetSdkVersion);
+                            move.dataAppName, move.appId, move.seinfo, move.targetSdkVersion,
+                            move.fromCodePath);
                 } catch (InstallerException e) {
                     Slog.w(TAG, "Failed to move app", e);
                     return PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
@@ -19626,15 +19643,15 @@
     }
 
     @Override
-    public String getSystemTextClassifierPackageName() {
-        return ensureSystemPackageName(mContext.getString(
-                R.string.config_defaultTextClassifierPackage));
+    public String getDefaultTextClassifierPackageName() {
+        return ensureSystemPackageName(
+                mContext.getString(R.string.config_servicesExtensionPackage));
     }
 
     @Override
-    public String[] getSystemTextClassifierPackages() {
-        return ensureSystemPackageNames(mContext.getResources().getStringArray(
-                R.array.config_defaultTextClassifierPackages));
+    public String getSystemTextClassifierPackageName() {
+        return ensureSystemPackageName(
+                mContext.getString(R.string.config_defaultTextClassifierPackage));
     }
 
     @Override
@@ -22052,6 +22069,7 @@
         final PackageFreezer freezer;
         final int[] installedUserIds;
         final boolean isCurrentLocationExternal;
+        final String fromCodePath;
 
         // reader
         synchronized (mLock) {
@@ -22108,6 +22126,7 @@
             targetSdkVersion = pkg.getTargetSdkVersion();
             freezer = freezePackage(packageName, "movePackageInternal");
             installedUserIds = ps.queryInstalledUsers(mUserManager.getUserIds(), true);
+            fromCodePath = pkg.getCodePath();
         }
 
         final Bundle extras = new Bundle();
@@ -22236,7 +22255,7 @@
 
             final String dataAppName = codeFile.getName();
             move = new MoveInfo(moveId, currentVolumeUuid, volumeUuid, packageName,
-                    dataAppName, appId, seinfo, targetSdkVersion);
+                    dataAppName, appId, seinfo, targetSdkVersion, fromCodePath);
         } else {
             move = null;
         }
@@ -22249,7 +22268,8 @@
                 installSource, volumeUuid, null /*verificationInfo*/, user,
                 packageAbiOverride, null /*grantedPermissions*/,
                 null /*whitelistedRestrictedPermissions*/, PackageParser.SigningDetails.UNKNOWN,
-                PackageManager.INSTALL_REASON_UNKNOWN, PackageManager.VERSION_CODE_HIGHEST);
+                PackageManager.INSTALL_REASON_UNKNOWN, PackageManager.VERSION_CODE_HIGHEST,
+                DataLoaderType.NONE);
         params.setTraceMethod("movePackage").setTraceCookie(System.identityHashCode(params));
         msg.obj = params;
 
@@ -22755,6 +22775,11 @@
 
     private class PackageManagerNative extends IPackageManagerNative.Stub {
         @Override
+        public String[] getAllPackages() {
+            return PackageManagerService.this.getAllPackages().toArray(new String[0]);
+        }
+
+        @Override
         public String[] getNamesForUids(int[] uids) throws RemoteException {
             final String[] results = PackageManagerService.this.getNamesForUids(uids);
             // massage results so they can be parsed by the native binder
@@ -23063,7 +23088,7 @@
         }
 
         private String[] getKnownPackageNamesInternal(int knownPackage, int userId) {
-            switch(knownPackage) {
+            switch (knownPackage) {
                 case PackageManagerInternal.PACKAGE_BROWSER:
                     return new String[]{mPermissionManager.getDefaultBrowser(userId)};
                 case PackageManagerInternal.PACKAGE_INSTALLER:
@@ -23075,7 +23100,8 @@
                 case PackageManagerInternal.PACKAGE_VERIFIER:
                     return filterOnlySystemPackages(mRequiredVerifierPackage);
                 case PackageManagerInternal.PACKAGE_SYSTEM_TEXT_CLASSIFIER:
-                    return filterOnlySystemPackages(mSystemTextClassifierPackage);
+                    return filterOnlySystemPackages(
+                            mDefaultTextClassifierPackage, mSystemTextClassifierPackageName);
                 case PackageManagerInternal.PACKAGE_PERMISSION_CONTROLLER:
                     return filterOnlySystemPackages(mRequiredPermissionControllerPackage);
                 case PackageManagerInternal.PACKAGE_WELLBEING:
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index f7889ea..d16c074 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -33,6 +33,7 @@
 import android.content.IntentFilter;
 import android.content.IntentSender;
 import android.content.IntentSender.SendIntentException;
+import android.content.LocusId;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
@@ -1985,7 +1986,6 @@
             // Verify if caller is the shortcut owner, only if caller doesn't have ACCESS_SHORTCUTS.
             verifyShortcutInfoPackage(callingPackage, shortcut);
         }
-        final String shortcutPackage = shortcut.getPackage();
 
         final boolean ret;
         synchronized (mLock) {
@@ -1999,6 +1999,7 @@
             // someone already), then we just replace the existing one with this new one,
             // and then proceed the rest of the process.
             if (shortcut != null) {
+                final String shortcutPackage = shortcut.getPackage();
                 final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(
                         shortcutPackage, userId);
                 final String id = shortcut.getId();
@@ -2155,48 +2156,6 @@
     }
 
     @Override
-    public ParceledListSlice<ShortcutInfo> getDynamicShortcuts(String packageName,
-            @UserIdInt int userId) {
-        verifyCaller(packageName, userId);
-
-        synchronized (mLock) {
-            throwIfUserLockedL(userId);
-
-            return getShortcutsWithQueryLocked(
-                    packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
-                    ShortcutInfo::isDynamicVisible);
-        }
-    }
-
-    @Override
-    public ParceledListSlice<ShortcutInfo> getManifestShortcuts(String packageName,
-            @UserIdInt int userId) {
-        verifyCaller(packageName, userId);
-
-        synchronized (mLock) {
-            throwIfUserLockedL(userId);
-
-            return getShortcutsWithQueryLocked(
-                    packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
-                    ShortcutInfo::isManifestVisible);
-        }
-    }
-
-    @Override
-    public ParceledListSlice<ShortcutInfo> getPinnedShortcuts(String packageName,
-            @UserIdInt int userId) {
-        verifyCaller(packageName, userId);
-
-        synchronized (mLock) {
-            throwIfUserLockedL(userId);
-
-            return getShortcutsWithQueryLocked(
-                    packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
-                    ShortcutInfo::isPinnedVisible);
-        }
-    }
-
-    @Override
     public ParceledListSlice<ShortcutInfo> getShortcuts(String packageName,
             @ShortcutManager.ShortcutMatchFlags int matchFlags, @UserIdInt int userId) {
         verifyCaller(packageName, userId);
@@ -2631,7 +2590,7 @@
         public List<ShortcutInfo> getShortcuts(int launcherUserId,
                 @NonNull String callingPackage, long changedSince,
                 @Nullable String packageName, @Nullable List<String> shortcutIds,
-                @Nullable ComponentName componentName,
+                @Nullable List<LocusId> locusIds, @Nullable ComponentName componentName,
                 int queryFlags, int userId, int callingPid, int callingUid) {
             final ArrayList<ShortcutInfo> ret = new ArrayList<>();
 
@@ -2652,15 +2611,16 @@
 
                 if (packageName != null) {
                     getShortcutsInnerLocked(launcherUserId,
-                            callingPackage, packageName, shortcutIds, changedSince,
+                            callingPackage, packageName, shortcutIds, locusIds, changedSince,
                             componentName, queryFlags, userId, ret, cloneFlag,
                             callingPid, callingUid);
                 } else {
                     final List<String> shortcutIdsF = shortcutIds;
+                    final List<LocusId> locusIdsF = locusIds;
                     getUserShortcutsLocked(userId).forAllPackages(p -> {
                         getShortcutsInnerLocked(launcherUserId,
-                                callingPackage, p.getPackageName(), shortcutIdsF, changedSince,
-                                componentName, queryFlags, userId, ret, cloneFlag,
+                                callingPackage, p.getPackageName(), shortcutIdsF, locusIdsF,
+                                changedSince, componentName, queryFlags, userId, ret, cloneFlag,
                                 callingPid, callingUid);
                     });
                 }
@@ -2670,12 +2630,15 @@
 
         @GuardedBy("ShortcutService.this.mLock")
         private void getShortcutsInnerLocked(int launcherUserId, @NonNull String callingPackage,
-                @Nullable String packageName, @Nullable List<String> shortcutIds, long changedSince,
+                @Nullable String packageName, @Nullable List<String> shortcutIds,
+                @Nullable List<LocusId> locusIds, long changedSince,
                 @Nullable ComponentName componentName, int queryFlags,
                 int userId, ArrayList<ShortcutInfo> ret, int cloneFlag,
                 int callingPid, int callingUid) {
             final ArraySet<String> ids = shortcutIds == null ? null
                     : new ArraySet<>(shortcutIds);
+            final ArraySet<LocusId> locIds = locusIds == null ? null
+                    : new ArraySet<>(locusIds);
 
             final ShortcutUser user = getUserShortcutsLocked(userId);
             final ShortcutPackage p = user.getPackageShortcutsIfExists(packageName);
@@ -2702,6 +2665,9 @@
                         if (ids != null && !ids.contains(si.getId())) {
                             return false;
                         }
+                        if (locIds != null && !locIds.contains(si.getLocusId())) {
+                            return false;
+                        }
                         if (componentName != null) {
                             if (si.getActivity() != null
                                     && !si.getActivity().equals(componentName)) {
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 46893b2..e6eaf21 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -720,12 +720,9 @@
                 userId, STORAGE_PERMISSIONS);
 
         // TextClassifier Service
-        final String[] packages = mContext.getPackageManager().getSystemTextClassifierPackages();
-        if (packages.length > 0) {
-            // We have a list of supported system TextClassifier package names, the first one
-            // package is the default system TextClassifier service. Grant permissions to default
-            // TextClassifier Service.
-            grantPermissionsToSystemPackage(packages[0], userId,
+        for (String textClassifierPackage :
+                getKnownPackages(PackageManagerInternal.PACKAGE_SYSTEM_TEXT_CLASSIFIER, userId)) {
+            grantPermissionsToSystemPackage(textClassifierPackage, userId,
                     COARSE_BACKGROUND_LOCATION_PERMISSIONS, CONTACTS_PERMISSIONS);
         }
 
diff --git a/services/core/java/com/android/server/policy/LegacyGlobalActions.java b/services/core/java/com/android/server/policy/LegacyGlobalActions.java
index 6daf516..6eba59a 100644
--- a/services/core/java/com/android/server/policy/LegacyGlobalActions.java
+++ b/services/core/java/com/android/server/policy/LegacyGlobalActions.java
@@ -402,7 +402,7 @@
         public String getStatus() {
             return mContext.getString(
                     com.android.internal.R.string.bugreport_status,
-                    Build.VERSION.RELEASE,
+                    Build.VERSION.RELEASE_OR_CODENAME,
                     Build.ID);
         }
     }
diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java
index bf7413b..4d7af9c 100644
--- a/services/core/java/com/android/server/rollback/Rollback.java
+++ b/services/core/java/com/android/server/rollback/Rollback.java
@@ -93,19 +93,6 @@
      */
     static final int ROLLBACK_STATE_DELETED = 4;
 
-    @IntDef(flag = true, prefix = { "MATCH_" }, value = {
-            MATCH_APK_IN_APEX,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    @interface RollbackInfoFlags {}
-
-    /**
-     * {@link RollbackInfo} flag: include {@code RollbackInfo} packages that are apk-in-apex.
-     * These packages do not have their own sessions. They are embedded in an apex which has a
-     * session id.
-     */
-    static final int MATCH_APK_IN_APEX = 1;
-
     /**
      * The session ID for the staged session if this rollback data represents a staged session,
      * {@code -1} otherwise.
@@ -783,33 +770,6 @@
     }
 
     /**
-     * Returns the number of {@link PackageRollbackInfo} we are storing in this {@link Rollback}
-     * instance. By default, this method does not include apk-in-apex package in the count.
-     *
-     * @param flags Apk-in-apex packages can be included in the count by passing
-     * {@link Rollback#MATCH_APK_IN_APEX}
-     *
-     * @return Counts number of {@link PackageRollbackInfo} stored in the {@link Rollback}
-     * according to {@code flags} passed
-     */
-    int getPackageCount(@RollbackInfoFlags int flags) {
-        synchronized (mLock) {
-            List<PackageRollbackInfo> packages = info.getPackages();
-            if ((flags & MATCH_APK_IN_APEX) != 0) {
-                return packages.size();
-            }
-
-            int packagesWithoutApkInApex = 0;
-            for (PackageRollbackInfo rollbackInfo : packages) {
-                if (!rollbackInfo.isApkInApex()) {
-                    packagesWithoutApkInApex++;
-                }
-            }
-            return packagesWithoutApkInApex;
-        }
-    }
-
-    /**
      * Adds a rollback token to be associated with this rollback. This may be used to
      * identify which rollback should be removed in case {@link PackageManager} sends an
      * {@link Intent#ACTION_CANCEL_ENABLE_ROLLBACK} intent.
@@ -842,13 +802,6 @@
     }
 
     /**
-     * Returns the number of package session ids in this rollback.
-     */
-    int getPackageSessionIdCount() {
-        return mPackageSessionIds.length;
-    }
-
-    /**
      * Called when a child session finished with success.
      * Returns true when all child sessions are notified with success. This rollback will be
      * enabled only after all child sessions finished with success.
@@ -859,6 +812,23 @@
         }
     }
 
+    /**
+     * Returns true if all packages in this rollback are enabled. We won't enable this rollback
+     * until all packages are enabled. Note we don't count apk-in-apex here since they are enabled
+     * automatically when the embedding apex is enabled.
+     */
+    boolean allPackagesEnabled() {
+        synchronized (mLock) {
+            int packagesWithoutApkInApex = 0;
+            for (PackageRollbackInfo rollbackInfo : info.getPackages()) {
+                if (!rollbackInfo.isApkInApex()) {
+                    packagesWithoutApkInApex++;
+                }
+            }
+            return packagesWithoutApkInApex == mPackageSessionIds.length;
+        }
+    }
+
     static String rollbackStateToString(@RollbackState int state) {
         switch (state) {
             case Rollback.ROLLBACK_STATE_ENABLING: return "enabling";
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 3fa114e..8bd9533 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -1237,8 +1237,7 @@
         // equal to the number of sessions we are installing, to ensure we didn't skip enabling
         // of any sessions. If we successfully enable an apex, then we can assume we enabled
         // rollback for the embedded apk-in-apex, if any.
-        // TODO: add a helper instead of exposing 2 methods from Rollback
-        if (rollback.getPackageCount(0 /*flags*/) != rollback.getPackageSessionIdCount()) {
+        if (!rollback.allPackagesEnabled()) {
             Slog.e(TAG, "Failed to enable rollback for all packages in session.");
             rollback.delete(mAppDataRollbackHelper);
             return null;
diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java
index 4f89482..7b046c1 100644
--- a/services/core/java/com/android/server/rollback/RollbackStore.java
+++ b/services/core/java/com/android/server/rollback/RollbackStore.java
@@ -199,11 +199,6 @@
      * Creates a new Rollback instance for a non-staged rollback with
      * backupDir assigned.
      */
-    Rollback createNonStagedRollback(int rollbackId, int userId, String installerPackageName) {
-        File backupDir = new File(mRollbackDataDir, Integer.toString(rollbackId));
-        return new Rollback(rollbackId, backupDir, -1, userId, installerPackageName);
-    }
-
     Rollback createNonStagedRollback(int rollbackId, int userId, String installerPackageName,
             int[] packageSessionIds) {
         File backupDir = new File(mRollbackDataDir, Integer.toString(rollbackId));
@@ -216,16 +211,6 @@
      * backupDir assigned.
      */
     Rollback createStagedRollback(int rollbackId, int stagedSessionId, int userId,
-            String installerPackageName) {
-        File backupDir = new File(mRollbackDataDir, Integer.toString(rollbackId));
-        return new Rollback(rollbackId, backupDir, stagedSessionId, userId, installerPackageName);
-    }
-
-    /**
-     * TODO: Now we have 4 factory methods for creating Rollback objects which is verbose and
-     * cumbersome. Need to merge them for simplicity.
-     */
-    Rollback createStagedRollback(int rollbackId, int stagedSessionId, int userId,
             String installerPackageName, int[] packageSessionIds) {
         File backupDir = new File(mRollbackDataDir, Integer.toString(rollbackId));
         return new Rollback(rollbackId, backupDir, stagedSessionId, userId, installerPackageName,
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 96f1219..5c79f6e 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -2480,7 +2480,7 @@
                 .writeString(Build.BRAND)
                 .writeString(Build.PRODUCT)
                 .writeString(Build.DEVICE)
-                .writeString(Build.VERSION.RELEASE)
+                .writeString(Build.VERSION.RELEASE_OR_CODENAME)
                 .writeString(Build.ID)
                 .writeString(Build.VERSION.INCREMENTAL)
                 .writeString(Build.TYPE)
diff --git a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
index 3dee853..34d2c16 100644
--- a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
+++ b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
@@ -36,7 +36,7 @@
 import android.service.textclassifier.TextClassifierService;
 import android.service.textclassifier.TextClassifierService.ConnectionState;
 import android.text.TextUtils;
-import android.util.ArrayMap;
+import android.util.LruCache;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.textclassifier.ConversationActions;
@@ -63,7 +63,8 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayDeque;
-import java.util.Map;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Objects;
 import java.util.Queue;
 
@@ -132,9 +133,7 @@
             synchronized (mManagerService.mLock) {
                 UserState userState = mManagerService.peekUserStateLocked(userId);
                 if (userState != null) {
-                    if (userState.mConnection != null) {
-                        userState.mConnection.cleanupService();
-                    }
+                    userState.cleanupServiceLocked();
                     mManagerService.mUserStates.remove(userId);
                 }
             }
@@ -147,22 +146,31 @@
     private final Object mLock;
     @GuardedBy("mLock")
     final SparseArray<UserState> mUserStates = new SparseArray<>();
+    // SystemTextClassifier.onDestroy() is not guaranteed to be called, use LruCache here
+    // to avoid leak.
     @GuardedBy("mLock")
-    private final Map<TextClassificationSessionId, Integer> mSessionUserIds = new ArrayMap<>();
-    @GuardedBy("mLock")
-    private TextClassificationConstants mSettings;
+    private final LruCache<TextClassificationSessionId, TextClassificationContext>
+            mSessionContextCache = new LruCache<>(40);
+    private final TextClassificationConstants mSettings;
+    @Nullable
+    private final String mDefaultTextClassifierPackage;
+    @Nullable
+    private final String mSystemTextClassifierPackage;
 
     private TextClassificationManagerService(Context context) {
         mContext = Objects.requireNonNull(context);
         mLock = new Object();
+        mSettings = new TextClassificationConstants();
         mSettingsListener = new TextClassifierSettingsListener(mContext);
+        PackageManager packageManager = mContext.getPackageManager();
+        mDefaultTextClassifierPackage = packageManager.getDefaultTextClassifierPackageName();
+        mSystemTextClassifierPackage = packageManager.getSystemTextClassifierPackageName();
     }
 
     private void startListenSettings() {
         mSettingsListener.registerObserver();
     }
 
-
     @Override
     public void onConnectedStateChanged(@ConnectionState int connected) {
     }
@@ -178,6 +186,7 @@
                 request.getUserId(),
                 request.getCallingPackageName(),
                 /* attemptToBind= */ true,
+                request.getUseDefaultTextClassifier(),
                 service -> service.onSuggestSelection(sessionId, request, callback),
                 "onSuggestSelection",
                 callback);
@@ -194,6 +203,7 @@
                 request.getUserId(),
                 request.getCallingPackageName(),
                 /* attemptToBind= */ true,
+                request.getUseDefaultTextClassifier(),
                 service -> service.onClassifyText(sessionId, request, callback),
                 "onClassifyText",
                 callback);
@@ -210,6 +220,7 @@
                 request.getUserId(),
                 request.getCallingPackageName(),
                 /* attemptToBind= */ true,
+                request.getUseDefaultTextClassifier(),
                 service -> service.onGenerateLinks(sessionId, request, callback),
                 "onGenerateLinks",
                 callback);
@@ -223,28 +234,32 @@
 
         handleRequest(
                 event.getUserId(),
-                event.getPackageName(),
+                /* callingPackageName= */ null,
                 /* attemptToBind= */ false,
+                event.getUseDefaultTextClassifier(),
                 service -> service.onSelectionEvent(sessionId, event),
                 "onSelectionEvent",
                 NO_OP_CALLBACK);
     }
+
     @Override
     public void onTextClassifierEvent(
             @Nullable TextClassificationSessionId sessionId,
             TextClassifierEvent event) throws RemoteException {
         Objects.requireNonNull(event);
 
-        final String packageName = event.getEventContext() == null
-                ? null
-                : event.getEventContext().getPackageName();
         final int userId = event.getEventContext() == null
                 ? UserHandle.getCallingUserId()
                 : event.getEventContext().getUserId();
+        final boolean useDefaultTextClassifier =
+                event.getEventContext() != null
+                        ? event.getEventContext().getUseDefaultTextClassifier()
+                        : true;
         handleRequest(
                 userId,
-                packageName,
+                /* callingPackageName= */ null,
                 /* attemptToBind= */ false,
+                useDefaultTextClassifier,
                 service -> service.onTextClassifierEvent(sessionId, event),
                 "onTextClassifierEvent",
                 NO_OP_CALLBACK);
@@ -261,6 +276,7 @@
                 request.getUserId(),
                 request.getCallingPackageName(),
                 /* attemptToBind= */ true,
+                request.getUseDefaultTextClassifier(),
                 service -> service.onDetectLanguage(sessionId, request, callback),
                 "onDetectLanguage",
                 callback);
@@ -277,6 +293,7 @@
                 request.getUserId(),
                 request.getCallingPackageName(),
                 /* attemptToBind= */ true,
+                request.getUseDefaultTextClassifier(),
                 service -> service.onSuggestConversationActions(sessionId, request, callback),
                 "onSuggestConversationActions",
                 callback);
@@ -294,9 +311,10 @@
                 userId,
                 classificationContext.getPackageName(),
                 /* attemptToBind= */ false,
+                classificationContext.getUseDefaultTextClassifier(),
                 service -> {
                     service.onCreateTextClassificationSession(classificationContext, sessionId);
-                    mSessionUserIds.put(sessionId, userId);
+                    mSessionContextCache.put(sessionId, classificationContext);
                 },
                 "onCreateTextClassificationSession",
                 NO_OP_CALLBACK);
@@ -308,16 +326,23 @@
         Objects.requireNonNull(sessionId);
 
         synchronized (mLock) {
-            final int userId = mSessionUserIds.containsKey(sessionId)
-                    ? mSessionUserIds.get(sessionId)
+            TextClassificationContext textClassificationContext =
+                    mSessionContextCache.get(sessionId);
+            final int userId = textClassificationContext != null
+                    ? textClassificationContext.getUserId()
                     : UserHandle.getCallingUserId();
+            final boolean useDefaultTextClassifier =
+                    textClassificationContext != null
+                            ? textClassificationContext.getUseDefaultTextClassifier()
+                            : true;
             handleRequest(
                     userId,
                     /* callingPackageName= */ null,
                     /* attemptToBind= */ false,
+                    useDefaultTextClassifier,
                     service -> {
                         service.onDestroyTextClassificationSession(sessionId);
-                        mSessionUserIds.remove(sessionId);
+                        mSessionContextCache.remove(sessionId);
                     },
                     "onDestroyTextClassificationSession",
                     NO_OP_CALLBACK);
@@ -328,7 +353,7 @@
     private UserState getUserStateLocked(int userId) {
         UserState result = mUserStates.get(userId);
         if (result == null) {
-            result = new UserState(userId, mContext, mLock);
+            result = new UserState(userId);
             mUserStates.put(userId, result);
         }
         return result;
@@ -339,6 +364,19 @@
         return mUserStates.get(userId);
     }
 
+    private int resolvePackageToUid(@Nullable String packageName, @UserIdInt int userId) {
+        if (packageName == null) {
+            return Process.INVALID_UID;
+        }
+        final PackageManager pm = mContext.getPackageManager();
+        try {
+            return pm.getPackageUidAsUser(packageName, userId);
+        } catch (PackageManager.NameNotFoundException e) {
+            Slog.e(LOG_TAG, "Could not get the UID for " + packageName);
+        }
+        return Process.INVALID_UID;
+    }
+
     @Override
     protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
         if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, fout)) return;
@@ -353,18 +391,25 @@
 
         pw.printPair("context", mContext);
         pw.println();
+        pw.printPair("defaultTextClassifierPackage", mDefaultTextClassifierPackage);
+        pw.println();
+        pw.printPair("systemTextClassifierPackage", mSystemTextClassifierPackage);
+        pw.println();
         synchronized (mLock) {
             int size = mUserStates.size();
-            pw.print("Number user states: "); pw.println(size);
+            pw.print("Number user states: ");
+            pw.println(size);
             if (size > 0) {
                 for (int i = 0; i < size; i++) {
                     pw.increaseIndent();
                     UserState userState = mUserStates.valueAt(i);
-                    pw.print(i); pw.print(":"); userState.dump(pw); pw.println();
+                    pw.printPair("User", mUserStates.keyAt(i));
+                    pw.println();
+                    userState.dump(pw);
                     pw.decreaseIndent();
                 }
             }
-            pw.println("Number of active sessions: " + mSessionUserIds.size());
+            pw.println("Number of active sessions: " + mSessionContextCache.size());
         }
     }
 
@@ -372,57 +417,55 @@
             @UserIdInt int userId,
             @Nullable String callingPackageName,
             boolean attemptToBind,
+            boolean useDefaultTextClassifier,
             @NonNull ThrowingConsumer<ITextClassifierService> textClassifierServiceConsumer,
             @NonNull String methodName,
-            @NonNull ITextClassifierCallback callback)
-            throws RemoteException {
+            @NonNull ITextClassifierCallback callback) throws RemoteException {
         Objects.requireNonNull(textClassifierServiceConsumer);
         Objects.requireNonNull(methodName);
         Objects.requireNonNull(callback);
 
-        validateInput(mContext, callingPackageName, userId);
+        try {
+            validateCallingPackage(callingPackageName);
+            validateUser(userId);
+        } catch (Exception e) {
+            throw new RemoteException("Invalid request: " + e.getMessage(), e,
+                    /* enableSuppression */ true, /* writableStackTrace */ true);
+        }
         synchronized (mLock) {
             UserState userState = getUserStateLocked(userId);
-            if (attemptToBind && !userState.bindLocked()) {
+            ServiceState serviceState =
+                    userState.getServiceStateLocked(useDefaultTextClassifier);
+            if (serviceState == null) {
+                Slog.d(LOG_TAG, "No configured system TextClassifierService");
+                callback.onFailure();
+            } else if (attemptToBind && !serviceState.bindLocked()) {
                 Slog.d(LOG_TAG, "Unable to bind TextClassifierService at " + methodName);
                 callback.onFailure();
-            } else if (userState.isBoundLocked()) {
-                if (!userState.checkRequestAcceptedLocked(Binder.getCallingUid(), methodName)) {
+            } else if (serviceState.isBoundLocked()) {
+                if (!serviceState.checkRequestAcceptedLocked(Binder.getCallingUid(), methodName)) {
                     return;
                 }
-                textClassifierServiceConsumer.accept(userState.mService);
+                textClassifierServiceConsumer.accept(serviceState.mService);
             } else {
-                userState.mPendingRequests.add(
+                serviceState.mPendingRequests.add(
                         new PendingRequest(
                                 methodName,
-                                () -> textClassifierServiceConsumer.accept(userState.mService),
+                                () -> textClassifierServiceConsumer.accept(serviceState.mService),
                                 callback::onFailure, callback.asBinder(),
                                 this,
-                                userState,
+                                serviceState,
                                 Binder.getCallingUid()));
             }
         }
     }
 
-    private void unbindServiceIfNecessary() {
-        final ComponentName serviceComponentName =
-                TextClassifierService.getServiceComponentName(mContext);
-        if (serviceComponentName == null) {
-            // It should not occur if we had defined default service names in config.xml
-            Slog.w(LOG_TAG, "No default configured system TextClassifierService.");
-            return;
-        }
+    private void onTextClassifierServicePackageOverrideChanged(String overriddenPackage) {
         synchronized (mLock) {
             final int size = mUserStates.size();
             for (int i = 0; i < size; i++) {
                 UserState userState = mUserStates.valueAt(i);
-                // Only unbind for a new service
-                if (userState.isCurrentlyBoundToComponentLocked(serviceComponentName)) {
-                    return;
-                }
-                if (userState.isBoundLocked()) {
-                    userState.unbindLocked();
-                }
+                userState.onTextClassifierServicePackageOverrideChangedLocked(overriddenPackage);
             }
         }
     }
@@ -430,27 +473,35 @@
     private static final class PendingRequest implements IBinder.DeathRecipient {
 
         private final int mUid;
-        @Nullable private final String mName;
-        @Nullable private final IBinder mBinder;
-        @NonNull private final Runnable mRequest;
-        @Nullable private final Runnable mOnServiceFailure;
+        @Nullable
+        private final String mName;
+        @Nullable
+        private final IBinder mBinder;
+        @NonNull
+        private final Runnable mRequest;
+        @Nullable
+        private final Runnable mOnServiceFailure;
         @GuardedBy("mLock")
-        @NonNull private final UserState mOwningUser;
-        @NonNull private final TextClassificationManagerService mService;
+        @NonNull
+        private final ServiceState mServiceState;
+        @NonNull
+        private final TextClassificationManagerService mService;
 
         /**
          * Initializes a new pending request.
-         * @param request action to perform when the service is bound
+         *
+         * @param request          action to perform when the service is bound
          * @param onServiceFailure action to perform when the service dies or disconnects
-         * @param binder binder to the process that made this pending request
-         * @param service
-         * @param owningUser
+         * @param binder           binder to the process that made this pending request
+         * @parm service           the TCMS instance.
+         * @param serviceState     the service state of the service that will execute the request.
+         * @param uid              the calling uid of the request.
          */
         PendingRequest(@Nullable String name,
                 @NonNull ThrowingRunnable request, @Nullable ThrowingRunnable onServiceFailure,
                 @Nullable IBinder binder,
-                TextClassificationManagerService service,
-                UserState owningUser, int uid) {
+                @NonNull TextClassificationManagerService service,
+                @NonNull ServiceState serviceState, int uid) {
             mName = name;
             mRequest =
                     logOnFailure(Objects.requireNonNull(request), "handling pending request");
@@ -458,7 +509,7 @@
                     logOnFailure(onServiceFailure, "notifying callback of service failure");
             mBinder = binder;
             mService = service;
-            mOwningUser = owningUser;
+            mServiceState = Objects.requireNonNull(serviceState);
             if (mBinder != null) {
                 try {
                     mBinder.linkToDeath(this, 0);
@@ -479,7 +530,7 @@
 
         @GuardedBy("mLock")
         private void removeLocked() {
-            mOwningUser.mPendingRequests.remove(this);
+            mServiceState.mPendingRequests.remove(this);
             if (mBinder != null) {
                 mBinder.unlinkToDeath(this, 0);
             }
@@ -492,59 +543,172 @@
                 e -> Slog.d(LOG_TAG, "Error " + opDesc + ": " + e.getMessage()));
     }
 
-    private static void validateInput(
-            Context context, @Nullable String packageName, @UserIdInt int userId)
-            throws RemoteException {
+    private void validateCallingPackage(@Nullable String callingPackage)
+            throws PackageManager.NameNotFoundException {
+        if (callingPackage != null) {
+            final int packageUid = mContext.getPackageManager()
+                    .getPackageUidAsUser(callingPackage, UserHandle.getCallingUserId());
+            final int callingUid = Binder.getCallingUid();
+            Preconditions.checkArgument(
+                    callingUid == packageUid
+                            // Trust the system process:
+                            || callingUid == android.os.Process.SYSTEM_UID,
+                    "Invalid package name. callingPackage=" + callingPackage
+                            + ", callingUid=" + callingUid);
+        }
+    }
 
-        try {
-            if (packageName != null) {
-                final int packageUid = context.getPackageManager()
-                        .getPackageUidAsUser(packageName, UserHandle.getCallingUserId());
-                final int callingUid = Binder.getCallingUid();
-                Preconditions.checkArgument(callingUid == packageUid
-                        // Trust the system process:
-                        || callingUid == android.os.Process.SYSTEM_UID,
-                        "Invalid package name. Package=" + packageName
-                                + ", CallingUid=" + callingUid);
-            }
-
-            Preconditions.checkArgument(userId != UserHandle.USER_NULL, "Null userId");
-            final int callingUserId = UserHandle.getCallingUserId();
-            if (callingUserId != userId) {
-                context.enforceCallingOrSelfPermission(
-                        android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
-                        "Invalid userId. UserId=" + userId + ", CallingUserId=" + callingUserId);
-            }
-        } catch (Exception e) {
-            throw new RemoteException("Invalid request: " + e.getMessage(), e,
-                    /* enableSuppression */ true, /* writableStackTrace */ true);
+    private void validateUser(@UserIdInt int userId) {
+        Preconditions.checkArgument(userId != UserHandle.USER_NULL, "Null userId");
+        final int callingUserId = UserHandle.getCallingUserId();
+        if (callingUserId != userId) {
+            mContext.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                    "Invalid userId. UserId=" + userId + ", CallingUserId=" + callingUserId);
         }
     }
 
     private final class UserState {
-        @UserIdInt final int mUserId;
+        @UserIdInt
+        final int mUserId;
+        @Nullable
+        private final ServiceState mDefaultServiceState;
+        @Nullable
+        private final ServiceState mSystemServiceState;
         @GuardedBy("mLock")
-        TextClassifierServiceConnection mConnection = null;
+        @Nullable
+        private ServiceState mUntrustedServiceState;
+
+        private UserState(int userId) {
+            mUserId = userId;
+            mDefaultServiceState = TextUtils.isEmpty(mDefaultTextClassifierPackage)
+                    ? null
+                    : new ServiceState(userId, mDefaultTextClassifierPackage, /* isTrusted= */true);
+            mSystemServiceState = TextUtils.isEmpty(mSystemTextClassifierPackage)
+                    ? null
+                    : new ServiceState(userId, mSystemTextClassifierPackage, /* isTrusted= */ true);
+        }
+
+        @GuardedBy("mLock")
+        @Nullable
+        ServiceState getServiceStateLocked(boolean useDefaultTextClassifier) {
+            if (useDefaultTextClassifier) {
+                return mDefaultServiceState;
+            }
+            String textClassifierServicePackageOverride =
+                    mSettings.getTextClassifierServicePackageOverride();
+            if (!TextUtils.isEmpty(textClassifierServicePackageOverride)) {
+                if (textClassifierServicePackageOverride.equals(mDefaultTextClassifierPackage)) {
+                    return mDefaultServiceState;
+                }
+                if (textClassifierServicePackageOverride.equals(mSystemTextClassifierPackage)
+                        && mSystemServiceState != null) {
+                    return mSystemServiceState;
+                }
+                if (mUntrustedServiceState == null) {
+                    mUntrustedServiceState =
+                            new ServiceState(
+                                    mUserId,
+                                    textClassifierServicePackageOverride,
+                                    /* isTrusted= */false);
+                }
+                return mUntrustedServiceState;
+            }
+            return mSystemServiceState != null ? mSystemServiceState : mDefaultServiceState;
+        }
+
+        @GuardedBy("mLock")
+        void onTextClassifierServicePackageOverrideChangedLocked(String overriddenPackageName) {
+            // The override config is just used for testing, and the flag value is not expected
+            // to change often. So, let's keep it simple and just unbind all the services here. The
+            // right service will be bound when the next request comes.
+            for (ServiceState serviceState : getAllServiceStatesLocked()) {
+                serviceState.unbindIfBoundLocked();
+            }
+            mUntrustedServiceState = null;
+        }
+
+        @GuardedBy("mLock")
+        void bindIfHasPendingRequestsLocked() {
+            for (ServiceState serviceState : getAllServiceStatesLocked()) {
+                serviceState.bindIfHasPendingRequestsLocked();
+            }
+        }
+
+        @GuardedBy("mLock")
+        void cleanupServiceLocked() {
+            for (ServiceState serviceState : getAllServiceStatesLocked()) {
+                if (serviceState.mConnection != null) {
+                    serviceState.mConnection.cleanupService();
+                }
+            }
+        }
+
+        @GuardedBy("mLock")
+        @NonNull
+        private List<ServiceState> getAllServiceStatesLocked() {
+            List<ServiceState> serviceStates = new ArrayList<>();
+            if (mDefaultServiceState != null) {
+                serviceStates.add(mDefaultServiceState);
+            }
+            if (mSystemServiceState != null) {
+                serviceStates.add(mSystemServiceState);
+            }
+            if (mUntrustedServiceState != null) {
+                serviceStates.add(mUntrustedServiceState);
+            }
+            return serviceStates;
+        }
+
+        void dump(IndentingPrintWriter pw) {
+            synchronized (mLock) {
+                pw.increaseIndent();
+                dump(pw, mDefaultServiceState, "Default");
+                dump(pw, mSystemServiceState, "System");
+                dump(pw, mUntrustedServiceState, "Untrusted");
+                pw.decreaseIndent();
+            }
+        }
+
+        private void dump(
+                IndentingPrintWriter pw, @Nullable ServiceState serviceState, String name) {
+            synchronized (mLock) {
+                if (serviceState != null) {
+                    pw.print(name + ": ");
+                    serviceState.dump(pw);
+                    pw.println();
+                }
+            }
+        }
+    }
+
+    private final class ServiceState {
+        @UserIdInt
+        final int mUserId;
+        @NonNull
+        final String mPackageName;
+        @NonNull
+        final TextClassifierServiceConnection mConnection;
+        final boolean mIsTrusted;
+        @NonNull
         @GuardedBy("mLock")
         final Queue<PendingRequest> mPendingRequests = new ArrayDeque<>();
+        @Nullable
         @GuardedBy("mLock")
         ITextClassifierService mService;
         @GuardedBy("mLock")
         boolean mBinding;
+        @Nullable
         @GuardedBy("mLock")
         ComponentName mBoundComponentName = null;
         @GuardedBy("mLock")
-        boolean mBoundToDefaultTrustService;
-        @GuardedBy("mLock")
-        int mBoundServiceUid;
+        int mBoundServiceUid = Process.INVALID_UID;
 
-        private final Context mContext;
-        private final Object mLock;
-
-        private UserState(int userId, Context context, Object lock) {
+        private ServiceState(@UserIdInt int userId, String packageName, boolean isTrusted) {
             mUserId = userId;
-            mContext = Objects.requireNonNull(context);
-            mLock = Objects.requireNonNull(lock);
+            mPackageName = packageName;
+            mConnection = new TextClassifierServiceConnection(mUserId);
+            mIsTrusted = isTrusted;
         }
 
         @GuardedBy("mLock")
@@ -581,18 +745,12 @@
         }
 
         @GuardedBy("mLock")
-        private boolean isCurrentlyBoundToComponentLocked(@NonNull ComponentName componentName) {
-            return (mBoundComponentName != null
-                    && mBoundComponentName.getPackageName().equals(
-                    componentName.getPackageName()));
-        }
-
-        @GuardedBy("mLock")
-        private void unbindLocked() {
-            Slog.v(LOG_TAG, "unbinding from " + mBoundComponentName + " for " + mUserId);
-            mContext.unbindService(mConnection);
-            mConnection.cleanupService();
-            mConnection = null;
+        void unbindIfBoundLocked() {
+            if (isBoundLocked()) {
+                Slog.v(LOG_TAG, "Unbinding " + mBoundComponentName + " for " + mUserId);
+                mContext.unbindService(mConnection);
+                mConnection.cleanupService();
+            }
         }
 
         /**
@@ -609,8 +767,7 @@
             final boolean willBind;
             final long identity = Binder.clearCallingIdentity();
             try {
-                final ComponentName componentName =
-                        TextClassifierService.getServiceComponentName(mContext);
+                final ComponentName componentName = getTextClassifierServiceComponent();
                 if (componentName == null) {
                     // Might happen if the storage is encrypted and the user is not unlocked
                     return false;
@@ -618,7 +775,6 @@
                 Intent serviceIntent = new Intent(TextClassifierService.SERVICE_INTERFACE)
                         .setComponent(componentName);
                 Slog.d(LOG_TAG, "Binding to " + serviceIntent.getComponent());
-                mConnection = new TextClassifierServiceConnection(mUserId);
                 willBind = mContext.bindServiceAsUser(
                         serviceIntent, mConnection,
                         Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
@@ -631,12 +787,21 @@
             return willBind;
         }
 
+        @Nullable
+        private ComponentName getTextClassifierServiceComponent() {
+            return TextClassifierService.getServiceComponentName(
+                    mContext,
+                    mPackageName,
+                    mIsTrusted ? PackageManager.MATCH_SYSTEM_ONLY : 0);
+        }
+
         private void dump(IndentingPrintWriter pw) {
             pw.printPair("context", mContext);
             pw.printPair("userId", mUserId);
             synchronized (mLock) {
+                pw.printPair("packageName", mPackageName);
                 pw.printPair("boundComponentName", mBoundComponentName);
-                pw.printPair("boundToDefaultTrustService", mBoundToDefaultTrustService);
+                pw.printPair("isTrusted", mIsTrusted);
                 pw.printPair("boundServiceUid", mBoundServiceUid);
                 pw.printPair("binding", mBinding);
                 pw.printPair("numberRequests", mPendingRequests.size());
@@ -645,7 +810,7 @@
 
         @GuardedBy("mLock")
         private boolean checkRequestAcceptedLocked(int requestUid, @NonNull String methodName) {
-            if (mBoundToDefaultTrustService || (requestUid == mBoundServiceUid)) {
+            if (mIsTrusted || (requestUid == mBoundServiceUid)) {
                 return true;
             }
             Slog.w(LOG_TAG, String.format(
@@ -654,47 +819,19 @@
             return false;
         }
 
-        private boolean isDefaultTrustService(@NonNull ComponentName currentService) {
-            final String[] defaultServiceNames =
-                    mContext.getPackageManager().getSystemTextClassifierPackages();
-            final String servicePackageName = currentService.getPackageName();
-
-            for (int i = 0; i < defaultServiceNames.length; i++) {
-                if (defaultServiceNames[i].equals(servicePackageName)) {
-                    return true;
-                }
-            }
-            return false;
-        }
-
-        private int getServiceUid(@Nullable ComponentName service, int userId) {
-            if (service == null) {
-                return Process.INVALID_UID;
-            }
-            final String servicePackageName = service.getPackageName();
-            final PackageManager pm = mContext.getPackageManager();
-            final int serviceUid;
-
-            try {
-                serviceUid = pm.getPackageUidAsUser(servicePackageName, userId);
-            } catch (PackageManager.NameNotFoundException e) {
-                Slog.e(LOG_TAG, "Could not verify UID for " + service);
-                return Process.INVALID_UID;
-            }
-            return serviceUid;
-        }
-
         @GuardedBy("mLock")
         private void updateServiceInfoLocked(int userId, @Nullable ComponentName componentName) {
             mBoundComponentName = componentName;
-            mBoundToDefaultTrustService = (mBoundComponentName != null && isDefaultTrustService(
-                    mBoundComponentName));
-            mBoundServiceUid = getServiceUid(mBoundComponentName, userId);
+            mBoundServiceUid =
+                    mBoundComponentName == null
+                            ? Process.INVALID_UID
+                            : resolvePackageToUid(mBoundComponentName.getPackageName(), userId);
         }
 
         private final class TextClassifierServiceConnection implements ServiceConnection {
 
-            @UserIdInt private final int mUserId;
+            @UserIdInt
+            private final int mUserId;
 
             TextClassifierServiceConnection(int userId) {
                 mUserId = userId;
@@ -745,18 +882,18 @@
 
     private final class TextClassifierSettingsListener implements
             DeviceConfig.OnPropertiesChangedListener {
+        @NonNull
+        private final Context mContext;
+        @Nullable
+        private String mServicePackageOverride;
 
-        @NonNull private final Context mContext;
-        @NonNull private final TextClassificationConstants mSettings;
-        @Nullable private String mServicePackageName;
 
         TextClassifierSettingsListener(Context context) {
             mContext = context;
-            mSettings = TextClassificationManager.getSettings(mContext);
-            mServicePackageName = mSettings.getTextClassifierServicePackageOverride();
+            mServicePackageOverride = mSettings.getTextClassifierServicePackageOverride();
         }
 
-        public void registerObserver() {
+        void registerObserver() {
             DeviceConfig.addOnPropertiesChangedListener(
                     DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
                     mContext.getMainExecutor(),
@@ -765,13 +902,13 @@
 
         @Override
         public void onPropertiesChanged(DeviceConfig.Properties properties) {
-            final String overrideServiceName = mSettings.getTextClassifierServicePackageOverride();
-
-            if (TextUtils.equals(overrideServiceName, mServicePackageName)) {
+            final String currentServicePackageOverride =
+                    mSettings.getTextClassifierServicePackageOverride();
+            if (TextUtils.equals(currentServicePackageOverride, mServicePackageOverride)) {
                 return;
             }
-            mServicePackageName = overrideServiceName;
-            unbindServiceIfNecessary();
+            mServicePackageOverride = currentServicePackageOverride;
+            onTextClassifierServicePackageOverrideChanged(currentServicePackageOverride);
         }
     }
 }
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
index b7d6360..ed6424c 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
@@ -21,7 +21,7 @@
 import android.app.timedetector.ITimeDetectorService;
 import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.NetworkTimeSuggestion;
-import android.app.timedetector.PhoneTimeSuggestion;
+import android.app.timedetector.TelephonyTimeSuggestion;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.database.ContentObserver;
@@ -37,6 +37,9 @@
 import java.io.PrintWriter;
 import java.util.Objects;
 
+/**
+ * The implementation of ITimeDetectorService.aidl.
+ */
 public final class TimeDetectorService extends ITimeDetectorService.Stub {
     private static final String TAG = "TimeDetectorService";
 
@@ -75,7 +78,7 @@
                 Settings.Global.getUriFor(Settings.Global.AUTO_TIME), true,
                 new ContentObserver(handler) {
                     public void onChange(boolean selfChange) {
-                        timeDetectorService.handleAutoTimeDetectionToggle();
+                        timeDetectorService.handleAutoTimeDetectionChanged();
                     }
                 });
 
@@ -91,11 +94,11 @@
     }
 
     @Override
-    public void suggestPhoneTime(@NonNull PhoneTimeSuggestion timeSignal) {
-        enforceSuggestPhoneTimePermission();
+    public void suggestTelephonyTime(@NonNull TelephonyTimeSuggestion timeSignal) {
+        enforceSuggestTelephonyTimePermission();
         Objects.requireNonNull(timeSignal);
 
-        mHandler.post(() -> mTimeDetectorStrategy.suggestPhoneTime(timeSignal));
+        mHandler.post(() -> mTimeDetectorStrategy.suggestTelephonyTime(timeSignal));
     }
 
     @Override
@@ -114,8 +117,9 @@
         mHandler.post(() -> mTimeDetectorStrategy.suggestNetworkTime(timeSignal));
     }
 
+    /** Internal method for handling the auto time setting being changed. */
     @VisibleForTesting
-    public void handleAutoTimeDetectionToggle() {
+    public void handleAutoTimeDetectionChanged() {
         mHandler.post(mTimeDetectorStrategy::handleAutoTimeDetectionChanged);
     }
 
@@ -127,10 +131,10 @@
         mTimeDetectorStrategy.dump(pw, args);
     }
 
-    private void enforceSuggestPhoneTimePermission() {
+    private void enforceSuggestTelephonyTimePermission() {
         mContext.enforceCallingPermission(
-                android.Manifest.permission.SUGGEST_PHONE_TIME_AND_ZONE,
-                "suggest phone time and time zone");
+                android.Manifest.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE,
+                "suggest telephony time and time zone");
     }
 
     private void enforceSuggestManualTimePermission() {
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
index 468b806..a5fba4e 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
@@ -20,14 +20,14 @@
 import android.annotation.Nullable;
 import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.NetworkTimeSuggestion;
-import android.app.timedetector.PhoneTimeSuggestion;
+import android.app.timedetector.TelephonyTimeSuggestion;
 import android.os.TimestampedValue;
 
 import java.io.PrintWriter;
 
 /**
- * The interface for classes that implement the time detection algorithm used by the
- * TimeDetectorService.
+ * The interface for the class that implements the time detection algorithm used by the
+ * {@link TimeDetectorService}.
  *
  * <p>Most calls will be handled by a single thread but that is not true for all calls. For example
  * {@link #dump(PrintWriter, String[])}) may be called on a different thread so implementations must
@@ -78,7 +78,7 @@
     void initialize(@NonNull Callback callback);
 
     /** Process the suggested time from telephony sources. */
-    void suggestPhoneTime(@NonNull PhoneTimeSuggestion timeSuggestion);
+    void suggestTelephonyTime(@NonNull TelephonyTimeSuggestion timeSuggestion);
 
     /** Process the suggested manually entered time. */
     void suggestManualTime(@NonNull ManualTimeSuggestion timeSuggestion);
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
index a1e643f..8c54fa9 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
@@ -22,7 +22,7 @@
 import android.app.AlarmManager;
 import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.NetworkTimeSuggestion;
-import android.app.timedetector.PhoneTimeSuggestion;
+import android.app.timedetector.TelephonyTimeSuggestion;
 import android.os.TimestampedValue;
 import android.util.LocalLog;
 import android.util.Slog;
@@ -38,9 +38,9 @@
 import java.lang.annotation.RetentionPolicy;
 
 /**
- * An implementation of TimeDetectorStrategy that passes phone and manual suggestions to
- * {@link AlarmManager}. When there are multiple phone sources, the one with the lowest ID is used
- * unless the data becomes too stale.
+ * An implementation of {@link TimeDetectorStrategy} that passes telephony and manual suggestions to
+ * {@link AlarmManager}. When there are multiple telephony sources, the one with the lowest ID is
+ * used unless the data becomes too stale.
  *
  * <p>Most public methods are marked synchronized to ensure thread safety around internal state.
  */
@@ -50,23 +50,26 @@
     private static final String LOG_TAG = "SimpleTimeDetectorStrategy";
 
     /** A score value used to indicate "no score", either due to validation failure or age. */
-    private static final int PHONE_INVALID_SCORE = -1;
-    /** The number of buckets phone suggestions can be put in by age. */
-    private static final int PHONE_BUCKET_COUNT = 24;
+    private static final int TELEPHONY_INVALID_SCORE = -1;
+    /** The number of buckets telephony suggestions can be put in by age. */
+    private static final int TELEPHONY_BUCKET_COUNT = 24;
     /** Each bucket is this size. All buckets are equally sized. */
     @VisibleForTesting
-    static final int PHONE_BUCKET_SIZE_MILLIS = 60 * 60 * 1000;
-    /** Phone and network suggestions older than this value are considered too old to be used. */
+    static final int TELEPHONY_BUCKET_SIZE_MILLIS = 60 * 60 * 1000;
+    /**
+     * Telephony and network suggestions older than this value are considered too old to be used.
+     */
     @VisibleForTesting
-    static final long MAX_UTC_TIME_AGE_MILLIS = PHONE_BUCKET_COUNT * PHONE_BUCKET_SIZE_MILLIS;
+    static final long MAX_UTC_TIME_AGE_MILLIS =
+            TELEPHONY_BUCKET_COUNT * TELEPHONY_BUCKET_SIZE_MILLIS;
 
-    @IntDef({ ORIGIN_PHONE, ORIGIN_MANUAL, ORIGIN_NETWORK })
+    @IntDef({ ORIGIN_TELEPHONY, ORIGIN_MANUAL, ORIGIN_NETWORK })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Origin {}
 
     /** Used when a time value originated from a telephony signal. */
     @Origin
-    private static final int ORIGIN_PHONE = 1;
+    private static final int ORIGIN_TELEPHONY = 1;
 
     /** Used when a time value originated from a user / manual settings. */
     @Origin
@@ -83,7 +86,9 @@
      */
     private static final long SYSTEM_CLOCK_PARANOIA_THRESHOLD_MILLIS = 2 * 1000;
 
-    /** The number of previous phone suggestions to keep for each ID (for use during debugging). */
+    /**
+     * The number of previous telephony suggestions to keep for each ID (for use during debugging).
+     */
     private static final int KEEP_SUGGESTION_HISTORY_SIZE = 30;
 
     // A log for changes made to the system clock and why.
@@ -106,7 +111,7 @@
      * stable.
      */
     @GuardedBy("this")
-    private final ArrayMapWithHistory<Integer, PhoneTimeSuggestion> mSuggestionBySlotIndex =
+    private final ArrayMapWithHistory<Integer, TelephonyTimeSuggestion> mSuggestionBySlotIndex =
             new ArrayMapWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE);
 
     @GuardedBy("this")
@@ -144,7 +149,7 @@
     }
 
     @Override
-    public synchronized void suggestPhoneTime(@NonNull PhoneTimeSuggestion timeSuggestion) {
+    public synchronized void suggestTelephonyTime(@NonNull TelephonyTimeSuggestion timeSuggestion) {
         // Empty time suggestion means that telephony network connectivity has been lost.
         // The passage of time is relentless, and we don't expect our users to use a time machine,
         // so we can continue relying on previous suggestions when we lose connectivity. This is
@@ -157,13 +162,13 @@
 
         // Perform validation / input filtering and record the validated suggestion against the
         // slotIndex.
-        if (!validateAndStorePhoneSuggestion(timeSuggestion)) {
+        if (!validateAndStoreTelephonySuggestion(timeSuggestion)) {
             return;
         }
 
         // Now perform auto time detection. The new suggestion may be used to modify the system
         // clock.
-        String reason = "New phone time suggested. timeSuggestion=" + timeSuggestion;
+        String reason = "New telephony time suggested. timeSuggestion=" + timeSuggestion;
         doAutoTimeDetection(reason);
     }
 
@@ -201,7 +206,7 @@
         mTimeChangesLog.dump(ipw);
         ipw.decreaseIndent(); // level 2
 
-        ipw.println("Phone suggestion history:");
+        ipw.println("Telephony suggestion history:");
         ipw.increaseIndent(); // level 2
         mSuggestionBySlotIndex.dump(ipw);
         ipw.decreaseIndent(); // level 2
@@ -216,7 +221,8 @@
     }
 
     @GuardedBy("this")
-    private boolean validateAndStorePhoneSuggestion(@NonNull PhoneTimeSuggestion suggestion) {
+    private boolean validateAndStoreTelephonySuggestion(
+            @NonNull TelephonyTimeSuggestion suggestion) {
         TimestampedValue<Long> newUtcTime = suggestion.getUtcTime();
         if (!validateSuggestionTime(newUtcTime, suggestion)) {
             // There's probably nothing useful we can do: elsewhere we assume that reference
@@ -225,7 +231,7 @@
         }
 
         int slotIndex = suggestion.getSlotIndex();
-        PhoneTimeSuggestion previousSuggestion = mSuggestionBySlotIndex.get(slotIndex);
+        TelephonyTimeSuggestion previousSuggestion = mSuggestionBySlotIndex.get(slotIndex);
         if (previousSuggestion != null) {
             // We can log / discard suggestions with obvious issues with the reference time clock.
             if (previousSuggestion.getUtcTime() == null
@@ -241,7 +247,7 @@
                     newUtcTime, previousSuggestion.getUtcTime());
             if (referenceTimeDifference < 0) {
                 // The reference time is before the previously received suggestion. Ignore it.
-                Slog.w(LOG_TAG, "Out of order phone suggestion received."
+                Slog.w(LOG_TAG, "Out of order telephony suggestion received."
                         + " referenceTimeDifference=" + referenceTimeDifference
                         + " previousSuggestion=" + previousSuggestion
                         + " suggestion=" + suggestion);
@@ -282,18 +288,18 @@
 
         // Android devices currently prioritize any telephony over network signals. There are
         // carrier compliance tests that would need to be changed before we could ignore NITZ or
-        // prefer NTP generally. This check is cheap on devices without phone hardware.
-        PhoneTimeSuggestion bestPhoneSuggestion = findBestPhoneSuggestion();
-        if (bestPhoneSuggestion != null) {
-            final TimestampedValue<Long> newUtcTime = bestPhoneSuggestion.getUtcTime();
-            String cause = "Found good phone suggestion."
-                    + ", bestPhoneSuggestion=" + bestPhoneSuggestion
+        // prefer NTP generally. This check is cheap on devices without telephony hardware.
+        TelephonyTimeSuggestion bestTelephonySuggestion = findBestTelephonySuggestion();
+        if (bestTelephonySuggestion != null) {
+            final TimestampedValue<Long> newUtcTime = bestTelephonySuggestion.getUtcTime();
+            String cause = "Found good telephony suggestion."
+                    + ", bestTelephonySuggestion=" + bestTelephonySuggestion
                     + ", detectionReason=" + detectionReason;
-            setSystemClockIfRequired(ORIGIN_PHONE, newUtcTime, cause);
+            setSystemClockIfRequired(ORIGIN_TELEPHONY, newUtcTime, cause);
             return;
         }
 
-        // There is no good phone suggestion, try network.
+        // There is no good telephony suggestion, try network.
         NetworkTimeSuggestion networkSuggestion = findLatestValidNetworkSuggestion();
         if (networkSuggestion != null) {
             final TimestampedValue<Long> newUtcTime = networkSuggestion.getUtcTime();
@@ -305,18 +311,18 @@
         }
 
         if (DBG) {
-            Slog.d(LOG_TAG, "Could not determine time: No best phone or network suggestion."
+            Slog.d(LOG_TAG, "Could not determine time: No best telephony or network suggestion."
                     + " detectionReason=" + detectionReason);
         }
     }
 
     @GuardedBy("this")
     @Nullable
-    private PhoneTimeSuggestion findBestPhoneSuggestion() {
+    private TelephonyTimeSuggestion findBestTelephonySuggestion() {
         long elapsedRealtimeMillis = mCallback.elapsedRealtimeMillis();
 
-        // Phone time suggestions are assumed to be derived from NITZ or NITZ-like signals. These
-        // have a number of limitations:
+        // Telephony time suggestions are assumed to be derived from NITZ or NITZ-like signals.
+        // These have a number of limitations:
         // 1) No guarantee of accuracy ("accuracy of the time information is in the order of
         // minutes") [1]
         // 2) No guarantee of regular signals ("dependent on the handset crossing radio network
@@ -335,8 +341,8 @@
         // 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
-        // discrete age bucket, i.e. so signals received around the same time will be in the same
+        // Recency: The most recent suggestion from each slotIndex is scored. The score is based on
+        // a discrete age bucket, i.e. so signals received around the same time will be in the same
         // 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
@@ -345,11 +351,11 @@
         // 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;
+        TelephonyTimeSuggestion bestSuggestion = null;
+        int bestScore = TELEPHONY_INVALID_SCORE;
         for (int i = 0; i < mSuggestionBySlotIndex.size(); i++) {
             Integer slotIndex = mSuggestionBySlotIndex.keyAt(i);
-            PhoneTimeSuggestion candidateSuggestion = mSuggestionBySlotIndex.valueAt(i);
+            TelephonyTimeSuggestion candidateSuggestion = mSuggestionBySlotIndex.valueAt(i);
             if (candidateSuggestion == null) {
                 // Unexpected - null suggestions should never be stored.
                 Slog.w(LOG_TAG, "Latest suggestion unexpectedly null for slotIndex."
@@ -362,8 +368,9 @@
                 continue;
             }
 
-            int candidateScore = scorePhoneSuggestion(elapsedRealtimeMillis, candidateSuggestion);
-            if (candidateScore == PHONE_INVALID_SCORE) {
+            int candidateScore =
+                    scoreTelephonySuggestion(elapsedRealtimeMillis, candidateSuggestion);
+            if (candidateScore == TELEPHONY_INVALID_SCORE) {
                 // Expected: This means the suggestion is obviously invalid or just too old.
                 continue;
             }
@@ -384,8 +391,8 @@
         return bestSuggestion;
     }
 
-    private static int scorePhoneSuggestion(
-            long elapsedRealtimeMillis, @NonNull PhoneTimeSuggestion timeSuggestion) {
+    private static int scoreTelephonySuggestion(
+            long elapsedRealtimeMillis, @NonNull TelephonyTimeSuggestion timeSuggestion) {
 
         // Validate first.
         TimestampedValue<Long> utcTime = timeSuggestion.getUtcTime();
@@ -393,21 +400,21 @@
             Slog.w(LOG_TAG, "Existing suggestion found to be invalid "
                     + " elapsedRealtimeMillis=" + elapsedRealtimeMillis
                     + ", timeSuggestion=" + timeSuggestion);
-            return PHONE_INVALID_SCORE;
+            return TELEPHONY_INVALID_SCORE;
         }
 
         // The score is based on the age since receipt. Suggestions are bucketed so two
         // 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.
-        int bucketIndex = (int) (ageMillis / PHONE_BUCKET_SIZE_MILLIS);
-        if (bucketIndex >= PHONE_BUCKET_COUNT) {
-            return PHONE_INVALID_SCORE;
+        // Turn the age into a discrete value: 0 <= bucketIndex < TELEPHONY_BUCKET_COUNT.
+        int bucketIndex = (int) (ageMillis / TELEPHONY_BUCKET_SIZE_MILLIS);
+        if (bucketIndex >= TELEPHONY_BUCKET_COUNT) {
+            return TELEPHONY_INVALID_SCORE;
         }
 
         // We want the lowest bucket index to have the highest score. 0 > score >= BUCKET_COUNT.
-        return PHONE_BUCKET_COUNT - bucketIndex;
+        return TELEPHONY_BUCKET_COUNT - bucketIndex;
     }
 
     /** Returns the latest, valid, network suggestion. Returns {@code null} if there isn't one. */
@@ -537,13 +544,13 @@
     }
 
     /**
-     * Returns the current best phone suggestion. Not intended for general use: it is used during
-     * tests to check strategy behavior.
+     * Returns the current best telephony suggestion. Not intended for general use: it is used
+     * during tests to check strategy behavior.
      */
     @VisibleForTesting
     @Nullable
-    public synchronized PhoneTimeSuggestion findBestPhoneSuggestionForTests() {
-        return findBestPhoneSuggestion();
+    public synchronized TelephonyTimeSuggestion findBestTelephonySuggestionForTests() {
+        return findBestTelephonySuggestion();
     }
 
     /**
@@ -561,7 +568,7 @@
      */
     @VisibleForTesting
     @Nullable
-    public synchronized PhoneTimeSuggestion getLatestPhoneSuggestion(int slotIndex) {
+    public synchronized TelephonyTimeSuggestion getLatestTelephonySuggestion(int slotIndex) {
         return mSuggestionBySlotIndex.get(slotIndex);
     }
 
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
index adf6d7e..2520316 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
@@ -24,9 +24,9 @@
 import android.provider.Settings;
 
 /**
- * The real implementation of {@link TimeZoneDetectorStrategy.Callback}.
+ * The real implementation of {@link TimeZoneDetectorStrategyImpl.Callback}.
  */
-public final class TimeZoneDetectorCallbackImpl implements TimeZoneDetectorStrategy.Callback {
+public final class TimeZoneDetectorCallbackImpl implements TimeZoneDetectorStrategyImpl.Callback {
 
     private static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
 
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
index 9a1fe65..57b6ec9 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
@@ -20,7 +20,7 @@
 import android.annotation.Nullable;
 import android.app.timezonedetector.ITimeZoneDetectorService;
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
-import android.app.timezonedetector.PhoneTimeZoneSuggestion;
+import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.database.ContentObserver;
@@ -67,19 +67,21 @@
 
     private static TimeZoneDetectorService create(@NonNull Context context) {
         final TimeZoneDetectorStrategy timeZoneDetectorStrategy =
-                TimeZoneDetectorStrategy.create(context);
+                TimeZoneDetectorStrategyImpl.create(context);
 
         Handler handler = FgThread.getHandler();
+        TimeZoneDetectorService service =
+                new TimeZoneDetectorService(context, handler, timeZoneDetectorStrategy);
+
         ContentResolver contentResolver = context.getContentResolver();
         contentResolver.registerContentObserver(
                 Settings.Global.getUriFor(Settings.Global.AUTO_TIME_ZONE), true,
                 new ContentObserver(handler) {
                     public void onChange(boolean selfChange) {
-                        timeZoneDetectorStrategy.handleAutoTimeZoneDetectionChange();
+                        service.handleAutoTimeZoneDetectionChanged();
                     }
                 });
-
-        return new TimeZoneDetectorService(context, handler, timeZoneDetectorStrategy);
+        return service;
     }
 
     @VisibleForTesting
@@ -99,11 +101,11 @@
     }
 
     @Override
-    public void suggestPhoneTimeZone(@NonNull PhoneTimeZoneSuggestion timeZoneSuggestion) {
-        enforceSuggestPhoneTimeZonePermission();
+    public void suggestTelephonyTimeZone(@NonNull TelephonyTimeZoneSuggestion timeZoneSuggestion) {
+        enforceSuggestTelephonyTimeZonePermission();
         Objects.requireNonNull(timeZoneSuggestion);
 
-        mHandler.post(() -> mTimeZoneDetectorStrategy.suggestPhoneTimeZone(timeZoneSuggestion));
+        mHandler.post(() -> mTimeZoneDetectorStrategy.suggestTelephonyTimeZone(timeZoneSuggestion));
     }
 
     @Override
@@ -111,17 +113,25 @@
             @Nullable String[] args) {
         if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
 
-        mTimeZoneDetectorStrategy.dumpState(pw, args);
+        mTimeZoneDetectorStrategy.dump(pw, args);
     }
 
-    private void enforceSuggestPhoneTimeZonePermission() {
+    /** Internal method for handling the auto time zone setting being changed. */
+    @VisibleForTesting
+    public void handleAutoTimeZoneDetectionChanged() {
+        mHandler.post(mTimeZoneDetectorStrategy::handleAutoTimeZoneDetectionChanged);
+    }
+
+    private void enforceSuggestTelephonyTimeZonePermission() {
         mContext.enforceCallingPermission(
-                android.Manifest.permission.SET_TIME_ZONE, "set time zone");
+                android.Manifest.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE,
+                "suggest telephony time and time zone");
     }
 
     private void enforceSuggestManualTimeZonePermission() {
         mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.SET_TIME_ZONE, "set time zone");
+                android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE,
+                "suggest manual time and time zone");
     }
 }
 
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
index b0e0069..0eb27cc 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
@@ -15,507 +15,44 @@
  */
 package com.android.server.timezonedetector;
 
-import static android.app.timezonedetector.PhoneTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID;
-import static android.app.timezonedetector.PhoneTimeZoneSuggestion.MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY;
-import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS;
-import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET;
-import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_SINGLE_ZONE;
-
-import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
-import android.app.timezonedetector.PhoneTimeZoneSuggestion;
-import android.content.Context;
-import android.util.LocalLog;
-import android.util.Slog;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.IndentingPrintWriter;
+import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
 
 import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.Objects;
 
 /**
- * A singleton, stateful time zone detection strategy that is aware of user (manual) suggestions and
- * suggestions from multiple phone devices. Suggestions are acted on or ignored as needed, dependent
- * on the current "auto time zone detection" setting.
+ * The interface for the class that implement the time detection algorithm used by the
+ * {@link TimeZoneDetectorService}.
  *
- * <p>For automatic detection it keeps track of the most recent suggestion from each phone it uses
- * the best suggestion based on a scoring algorithm. If several phones provide the same score then
- * the phone with the lowest numeric ID "wins". If the situation changes and it is no longer
- * possible to be confident about the time zone, phones must submit an empty suggestion in order to
- * "withdraw" their previous suggestion.
+ * <p>Most calls will be handled by a single thread but that is not true for all calls. For example
+ * {@link #dump(PrintWriter, String[])}) may be called on a different thread so implementations must
+ * handle thread safety.
+ *
+ * @hide
  */
-public class TimeZoneDetectorStrategy {
+public interface TimeZoneDetectorStrategy {
 
-    /**
-     * Used by {@link TimeZoneDetectorStrategy} to interact with the surrounding service. It can be
-     * faked for tests.
-     *
-     * <p>Note: Because the system properties-derived values like
-     * {@link #isAutoTimeZoneDetectionEnabled()}, {@link #isAutoTimeZoneDetectionEnabled()},
-     * {@link #getDeviceTimeZone()} can be modified independently and from different threads (and
-     * processes!), their use are prone to race conditions. That will be true until the
-     * responsibility for setting their values is moved to {@link TimeZoneDetectorStrategy}.
-     */
-    @VisibleForTesting
-    public interface Callback {
-
-        /**
-         * Returns true if automatic time zone detection is enabled in settings.
-         */
-        boolean isAutoTimeZoneDetectionEnabled();
-
-        /**
-         * Returns true if the device has had an explicit time zone set.
-         */
-        boolean isDeviceTimeZoneInitialized();
-
-        /**
-         * Returns the device's currently configured time zone.
-         */
-        String getDeviceTimeZone();
-
-        /**
-         * Sets the device's time zone.
-         */
-        void setDeviceTimeZone(@NonNull String zoneId);
-    }
-
-    private static final String LOG_TAG = "TimeZoneDetectorStrategy";
-    private static final boolean DBG = false;
-
-    @IntDef({ ORIGIN_PHONE, ORIGIN_MANUAL })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface Origin {}
-
-    /** Used when a time value originated from a telephony signal. */
-    @Origin
-    private static final int ORIGIN_PHONE = 1;
-
-    /** Used when a time value originated from a user / manual settings. */
-    @Origin
-    private static final int ORIGIN_MANUAL = 2;
-
-    /**
-     * The abstract score for an empty or invalid phone suggestion.
-     *
-     * Used to score phone suggestions where there is no zone.
-     */
-    @VisibleForTesting
-    public static final int PHONE_SCORE_NONE = 0;
-
-    /**
-     * The abstract score for a low quality phone suggestion.
-     *
-     * Used to score suggestions where:
-     * The suggested zone ID is one of several possibilities, and the possibilities have different
-     * offsets.
-     *
-     * You would have to be quite desperate to want to use this choice.
-     */
-    @VisibleForTesting
-    public static final int PHONE_SCORE_LOW = 1;
-
-    /**
-     * The abstract score for a medium quality phone suggestion.
-     *
-     * Used for:
-     * The suggested zone ID is one of several possibilities but at least the possibilities have the
-     * same offset. Users would get the correct time but for the wrong reason. i.e. their device may
-     * switch to DST at the wrong time and (for example) their calendar events.
-     */
-    @VisibleForTesting
-    public static final int PHONE_SCORE_MEDIUM = 2;
-
-    /**
-     * The abstract score for a high quality phone suggestion.
-     *
-     * Used for:
-     * The suggestion was for one zone ID and the answer was unambiguous and likely correct given
-     * the info available.
-     */
-    @VisibleForTesting
-    public static final int PHONE_SCORE_HIGH = 3;
-
-    /**
-     * The abstract score for a highest quality phone suggestion.
-     *
-     * Used for:
-     * Suggestions that must "win" because they constitute test or emulator zone ID.
-     */
-    @VisibleForTesting
-    public static final int PHONE_SCORE_HIGHEST = 4;
-
-    /**
-     * The threshold at which phone suggestions are good enough to use to set the device's time
-     * zone.
-     */
-    @VisibleForTesting
-    public static final int PHONE_SCORE_USAGE_THRESHOLD = PHONE_SCORE_MEDIUM;
-
-    /** The number of previous phone suggestions to keep for each ID (for use during debugging). */
-    private static final int KEEP_PHONE_SUGGESTION_HISTORY_SIZE = 30;
-
-    @NonNull
-    private final Callback mCallback;
-
-    /**
-     * A log that records the decisions / decision metadata that affected the device's time zone
-     * (for use during debugging).
-     */
-    @NonNull
-    private final LocalLog mTimeZoneChangesLog = new LocalLog(30, false /* useLocalTimestamps */);
-
-    /**
-     * 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> mSuggestionBySlotIndex =
-            new ArrayMapWithHistory<>(KEEP_PHONE_SUGGESTION_HISTORY_SIZE);
-
-    /**
-     * Creates a new instance of {@link TimeZoneDetectorStrategy}.
-     */
-    public static TimeZoneDetectorStrategy create(Context context) {
-        Callback timeZoneDetectionServiceHelper = new TimeZoneDetectorCallbackImpl(context);
-        return new TimeZoneDetectorStrategy(timeZoneDetectionServiceHelper);
-    }
-
-    @VisibleForTesting
-    public TimeZoneDetectorStrategy(Callback callback) {
-        mCallback = Objects.requireNonNull(callback);
-    }
-
-    /** Process the suggested manually- / user-entered time zone. */
-    public synchronized void suggestManualTimeZone(@NonNull ManualTimeZoneSuggestion suggestion) {
-        Objects.requireNonNull(suggestion);
-
-        String timeZoneId = suggestion.getZoneId();
-        String cause = "Manual time suggestion received: suggestion=" + suggestion;
-        setDeviceTimeZoneIfRequired(ORIGIN_MANUAL, timeZoneId, cause);
-    }
+    /** Process the suggested manually-entered (i.e. user sourced) time zone. */
+    void suggestManualTimeZone(@NonNull ManualTimeZoneSuggestion suggestion);
 
     /**
      * 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#getSlotIndex() phone}.
-     * See {@link PhoneTimeZoneSuggestion} for an explanation of the metadata associated with a
+     * {@link TelephonyTimeZoneSuggestion#getZoneId()} is {@code null}. The suggestion is scoped to
+     * a specific {@link TelephonyTimeZoneSuggestion#getSlotIndex() slotIndex}.
+     * See {@link TelephonyTimeZoneSuggestion} 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.
      */
-    public synchronized void suggestPhoneTimeZone(@NonNull PhoneTimeZoneSuggestion suggestion) {
-        if (DBG) {
-            Slog.d(LOG_TAG, "Phone suggestion received. newSuggestion=" + suggestion);
-        }
-        Objects.requireNonNull(suggestion);
-
-        // Score the suggestion.
-        int score = scorePhoneSuggestion(suggestion);
-        QualifiedPhoneTimeZoneSuggestion scoredSuggestion =
-                new QualifiedPhoneTimeZoneSuggestion(suggestion, score);
-
-        // 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.
-        String reason = "New phone time suggested. suggestion=" + suggestion;
-        doAutoTimeZoneDetection(reason);
-    }
-
-    private static int scorePhoneSuggestion(@NonNull PhoneTimeZoneSuggestion suggestion) {
-        int score;
-        if (suggestion.getZoneId() == null) {
-            score = PHONE_SCORE_NONE;
-        } else if (suggestion.getMatchType() == MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY
-                || suggestion.getMatchType() == MATCH_TYPE_EMULATOR_ZONE_ID) {
-            // Handle emulator / test cases : These suggestions should always just be used.
-            score = PHONE_SCORE_HIGHEST;
-        } else if (suggestion.getQuality() == QUALITY_SINGLE_ZONE) {
-            score = PHONE_SCORE_HIGH;
-        } else if (suggestion.getQuality() == QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET) {
-            // The suggestion may be wrong, but at least the offset should be correct.
-            score = PHONE_SCORE_MEDIUM;
-        } else if (suggestion.getQuality() == QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS) {
-            // The suggestion has a good chance of being wrong.
-            score = PHONE_SCORE_LOW;
-        } else {
-            throw new AssertionError();
-        }
-        return score;
-    }
-
-    /**
-     * Finds the best available time zone suggestion from all phones. If it is high-enough quality
-     * and automatic time zone detection is enabled then it will be set on the device. The outcome
-     * can be that this strategy becomes / remains un-opinionated and nothing is set.
-     */
-    @GuardedBy("this")
-    private void doAutoTimeZoneDetection(@NonNull String detectionReason) {
-        if (!mCallback.isAutoTimeZoneDetectionEnabled()) {
-            // Avoid doing unnecessary work with this (race-prone) check.
-            return;
-        }
-
-        QualifiedPhoneTimeZoneSuggestion bestPhoneSuggestion = findBestPhoneSuggestion();
-
-        // Work out what to do with the best suggestion.
-        if (bestPhoneSuggestion == null) {
-            // There is no phone suggestion available at all. Become un-opinionated.
-            if (DBG) {
-                Slog.d(LOG_TAG, "Could not determine time zone: No best phone suggestion."
-                        + " detectionReason=" + detectionReason);
-            }
-            return;
-        }
-
-        // Special case handling for uninitialized devices. This should only happen once.
-        String newZoneId = bestPhoneSuggestion.suggestion.getZoneId();
-        if (newZoneId != null && !mCallback.isDeviceTimeZoneInitialized()) {
-            String cause = "Device has no time zone set. Attempting to set the device to the best"
-                    + " available suggestion."
-                    + " bestPhoneSuggestion=" + bestPhoneSuggestion
-                    + ", detectionReason=" + detectionReason;
-            Slog.i(LOG_TAG, cause);
-            setDeviceTimeZoneIfRequired(ORIGIN_PHONE, newZoneId, cause);
-            return;
-        }
-
-        boolean suggestionGoodEnough = bestPhoneSuggestion.score >= PHONE_SCORE_USAGE_THRESHOLD;
-        if (!suggestionGoodEnough) {
-            if (DBG) {
-                Slog.d(LOG_TAG, "Best suggestion not good enough."
-                        + " bestPhoneSuggestion=" + bestPhoneSuggestion
-                        + ", detectionReason=" + detectionReason);
-            }
-            return;
-        }
-
-        // Paranoia: Every suggestion above the SCORE_USAGE_THRESHOLD should have a non-null time
-        // zone ID.
-        if (newZoneId == null) {
-            Slog.w(LOG_TAG, "Empty zone suggestion scored higher than expected. This is an error:"
-                    + " bestPhoneSuggestion=" + bestPhoneSuggestion
-                    + " detectionReason=" + detectionReason);
-            return;
-        }
-
-        String zoneId = bestPhoneSuggestion.suggestion.getZoneId();
-        String cause = "Found good suggestion."
-                + ", bestPhoneSuggestion=" + bestPhoneSuggestion
-                + ", detectionReason=" + detectionReason;
-        setDeviceTimeZoneIfRequired(ORIGIN_PHONE, zoneId, cause);
-    }
-
-    @GuardedBy("this")
-    private void setDeviceTimeZoneIfRequired(
-            @Origin int origin, @NonNull String newZoneId, @NonNull String cause) {
-        Objects.requireNonNull(newZoneId);
-        Objects.requireNonNull(cause);
-
-        boolean isOriginAutomatic = isOriginAutomatic(origin);
-        if (isOriginAutomatic) {
-            if (!mCallback.isAutoTimeZoneDetectionEnabled()) {
-                if (DBG) {
-                    Slog.d(LOG_TAG, "Auto time zone detection is not enabled."
-                            + " origin=" + origin
-                            + ", newZoneId=" + newZoneId
-                            + ", cause=" + cause);
-                }
-                return;
-            }
-        } else {
-            if (mCallback.isAutoTimeZoneDetectionEnabled()) {
-                if (DBG) {
-                    Slog.d(LOG_TAG, "Auto time zone detection is enabled."
-                            + " origin=" + origin
-                            + ", newZoneId=" + newZoneId
-                            + ", cause=" + cause);
-                }
-                return;
-            }
-        }
-
-        String currentZoneId = mCallback.getDeviceTimeZone();
-
-        // Avoid unnecessary changes / intents.
-        if (newZoneId.equals(currentZoneId)) {
-            // No need to set the device time zone - the setting is already what we would be
-            // suggesting.
-            if (DBG) {
-                Slog.d(LOG_TAG, "No need to change the time zone;"
-                        + " device is already set to the suggested zone."
-                        + " origin=" + origin
-                        + ", newZoneId=" + newZoneId
-                        + ", cause=" + cause);
-            }
-            return;
-        }
-
-        mCallback.setDeviceTimeZone(newZoneId);
-        String msg = "Set device time zone."
-                + " origin=" + origin
-                + ", currentZoneId=" + currentZoneId
-                + ", newZoneId=" + newZoneId
-                + ", cause=" + cause;
-        if (DBG) {
-            Slog.d(LOG_TAG, msg);
-        }
-        mTimeZoneChangesLog.log(msg);
-    }
-
-    private static boolean isOriginAutomatic(@Origin int origin) {
-        return origin != ORIGIN_MANUAL;
-    }
-
-    @GuardedBy("this")
-    @Nullable
-    private QualifiedPhoneTimeZoneSuggestion findBestPhoneSuggestion() {
-        QualifiedPhoneTimeZoneSuggestion bestSuggestion = null;
-
-        // Iterate over the latest QualifiedPhoneTimeZoneSuggestion objects received for each phone
-        // 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 < mSuggestionBySlotIndex.size(); i++) {
-            QualifiedPhoneTimeZoneSuggestion candidateSuggestion =
-                    mSuggestionBySlotIndex.valueAt(i);
-            if (candidateSuggestion == null) {
-                // Unexpected
-                continue;
-            }
-
-            if (bestSuggestion == null) {
-                bestSuggestion = candidateSuggestion;
-            } else if (candidateSuggestion.score > bestSuggestion.score) {
-                bestSuggestion = candidateSuggestion;
-            } else if (candidateSuggestion.score == bestSuggestion.score) {
-                // Tie! Use the suggestion with the lowest slotIndex.
-                int candidateSlotIndex = candidateSuggestion.suggestion.getSlotIndex();
-                int bestSlotIndex = bestSuggestion.suggestion.getSlotIndex();
-                if (candidateSlotIndex < bestSlotIndex) {
-                    bestSuggestion = candidateSuggestion;
-                }
-            }
-        }
-        return bestSuggestion;
-    }
-
-    /**
-     * Returns the current best phone suggestion. Not intended for general use: it is used during
-     * tests to check strategy behavior.
-     */
-    @VisibleForTesting
-    @Nullable
-    public synchronized QualifiedPhoneTimeZoneSuggestion findBestPhoneSuggestionForTests() {
-        return findBestPhoneSuggestion();
-    }
+    void suggestTelephonyTimeZone(@NonNull TelephonyTimeZoneSuggestion suggestion);
 
     /**
      * Called when there has been a change to the automatic time zone detection setting.
      */
-    @VisibleForTesting
-    public synchronized void handleAutoTimeZoneDetectionChange() {
-        if (DBG) {
-            Slog.d(LOG_TAG, "handleTimeZoneDetectionChange() called");
-        }
-        if (mCallback.isAutoTimeZoneDetectionEnabled()) {
-            // When the user enabled time zone detection, run the time zone detection and change the
-            // device time zone if possible.
-            String reason = "Auto time zone detection setting enabled.";
-            doAutoTimeZoneDetection(reason);
-        }
-    }
+    void handleAutoTimeZoneDetectionChanged();
 
     /**
      * Dumps internal state such as field values.
      */
-    public synchronized void dumpState(PrintWriter pw, String[] args) {
-        IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
-        ipw.println("TimeZoneDetectorStrategy:");
-
-        ipw.increaseIndent(); // level 1
-        ipw.println("mCallback.isTimeZoneDetectionEnabled()="
-                + mCallback.isAutoTimeZoneDetectionEnabled());
-        ipw.println("mCallback.isDeviceTimeZoneInitialized()="
-                + mCallback.isDeviceTimeZoneInitialized());
-        ipw.println("mCallback.getDeviceTimeZone()="
-                + mCallback.getDeviceTimeZone());
-
-        ipw.println("Time zone change log:");
-        ipw.increaseIndent(); // level 2
-        mTimeZoneChangesLog.dump(ipw);
-        ipw.decreaseIndent(); // level 2
-
-        ipw.println("Phone suggestion history:");
-        ipw.increaseIndent(); // level 2
-        mSuggestionBySlotIndex.dump(ipw);
-        ipw.decreaseIndent(); // level 2
-        ipw.decreaseIndent(); // level 1
-        ipw.flush();
-    }
-
-    /**
-     * A method used to inspect strategy state during tests. Not intended for general use.
-     */
-    @VisibleForTesting
-    public synchronized QualifiedPhoneTimeZoneSuggestion getLatestPhoneSuggestion(int slotIndex) {
-        return mSuggestionBySlotIndex.get(slotIndex);
-    }
-
-    /**
-     * A {@link PhoneTimeZoneSuggestion} with additional qualifying metadata.
-     */
-    @VisibleForTesting
-    public static class QualifiedPhoneTimeZoneSuggestion {
-
-        @VisibleForTesting
-        public final PhoneTimeZoneSuggestion suggestion;
-
-        /**
-         * The score the suggestion has been given. This can be used to rank against other
-         * suggestions of the same type.
-         */
-        @VisibleForTesting
-        public final int score;
-
-        @VisibleForTesting
-        public QualifiedPhoneTimeZoneSuggestion(PhoneTimeZoneSuggestion suggestion, int score) {
-            this.suggestion = suggestion;
-            this.score = score;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) {
-                return true;
-            }
-            if (o == null || getClass() != o.getClass()) {
-                return false;
-            }
-            QualifiedPhoneTimeZoneSuggestion that = (QualifiedPhoneTimeZoneSuggestion) o;
-            return score == that.score
-                    && suggestion.equals(that.suggestion);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(score, suggestion);
-        }
-
-        @Override
-        public String toString() {
-            return "QualifiedPhoneTimeZoneSuggestion{"
-                    + "suggestion=" + suggestion
-                    + ", score=" + score
-                    + '}';
-        }
-    }
+    void dump(PrintWriter pw, String[] args);
 }
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
new file mode 100644
index 0000000..652dbe15
--- /dev/null
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
@@ -0,0 +1,522 @@
+/*
+ * Copyright 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.server.timezonedetector;
+
+import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID;
+import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY;
+import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS;
+import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET;
+import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.timezonedetector.ManualTimeZoneSuggestion;
+import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
+import android.content.Context;
+import android.util.LocalLog;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * An implementation of {@link TimeZoneDetectorStrategy} that handle telephony and manual
+ * suggestions. Suggestions are acted on or ignored as needed, dependent on the current "auto time
+ * zone detection" setting.
+ *
+ * <p>For automatic detection, it keeps track of the most recent telephony suggestion from each
+ * slotIndex and it uses the best suggestion based on a scoring algorithm. If several slotIndexes
+ * provide the same score then the slotIndex with the lowest numeric value "wins". If the situation
+ * changes and it is no longer possible to be confident about the time zone, slotIndexes must have
+ * an empty suggestion submitted in order to "withdraw" their previous suggestion.
+ *
+ * <p>Most public methods are marked synchronized to ensure thread safety around internal state.
+ */
+public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrategy {
+
+    /**
+     * Used by {@link TimeZoneDetectorStrategyImpl} to interact with the surrounding service. It can
+     * be faked for tests.
+     *
+     * <p>Note: Because the system properties-derived values like
+     * {@link #isAutoTimeZoneDetectionEnabled()}, {@link #isAutoTimeZoneDetectionEnabled()},
+     * {@link #getDeviceTimeZone()} can be modified independently and from different threads (and
+     * processes!), their use are prone to race conditions. That will be true until the
+     * responsibility for setting their values is moved to {@link TimeZoneDetectorStrategyImpl}.
+     */
+    @VisibleForTesting
+    public interface Callback {
+
+        /**
+         * Returns true if automatic time zone detection is enabled in settings.
+         */
+        boolean isAutoTimeZoneDetectionEnabled();
+
+        /**
+         * Returns true if the device has had an explicit time zone set.
+         */
+        boolean isDeviceTimeZoneInitialized();
+
+        /**
+         * Returns the device's currently configured time zone.
+         */
+        String getDeviceTimeZone();
+
+        /**
+         * Sets the device's time zone.
+         */
+        void setDeviceTimeZone(@NonNull String zoneId);
+    }
+
+    private static final String LOG_TAG = "TimeZoneDetectorStrategy";
+    private static final boolean DBG = false;
+
+    @IntDef({ ORIGIN_TELEPHONY, ORIGIN_MANUAL })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Origin {}
+
+    /** Used when a time value originated from a telephony signal. */
+    @Origin
+    private static final int ORIGIN_TELEPHONY = 1;
+
+    /** Used when a time value originated from a user / manual settings. */
+    @Origin
+    private static final int ORIGIN_MANUAL = 2;
+
+    /**
+     * The abstract score for an empty or invalid telephony suggestion.
+     *
+     * Used to score telephony suggestions where there is no zone.
+     */
+    @VisibleForTesting
+    public static final int TELEPHONY_SCORE_NONE = 0;
+
+    /**
+     * The abstract score for a low quality telephony suggestion.
+     *
+     * Used to score suggestions where:
+     * The suggested zone ID is one of several possibilities, and the possibilities have different
+     * offsets.
+     *
+     * You would have to be quite desperate to want to use this choice.
+     */
+    @VisibleForTesting
+    public static final int TELEPHONY_SCORE_LOW = 1;
+
+    /**
+     * The abstract score for a medium quality telephony suggestion.
+     *
+     * Used for:
+     * The suggested zone ID is one of several possibilities but at least the possibilities have the
+     * same offset. Users would get the correct time but for the wrong reason. i.e. their device may
+     * switch to DST at the wrong time and (for example) their calendar events.
+     */
+    @VisibleForTesting
+    public static final int TELEPHONY_SCORE_MEDIUM = 2;
+
+    /**
+     * The abstract score for a high quality telephony suggestion.
+     *
+     * Used for:
+     * The suggestion was for one zone ID and the answer was unambiguous and likely correct given
+     * the info available.
+     */
+    @VisibleForTesting
+    public static final int TELEPHONY_SCORE_HIGH = 3;
+
+    /**
+     * The abstract score for a highest quality telephony suggestion.
+     *
+     * Used for:
+     * Suggestions that must "win" because they constitute test or emulator zone ID.
+     */
+    @VisibleForTesting
+    public static final int TELEPHONY_SCORE_HIGHEST = 4;
+
+    /**
+     * The threshold at which telephony suggestions are good enough to use to set the device's time
+     * zone.
+     */
+    @VisibleForTesting
+    public static final int TELEPHONY_SCORE_USAGE_THRESHOLD = TELEPHONY_SCORE_MEDIUM;
+
+    /**
+     * The number of previous telephony suggestions to keep for each ID (for use during debugging).
+     */
+    private static final int KEEP_TELEPHONY_SUGGESTION_HISTORY_SIZE = 30;
+
+    @NonNull
+    private final Callback mCallback;
+
+    /**
+     * A log that records the decisions / decision metadata that affected the device's time zone
+     * (for use during debugging).
+     */
+    @NonNull
+    private final LocalLog mTimeZoneChangesLog = new LocalLog(30, false /* useLocalTimestamps */);
+
+    /**
+     * A mapping from slotIndex to a telephony time zone suggestion. We typically expect one or two
+     * mappings: devices will have a small number of telephony devices and slotIndexes are assumed
+     * to be stable.
+     */
+    @GuardedBy("this")
+    private ArrayMapWithHistory<Integer, QualifiedTelephonyTimeZoneSuggestion>
+            mSuggestionBySlotIndex =
+            new ArrayMapWithHistory<>(KEEP_TELEPHONY_SUGGESTION_HISTORY_SIZE);
+
+    /**
+     * Creates a new instance of {@link TimeZoneDetectorStrategyImpl}.
+     */
+    public static TimeZoneDetectorStrategyImpl create(Context context) {
+        Callback timeZoneDetectionServiceHelper = new TimeZoneDetectorCallbackImpl(context);
+        return new TimeZoneDetectorStrategyImpl(timeZoneDetectionServiceHelper);
+    }
+
+    @VisibleForTesting
+    public TimeZoneDetectorStrategyImpl(Callback callback) {
+        mCallback = Objects.requireNonNull(callback);
+    }
+
+    @Override
+    public synchronized void suggestManualTimeZone(@NonNull ManualTimeZoneSuggestion suggestion) {
+        Objects.requireNonNull(suggestion);
+
+        String timeZoneId = suggestion.getZoneId();
+        String cause = "Manual time suggestion received: suggestion=" + suggestion;
+        setDeviceTimeZoneIfRequired(ORIGIN_MANUAL, timeZoneId, cause);
+    }
+
+    @Override
+    public synchronized void suggestTelephonyTimeZone(
+            @NonNull TelephonyTimeZoneSuggestion suggestion) {
+        if (DBG) {
+            Slog.d(LOG_TAG, "Telephony suggestion received. newSuggestion=" + suggestion);
+        }
+        Objects.requireNonNull(suggestion);
+
+        // Score the suggestion.
+        int score = scoreTelephonySuggestion(suggestion);
+        QualifiedTelephonyTimeZoneSuggestion scoredSuggestion =
+                new QualifiedTelephonyTimeZoneSuggestion(suggestion, score);
+
+        // 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.
+        String reason = "New telephony time suggested. suggestion=" + suggestion;
+        doAutoTimeZoneDetection(reason);
+    }
+
+    private static int scoreTelephonySuggestion(@NonNull TelephonyTimeZoneSuggestion suggestion) {
+        int score;
+        if (suggestion.getZoneId() == null) {
+            score = TELEPHONY_SCORE_NONE;
+        } else if (suggestion.getMatchType() == MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY
+                || suggestion.getMatchType() == MATCH_TYPE_EMULATOR_ZONE_ID) {
+            // Handle emulator / test cases : These suggestions should always just be used.
+            score = TELEPHONY_SCORE_HIGHEST;
+        } else if (suggestion.getQuality() == QUALITY_SINGLE_ZONE) {
+            score = TELEPHONY_SCORE_HIGH;
+        } else if (suggestion.getQuality() == QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET) {
+            // The suggestion may be wrong, but at least the offset should be correct.
+            score = TELEPHONY_SCORE_MEDIUM;
+        } else if (suggestion.getQuality() == QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS) {
+            // The suggestion has a good chance of being wrong.
+            score = TELEPHONY_SCORE_LOW;
+        } else {
+            throw new AssertionError();
+        }
+        return score;
+    }
+
+    /**
+     * Finds the best available time zone suggestion from all slotIndexes. If it is high-enough
+     * quality and automatic time zone detection is enabled then it will be set on the device. The
+     * outcome can be that this strategy becomes / remains un-opinionated and nothing is set.
+     */
+    @GuardedBy("this")
+    private void doAutoTimeZoneDetection(@NonNull String detectionReason) {
+        if (!mCallback.isAutoTimeZoneDetectionEnabled()) {
+            // Avoid doing unnecessary work with this (race-prone) check.
+            return;
+        }
+
+        QualifiedTelephonyTimeZoneSuggestion bestTelephonySuggestion =
+                findBestTelephonySuggestion();
+
+        // Work out what to do with the best suggestion.
+        if (bestTelephonySuggestion == null) {
+            // There is no telephony suggestion available at all. Become un-opinionated.
+            if (DBG) {
+                Slog.d(LOG_TAG, "Could not determine time zone: No best telephony suggestion."
+                        + " detectionReason=" + detectionReason);
+            }
+            return;
+        }
+
+        // Special case handling for uninitialized devices. This should only happen once.
+        String newZoneId = bestTelephonySuggestion.suggestion.getZoneId();
+        if (newZoneId != null && !mCallback.isDeviceTimeZoneInitialized()) {
+            String cause = "Device has no time zone set. Attempting to set the device to the best"
+                    + " available suggestion."
+                    + " bestTelephonySuggestion=" + bestTelephonySuggestion
+                    + ", detectionReason=" + detectionReason;
+            Slog.i(LOG_TAG, cause);
+            setDeviceTimeZoneIfRequired(ORIGIN_TELEPHONY, newZoneId, cause);
+            return;
+        }
+
+        boolean suggestionGoodEnough =
+                bestTelephonySuggestion.score >= TELEPHONY_SCORE_USAGE_THRESHOLD;
+        if (!suggestionGoodEnough) {
+            if (DBG) {
+                Slog.d(LOG_TAG, "Best suggestion not good enough."
+                        + " bestTelephonySuggestion=" + bestTelephonySuggestion
+                        + ", detectionReason=" + detectionReason);
+            }
+            return;
+        }
+
+        // Paranoia: Every suggestion above the SCORE_USAGE_THRESHOLD should have a non-null time
+        // zone ID.
+        if (newZoneId == null) {
+            Slog.w(LOG_TAG, "Empty zone suggestion scored higher than expected. This is an error:"
+                    + " bestTelephonySuggestion=" + bestTelephonySuggestion
+                    + " detectionReason=" + detectionReason);
+            return;
+        }
+
+        String zoneId = bestTelephonySuggestion.suggestion.getZoneId();
+        String cause = "Found good suggestion."
+                + ", bestTelephonySuggestion=" + bestTelephonySuggestion
+                + ", detectionReason=" + detectionReason;
+        setDeviceTimeZoneIfRequired(ORIGIN_TELEPHONY, zoneId, cause);
+    }
+
+    @GuardedBy("this")
+    private void setDeviceTimeZoneIfRequired(
+            @Origin int origin, @NonNull String newZoneId, @NonNull String cause) {
+        Objects.requireNonNull(newZoneId);
+        Objects.requireNonNull(cause);
+
+        boolean isOriginAutomatic = isOriginAutomatic(origin);
+        if (isOriginAutomatic) {
+            if (!mCallback.isAutoTimeZoneDetectionEnabled()) {
+                if (DBG) {
+                    Slog.d(LOG_TAG, "Auto time zone detection is not enabled."
+                            + " origin=" + origin
+                            + ", newZoneId=" + newZoneId
+                            + ", cause=" + cause);
+                }
+                return;
+            }
+        } else {
+            if (mCallback.isAutoTimeZoneDetectionEnabled()) {
+                if (DBG) {
+                    Slog.d(LOG_TAG, "Auto time zone detection is enabled."
+                            + " origin=" + origin
+                            + ", newZoneId=" + newZoneId
+                            + ", cause=" + cause);
+                }
+                return;
+            }
+        }
+
+        String currentZoneId = mCallback.getDeviceTimeZone();
+
+        // Avoid unnecessary changes / intents.
+        if (newZoneId.equals(currentZoneId)) {
+            // No need to set the device time zone - the setting is already what we would be
+            // suggesting.
+            if (DBG) {
+                Slog.d(LOG_TAG, "No need to change the time zone;"
+                        + " device is already set to the suggested zone."
+                        + " origin=" + origin
+                        + ", newZoneId=" + newZoneId
+                        + ", cause=" + cause);
+            }
+            return;
+        }
+
+        mCallback.setDeviceTimeZone(newZoneId);
+        String msg = "Set device time zone."
+                + " origin=" + origin
+                + ", currentZoneId=" + currentZoneId
+                + ", newZoneId=" + newZoneId
+                + ", cause=" + cause;
+        if (DBG) {
+            Slog.d(LOG_TAG, msg);
+        }
+        mTimeZoneChangesLog.log(msg);
+    }
+
+    private static boolean isOriginAutomatic(@Origin int origin) {
+        return origin != ORIGIN_MANUAL;
+    }
+
+    @GuardedBy("this")
+    @Nullable
+    private QualifiedTelephonyTimeZoneSuggestion findBestTelephonySuggestion() {
+        QualifiedTelephonyTimeZoneSuggestion bestSuggestion = null;
+
+        // Iterate over the latest QualifiedTelephonyTimeZoneSuggestion objects received for each
+        // slotIndex 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 < mSuggestionBySlotIndex.size(); i++) {
+            QualifiedTelephonyTimeZoneSuggestion candidateSuggestion =
+                    mSuggestionBySlotIndex.valueAt(i);
+            if (candidateSuggestion == null) {
+                // Unexpected
+                continue;
+            }
+
+            if (bestSuggestion == null) {
+                bestSuggestion = candidateSuggestion;
+            } else if (candidateSuggestion.score > bestSuggestion.score) {
+                bestSuggestion = candidateSuggestion;
+            } else if (candidateSuggestion.score == bestSuggestion.score) {
+                // Tie! Use the suggestion with the lowest slotIndex.
+                int candidateSlotIndex = candidateSuggestion.suggestion.getSlotIndex();
+                int bestSlotIndex = bestSuggestion.suggestion.getSlotIndex();
+                if (candidateSlotIndex < bestSlotIndex) {
+                    bestSuggestion = candidateSuggestion;
+                }
+            }
+        }
+        return bestSuggestion;
+    }
+
+    /**
+     * Returns the current best telephony suggestion. Not intended for general use: it is used
+     * during tests to check strategy behavior.
+     */
+    @VisibleForTesting
+    @Nullable
+    public synchronized QualifiedTelephonyTimeZoneSuggestion findBestTelephonySuggestionForTests() {
+        return findBestTelephonySuggestion();
+    }
+
+    @Override
+    public synchronized void handleAutoTimeZoneDetectionChanged() {
+        if (DBG) {
+            Slog.d(LOG_TAG, "handleTimeZoneDetectionChange() called");
+        }
+        if (mCallback.isAutoTimeZoneDetectionEnabled()) {
+            // When the user enabled time zone detection, run the time zone detection and change the
+            // device time zone if possible.
+            String reason = "Auto time zone detection setting enabled.";
+            doAutoTimeZoneDetection(reason);
+        }
+    }
+
+    /**
+     * Dumps internal state such as field values.
+     */
+    @Override
+    public synchronized void dump(PrintWriter pw, String[] args) {
+        IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
+        ipw.println("TimeZoneDetectorStrategy:");
+
+        ipw.increaseIndent(); // level 1
+        ipw.println("mCallback.isTimeZoneDetectionEnabled()="
+                + mCallback.isAutoTimeZoneDetectionEnabled());
+        ipw.println("mCallback.isDeviceTimeZoneInitialized()="
+                + mCallback.isDeviceTimeZoneInitialized());
+        ipw.println("mCallback.getDeviceTimeZone()="
+                + mCallback.getDeviceTimeZone());
+
+        ipw.println("Time zone change log:");
+        ipw.increaseIndent(); // level 2
+        mTimeZoneChangesLog.dump(ipw);
+        ipw.decreaseIndent(); // level 2
+
+        ipw.println("Telephony suggestion history:");
+        ipw.increaseIndent(); // level 2
+        mSuggestionBySlotIndex.dump(ipw);
+        ipw.decreaseIndent(); // level 2
+        ipw.decreaseIndent(); // level 1
+        ipw.flush();
+    }
+
+    /**
+     * A method used to inspect strategy state during tests. Not intended for general use.
+     */
+    @VisibleForTesting
+    public synchronized QualifiedTelephonyTimeZoneSuggestion getLatestTelephonySuggestion(
+            int slotIndex) {
+        return mSuggestionBySlotIndex.get(slotIndex);
+    }
+
+    /**
+     * A {@link TelephonyTimeZoneSuggestion} with additional qualifying metadata.
+     */
+    @VisibleForTesting
+    public static class QualifiedTelephonyTimeZoneSuggestion {
+
+        @VisibleForTesting
+        public final TelephonyTimeZoneSuggestion suggestion;
+
+        /**
+         * The score the suggestion has been given. This can be used to rank against other
+         * suggestions of the same type.
+         */
+        @VisibleForTesting
+        public final int score;
+
+        @VisibleForTesting
+        public QualifiedTelephonyTimeZoneSuggestion(
+                TelephonyTimeZoneSuggestion suggestion, int score) {
+            this.suggestion = suggestion;
+            this.score = score;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            QualifiedTelephonyTimeZoneSuggestion that = (QualifiedTelephonyTimeZoneSuggestion) o;
+            return score == that.score
+                    && suggestion.equals(that.suggestion);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(score, suggestion);
+        }
+
+        @Override
+        public String toString() {
+            return "QualifiedTelephonyTimeZoneSuggestion{"
+                    + "suggestion=" + suggestion
+                    + ", score=" + score
+                    + '}';
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 4f010d5..e3b1152c 100755
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -383,36 +383,38 @@
             if (mCurrentUserId == userId) {
                 return;
             }
-            UserState userState = mUserStates.get(mCurrentUserId);
-            List<SessionState> sessionStatesToRelease = new ArrayList<>();
-            for (SessionState sessionState : userState.sessionStateMap.values()) {
-                if (sessionState.session != null && !sessionState.isRecordingSession) {
-                    sessionStatesToRelease.add(sessionState);
-                }
-            }
-            for (SessionState sessionState : sessionStatesToRelease) {
-                try {
-                    sessionState.session.release();
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "error in release", e);
-                }
-                clearSessionAndNotifyClientLocked(sessionState);
-            }
-
-            for (Iterator<ComponentName> it = userState.serviceStateMap.keySet().iterator();
-                 it.hasNext(); ) {
-                ComponentName component = it.next();
-                ServiceState serviceState = userState.serviceStateMap.get(component);
-                if (serviceState != null && serviceState.sessionTokens.isEmpty()) {
-                    if (serviceState.callback != null) {
-                        try {
-                            serviceState.service.unregisterCallback(serviceState.callback);
-                        } catch (RemoteException e) {
-                            Slog.e(TAG, "error in unregisterCallback", e);
-                        }
+            if (mUserStates.contains(mCurrentUserId)) {
+                UserState userState = mUserStates.get(mCurrentUserId);
+                List<SessionState> sessionStatesToRelease = new ArrayList<>();
+                for (SessionState sessionState : userState.sessionStateMap.values()) {
+                    if (sessionState.session != null && !sessionState.isRecordingSession) {
+                        sessionStatesToRelease.add(sessionState);
                     }
-                    mContext.unbindService(serviceState.connection);
-                    it.remove();
+                }
+                for (SessionState sessionState : sessionStatesToRelease) {
+                    try {
+                        sessionState.session.release();
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "error in release", e);
+                    }
+                    clearSessionAndNotifyClientLocked(sessionState);
+                }
+
+                for (Iterator<ComponentName> it = userState.serviceStateMap.keySet().iterator();
+                    it.hasNext(); ) {
+                    ComponentName component = it.next();
+                    ServiceState serviceState = userState.serviceStateMap.get(component);
+                    if (serviceState != null && serviceState.sessionTokens.isEmpty()) {
+                        if (serviceState.callback != null) {
+                            try {
+                                serviceState.service.unregisterCallback(serviceState.callback);
+                            } catch (RemoteException e) {
+                                Slog.e(TAG, "error in unregisterCallback", e);
+                            }
+                        }
+                        mContext.unbindService(serviceState.connection);
+                        it.remove();
+                    }
                 }
             }
 
@@ -490,6 +492,10 @@
             userState.mainSessionToken = null;
 
             mUserStates.remove(userId);
+
+            if (userId == mCurrentUserId) {
+                switchUser(UserHandle.USER_SYSTEM);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index ed38e9a..a54f5d4 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1629,12 +1629,7 @@
         requestedVrComponent = (aInfo.requestedVrComponent == null) ?
                 null : ComponentName.unflattenFromString(aInfo.requestedVrComponent);
 
-        lockTaskLaunchMode = aInfo.lockTaskLaunchMode;
-        if (info.applicationInfo.isPrivilegedApp()
-                && (lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_ALWAYS
-                || lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_NEVER)) {
-            lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_DEFAULT;
-        }
+        lockTaskLaunchMode = getLockTaskLaunchMode(aInfo, options);
 
         if (options != null) {
             pendingOptions = options;
@@ -1642,15 +1637,27 @@
             if (usageReport != null) {
                 appTimeTracker = new AppTimeTracker(usageReport);
             }
-            final boolean useLockTask = pendingOptions.getLockTaskMode();
-            if (useLockTask && lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_DEFAULT) {
-                lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED;
-            }
             // Gets launch display id from options. It returns INVALID_DISPLAY if not set.
             mHandoverLaunchDisplayId = options.getLaunchDisplayId();
         }
     }
 
+    static int getLockTaskLaunchMode(ActivityInfo aInfo, @Nullable ActivityOptions options) {
+        int lockTaskLaunchMode = aInfo.lockTaskLaunchMode;
+        if (aInfo.applicationInfo.isPrivilegedApp()
+                && (lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_ALWAYS
+                || lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_NEVER)) {
+            lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_DEFAULT;
+        }
+        if (options != null) {
+            final boolean useLockTask = options.getLockTaskMode();
+            if (useLockTask && lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_DEFAULT) {
+                lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED;
+            }
+        }
+        return lockTaskLaunchMode;
+    }
+
     @Override
     ActivityRecord asActivityRecord() {
         // I am an activity record!
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index d5961a8..d380f8c 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -356,6 +356,8 @@
     // TODO(task-hierarchy): remove when tiles can be actual parents
     TaskTile mTile = null;
 
+    private int mLastTaskOrganizerWindowingMode = -1;
+
     private final Handler mHandler;
 
     private class ActivityStackHandler extends Handler {
@@ -794,6 +796,13 @@
         }
 
         final int windowingMode = getWindowingMode();
+        if (windowingMode == mLastTaskOrganizerWindowingMode) {
+            // If our windowing mode hasn't actually changed, then just stick
+            // with our old organizer. This lets us implement the semantic
+            // where SysUI can continue to manage it's old tasks
+            // while CTS temporarily takes over the registration.
+            return;
+        }
         /*
          * Different windowing modes may be managed by different task organizers. If
          * getTaskOrganizer returns null, we still call setTaskOrganizer to
@@ -802,6 +811,7 @@
         final ITaskOrganizer org =
             mWmService.mAtmService.mTaskOrganizerController.getTaskOrganizer(windowingMode);
         setTaskOrganizer(org);
+        mLastTaskOrganizerWindowingMode = windowingMode;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 75d87ed..f35ba9e 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -386,6 +386,8 @@
         } else {
             callingPid = callingUid = -1;
         }
+        final int filterCallingUid = ActivityStarter.computeResolveFilterUid(
+                callingUid, realCallingUid, UserHandle.USER_NULL);
         final SparseArray<String> startingUidPkgs = new SparseArray<>();
         final long origId = Binder.clearCallingIdentity();
         try {
@@ -408,9 +410,7 @@
 
                 // Collect information about the target of the Intent.
                 ActivityInfo aInfo = mSupervisor.resolveActivity(intent, resolvedTypes[i],
-                        0 /* startFlags */, null /* profilerInfo */, userId,
-                        ActivityStarter.computeResolveFilterUid(
-                                callingUid, realCallingUid, UserHandle.USER_NULL));
+                        0 /* startFlags */, null /* profilerInfo */, userId, filterCallingUid);
                 aInfo = mService.mAmInternal.getActivityInfoForUser(aInfo, userId);
 
                 if (aInfo != null) {
@@ -457,6 +457,7 @@
                 Slog.wtf(TAG, sb.toString());
             }
 
+            final IBinder sourceResultTo = resultTo;
             final ActivityRecord[] outActivity = new ActivityRecord[1];
             // Lock the loop to ensure the activities launched in a sequence.
             synchronized (mService.mGlobalLock) {
@@ -470,7 +471,18 @@
                         }
                         return startResult;
                     }
-                    resultTo = outActivity[0] != null ? outActivity[0].appToken : null;
+                    final ActivityRecord started = outActivity[0];
+                    if (started != null && started.getUid() == filterCallingUid) {
+                        // Only the started activity which has the same uid as the source caller can
+                        // be the caller of next activity.
+                        resultTo = started.appToken;
+                    } else {
+                        resultTo = sourceResultTo;
+                        // Different apps not adjacent to the caller are forced to be new task.
+                        if (i < starters.length - 1) {
+                            starters[i + 1].getIntent().addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                        }
+                    }
                 }
             }
         } finally {
diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
index 2fb0ac5..76aa1d1 100644
--- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
@@ -52,6 +52,7 @@
 import android.os.UserManager;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.BlockedAppActivity;
 import com.android.internal.app.HarmfulAppWarningActivity;
 import com.android.internal.app.SuspendedAppActivity;
 import com.android.internal.app.UnlaunchableAppActivity;
@@ -166,6 +167,9 @@
             // no user action can undo this.
             return true;
         }
+        if (interceptLockTaskModeViolationPackageIfNeeded()) {
+            return true;
+        }
         if (interceptHarmfulAppIfNeeded()) {
             // If the app has a "harmful app" warning associated with it, we should ask to uninstall
             // before issuing the work challenge.
@@ -270,6 +274,25 @@
         return true;
     }
 
+    private boolean interceptLockTaskModeViolationPackageIfNeeded() {
+        if (mAInfo == null || mAInfo.applicationInfo == null) {
+            return false;
+        }
+        LockTaskController controller = mService.getLockTaskController();
+        String packageName = mAInfo.applicationInfo.packageName;
+        int lockTaskLaunchMode = ActivityRecord.getLockTaskLaunchMode(mAInfo, mActivityOptions);
+        if (controller.isActivityAllowed(mUserId, packageName, lockTaskLaunchMode)) {
+            return false;
+        }
+        mIntent = BlockedAppActivity.createIntent(mUserId, mAInfo.applicationInfo.packageName);
+        mCallingPid = mRealCallingPid;
+        mCallingUid = mRealCallingUid;
+        mResolvedType = null;
+        mRInfo = mSupervisor.resolveIntent(mIntent, mResolvedType, mUserId, 0, mRealCallingUid);
+        mAInfo = mSupervisor.resolveActivity(mIntent, mRInfo, mStartFlags, null /*profilerInfo*/);
+        return true;
+    }
+
     private boolean interceptWorkProfileChallengeIfNeeded() {
         final Intent interceptingIntent = interceptWithConfirmCredentialsIfNeeded(mAInfo, mUserId);
         if (interceptingIntent == null) {
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 9e3292b..c7270f2 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2493,7 +2493,6 @@
         return this;
     }
 
-    @VisibleForTesting
     Intent getIntent() {
         return mRequest.intent;
     }
diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
new file mode 100644
index 0000000..94decc7
--- /dev/null
+++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
@@ -0,0 +1,122 @@
+/*
+ * 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 android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+import android.view.ITaskOrganizer;
+import android.view.SurfaceControl;
+
+import java.util.HashMap;
+
+/**
+ * Utility class for collecting and merging transactions from various sources asynchronously.
+ * For example to use to synchronously resize all the children of a window container
+ *   1. Open a new sync set, and pass the listener that will be invoked
+ *        int id startSyncSet(TransactionReadyListener)
+ *      the returned ID will be eventually passed to the TransactionReadyListener in combination
+ *      with the prepared transaction. You also use it to refer to the operation in future steps.
+ *   2. Ask each child to participate:
+ *       addToSyncSet(int id, WindowContainer wc)
+ *      if the child thinks it will be affected by a configuration change (a.k.a. has a visible
+ *      window in its sub hierarchy, then we will increment a counter of expected callbacks
+ *      At this point the containers hierarchy will redirect pendingTransaction and sub hierarchy
+ *      updates in to the sync engine.
+ *   3. Apply your configuration changes to the window containers.
+ *   4. Tell the engine that the sync set is ready
+ *       setReady(int id)
+ *   5. If there were no sub windows anywhere in the hierarchy to wait on, then
+ *      transactionReady is immediately invoked, otherwise all the windows are poked
+ *      to redraw and to deliver a buffer to WMS#finishDrawing.
+ *      Once all this drawing is complete the combined transaction of all the buffers
+ *      and pending transaction hierarchy changes will be delivered to the TransactionReadyListener
+ */
+class BLASTSyncEngine {
+    private static final String TAG = "BLASTSyncEngine";
+
+    interface TransactionReadyListener {
+        void transactionReady(int mSyncId, SurfaceControl.Transaction mergedTransaction);
+    };
+
+    // Holds state associated with a single synchronous set of operations.
+    class SyncState implements TransactionReadyListener {
+        int mSyncId;
+        SurfaceControl.Transaction mMergedTransaction;
+        int mRemainingTransactions;
+        TransactionReadyListener mListener;
+        boolean mReady = false;
+
+        private void tryFinish() {
+            if (mRemainingTransactions == 0 && mReady) {
+                mListener.transactionReady(mSyncId, mMergedTransaction);
+                mPendingSyncs.remove(mSyncId);
+            }
+        }
+
+        public void transactionReady(int mSyncId, SurfaceControl.Transaction mergedTransaction) {
+            mRemainingTransactions--;
+            mMergedTransaction.merge(mergedTransaction);
+            tryFinish();
+        }
+
+        void setReady() {
+            mReady = true;
+            tryFinish();
+        }
+
+        boolean addToSync(WindowContainer wc) {
+            if (wc.prepareForSync(this, mSyncId)) {
+                mRemainingTransactions++;
+                return true;
+            }
+            return false;
+        }
+
+        SyncState(TransactionReadyListener l, int id) {
+            mListener = l;
+            mSyncId = id;
+            mMergedTransaction = new SurfaceControl.Transaction();
+            mRemainingTransactions = 0;
+        }
+    };
+
+    int mNextSyncId = 0;
+
+    final HashMap<Integer, SyncState> mPendingSyncs = new HashMap();
+
+    BLASTSyncEngine() {
+    }
+
+    int startSyncSet(TransactionReadyListener listener) {
+        final int id = mNextSyncId++;
+        final SyncState s = new SyncState(listener, id);
+        mPendingSyncs.put(id, s);
+        return id;
+    }
+
+    boolean addToSyncSet(int id, WindowContainer wc) {
+        final SyncState st = mPendingSyncs.get(id);
+        return st.addToSync(wc);
+    }
+
+    // TODO(b/148476626): TIMEOUTS!
+    void setReady(int id) {
+        final SyncState st = mPendingSyncs.get(id);
+        st.setReady();
+    }
+}
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index efe79b3..e71371a 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -526,7 +526,9 @@
             mService.mH.removeCallbacks(mDisplayRotationHandlerTimeout);
             mIsWaitingForRemoteRotation = false;
             mDisplayContent.sendNewConfiguration();
-            mService.mAtmService.mTaskOrganizerController.applyContainerTransaction(t);
+            if (t != null) {
+                mService.mAtmService.mTaskOrganizerController.applyContainerTransaction(t, null);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java
index 02413bb..3b25b74 100644
--- a/services/core/java/com/android/server/wm/LockTaskController.java
+++ b/services/core/java/com/android/server/wm/LockTaskController.java
@@ -23,6 +23,8 @@
 import static android.content.Context.DEVICE_POLICY_SERVICE;
 import static android.content.Context.STATUS_BAR_SERVICE;
 import static android.content.Intent.ACTION_CALL_EMERGENCY;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER;
 import static android.os.UserHandle.USER_ALL;
 import static android.os.UserHandle.USER_CURRENT;
 import static android.telecom.TelecomManager.EMERGENCY_DIALER_COMPONENT;
@@ -339,6 +341,25 @@
                 & DevicePolicyManager.LOCK_TASK_FEATURE_KEYGUARD) != 0;
     }
 
+    private boolean isBlockingInTaskEnabled(int userId) {
+        return (getLockTaskFeaturesForUser(userId)
+                & DevicePolicyManager.LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK) != 0;
+    }
+
+    boolean isActivityAllowed(int userId, String packageName, int lockTaskLaunchMode) {
+        if (mLockTaskModeState != LOCK_TASK_MODE_LOCKED || !isBlockingInTaskEnabled(userId)) {
+            return true;
+        }
+        switch (lockTaskLaunchMode) {
+            case LOCK_TASK_LAUNCH_MODE_ALWAYS:
+                return true;
+            case LOCK_TASK_LAUNCH_MODE_NEVER:
+                return false;
+            default:
+        }
+        return isPackageWhitelisted(userId, packageName);
+    }
+
     private boolean isEmergencyCallTask(Task task) {
         final Intent intent = task.intent;
         if (intent == null) {
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 44a6fc9..096541f 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -38,6 +38,7 @@
 import android.util.Slog;
 import android.view.ITaskOrganizer;
 import android.view.IWindowContainer;
+import android.view.SurfaceControl;
 import android.view.WindowContainerTransaction;
 
 import com.android.internal.util.function.pooled.PooledConsumer;
@@ -53,7 +54,8 @@
  * Stores the TaskOrganizers associated with a given windowing mode and
  * their associated state.
  */
-class TaskOrganizerController extends ITaskOrganizerController.Stub {
+class TaskOrganizerController extends ITaskOrganizerController.Stub
+    implements BLASTSyncEngine.TransactionReadyListener {
     private static final String TAG = "TaskOrganizerController";
 
     /** Flag indicating that an applied transaction may have effected lifecycle */
@@ -74,11 +76,10 @@
         @Override
         public void binderDied() {
             synchronized (mGlobalLock) {
-                final TaskOrganizerState state = mTaskOrganizerStates.get(mTaskOrganizer);
-                for (int i = 0; i < state.mOrganizedTasks.size(); i++) {
-                    state.mOrganizedTasks.get(i).taskOrganizerDied();
-                }
-                mTaskOrganizerStates.remove(mTaskOrganizer);
+                final TaskOrganizerState state =
+                    mTaskOrganizerStates.get(mTaskOrganizer.asBinder());
+                state.releaseTasks();
+                mTaskOrganizerStates.remove(mTaskOrganizer.asBinder());
                 if (mTaskOrganizersForWindowingMode.get(mWindowingMode) == mTaskOrganizer) {
                     mTaskOrganizersForWindowingMode.remove(mWindowingMode);
                 }
@@ -89,32 +90,84 @@
     class TaskOrganizerState {
         ITaskOrganizer mOrganizer;
         DeathRecipient mDeathRecipient;
+        int mWindowingMode;
 
         ArrayList<Task> mOrganizedTasks = new ArrayList<>();
 
+        // Save the TaskOrganizer which we replaced registration for
+        // so it can be re-registered if we unregister.
+        TaskOrganizerState mReplacementFor;
+        boolean mDisposed = false;
+
+
+        TaskOrganizerState(ITaskOrganizer organizer, int windowingMode,
+                TaskOrganizerState replacing) {
+            mOrganizer = organizer;
+            mDeathRecipient = new DeathRecipient(organizer, windowingMode);
+            try {
+                organizer.asBinder().linkToDeath(mDeathRecipient, 0);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "TaskOrganizer failed to register death recipient");
+            }
+            mWindowingMode = windowingMode;
+            mReplacementFor = replacing;
+        }
+
         void addTask(Task t) {
             mOrganizedTasks.add(t);
+            try {
+                mOrganizer.taskAppeared(t.getTaskInfo());
+            } catch (Exception e) {
+                Slog.e(TAG, "Exception sending taskAppeared callback" + e);
+            }
         }
 
         void removeTask(Task t) {
+            try {
+                mOrganizer.taskVanished(t.getRemoteToken());
+            } catch (Exception e) {
+                Slog.e(TAG, "Exception sending taskVanished callback" + e);
+            }
             mOrganizedTasks.remove(t);
         }
 
-        TaskOrganizerState(ITaskOrganizer organizer, DeathRecipient deathRecipient) {
-            mOrganizer = organizer;
-            mDeathRecipient = deathRecipient;
+        void dispose() {
+            mDisposed = true;
+            releaseTasks();
+            handleReplacement();
+        }
+
+        void releaseTasks() {
+            for (int i = mOrganizedTasks.size() - 1; i >= 0; i--) {
+                final Task t = mOrganizedTasks.get(i);
+                t.taskOrganizerDied();
+                removeTask(t);
+            }
+        }
+
+        void handleReplacement() {
+            if (mReplacementFor != null && !mReplacementFor.mDisposed) {
+                mTaskOrganizersForWindowingMode.put(mWindowingMode, mReplacementFor);
+            }
+        }
+
+        void unlinkDeath() {
+            mDisposed = true;
+            mOrganizer.asBinder().unlinkToDeath(mDeathRecipient, 0);
         }
     };
 
 
     final HashMap<Integer, TaskOrganizerState> mTaskOrganizersForWindowingMode = new HashMap();
-    final HashMap<ITaskOrganizer, TaskOrganizerState> mTaskOrganizerStates = new HashMap();
+    final HashMap<IBinder, TaskOrganizerState> mTaskOrganizerStates = new HashMap();
 
     final HashMap<Integer, ITaskOrganizer> mTaskOrganizersByPendingSyncId = new HashMap();
 
     private final WeakHashMap<Task, RunningTaskInfo> mLastSentTaskInfos = new WeakHashMap<>();
     private final ArrayList<Task> mPendingTaskInfoChanges = new ArrayList<>();
 
+    private final BLASTSyncEngine mBLASTSyncEngine = new BLASTSyncEngine();
+
     final ActivityTaskManagerService mService;
 
     RunningTaskInfo mTmpTaskInfo;
@@ -128,17 +181,10 @@
         mService.mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS, func);
     }
 
-    private void clearIfNeeded(int windowingMode) {
-        final TaskOrganizerState oldState = mTaskOrganizersForWindowingMode.get(windowingMode);
-        if (oldState != null) {
-            oldState.mOrganizer.asBinder().unlinkToDeath(oldState.mDeathRecipient, 0);
-        }
-    }
-
     /**
      * Register a TaskOrganizer to manage tasks as they enter the given windowing mode.
      * If there was already a TaskOrganizer for this windowing mode it will be evicted
-     * and receive taskVanished callbacks in the process.
+     * but will continue to organize it's existing tasks.
      */
     @Override
     public void registerTaskOrganizer(ITaskOrganizer organizer, int windowingMode) {
@@ -153,24 +199,25 @@
         final long origId = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
-                clearIfNeeded(windowingMode);
-                DeathRecipient dr = new DeathRecipient(organizer, windowingMode);
-                try {
-                    organizer.asBinder().linkToDeath(dr, 0);
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "TaskOrganizer failed to register death recipient");
-                }
-
-                final TaskOrganizerState state = new TaskOrganizerState(organizer, dr);
+                final TaskOrganizerState state = new TaskOrganizerState(organizer, windowingMode,
+                        mTaskOrganizersForWindowingMode.get(windowingMode));
                 mTaskOrganizersForWindowingMode.put(windowingMode, state);
-
-                mTaskOrganizerStates.put(organizer, state);
+                mTaskOrganizerStates.put(organizer.asBinder(), state);
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
         }
     }
 
+    void unregisterTaskOrganizer(ITaskOrganizer organizer) {
+        final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder());
+        state.unlinkDeath();
+        if (mTaskOrganizersForWindowingMode.get(state.mWindowingMode) == state) {
+            mTaskOrganizersForWindowingMode.remove(state.mWindowingMode);
+        }
+        state.dispose();
+    }
+
     ITaskOrganizer getTaskOrganizer(int windowingMode) {
         final TaskOrganizerState state = mTaskOrganizersForWindowingMode.get(windowingMode);
         if (state == null) {
@@ -179,35 +226,13 @@
         return state.mOrganizer;
     }
 
-    private void sendTaskAppeared(ITaskOrganizer organizer, Task task) {
-        try {
-            organizer.taskAppeared(task.getTaskInfo());
-        } catch (Exception e) {
-            Slog.e(TAG, "Exception sending taskAppeared callback" + e);
-        }
-    }
-
-    private void sendTaskVanished(ITaskOrganizer organizer, Task task) {
-        try {
-            organizer.taskVanished(task.getRemoteToken());
-        } catch (Exception e) {
-            Slog.e(TAG, "Exception sending taskVanished callback" + e);
-        }
-    }
-
     void onTaskAppeared(ITaskOrganizer organizer, Task task) {
-        TaskOrganizerState state = mTaskOrganizerStates.get(organizer);
-
+        final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder());
         state.addTask(task);
-        sendTaskAppeared(organizer, task);
     }
 
     void onTaskVanished(ITaskOrganizer organizer, Task task) {
-        final TaskOrganizerState state = mTaskOrganizerStates.get(organizer);
-        sendTaskVanished(organizer, task);
-
-        // This could trigger TaskAppeared for other tasks in the same stack so make sure
-        // we do this AFTER sending taskVanished.
+        final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder());
         state.removeTask(task);
     }
 
@@ -408,15 +433,35 @@
     }
 
     @Override
-    public void applyContainerTransaction(WindowContainerTransaction t) {
+    public int applyContainerTransaction(WindowContainerTransaction t, ITaskOrganizer organizer) {
         enforceStackPermission("applyContainerTransaction()");
+        int syncId = -1;
         if (t == null) {
-            return;
+            throw new IllegalArgumentException(
+                    "Null transaction passed to applyContainerTransaction");
         }
         long ident = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
                 int effects = 0;
+
+                /**
+                 * If organizer is non-null we are looking to synchronize this transaction
+                 * by collecting all the results in to a SurfaceFlinger transaction and
+                 * then delivering that to the given organizers transaction ready callback.
+                 * See {@link BLASTSyncEngine} for the details of the operation. But at
+                 * a high level we create a sync operation with a given ID and an associated
+                 * organizer. Then we notify each WindowContainer in this WindowContainer
+                 * transaction that it is participating in a sync operation with that
+                 * ID. Once everything is notified we tell the BLASTSyncEngine
+                 * "setSyncReady" which means that we have added everything
+                 * to the set. At any point after this, all the WindowContainers
+                 * will eventually finish applying their changes and notify the
+                 * BLASTSyncEngine which will deliver the Transaction to the organizer.
+                 */
+                if (organizer != null) {
+                    syncId = startSyncWithOrganizer(organizer);
+                }
                 mService.deferWindowLayout();
                 try {
                     ArraySet<WindowContainer> haveConfigChanges = new ArraySet<>();
@@ -429,11 +474,15 @@
                                 entry.getKey()).getContainer();
                         int containerEffect = applyWindowContainerChange(wc, entry.getValue());
                         effects |= containerEffect;
+
                         // Lifecycle changes will trigger ensureConfig for everything.
                         if ((effects & TRANSACT_EFFECTS_LIFECYCLE) == 0
                                 && (containerEffect & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) {
                             haveConfigChanges.add(wc);
                         }
+                        if (syncId >= 0) {
+                            mBLASTSyncEngine.addToSyncSet(syncId, wc);
+                        }
                     }
                     if ((effects & TRANSACT_EFFECTS_LIFECYCLE) != 0) {
                         // Already calls ensureActivityConfig
@@ -454,10 +503,38 @@
                     }
                 } finally {
                     mService.continueWindowLayout();
+                    if (syncId >= 0) {
+                        setSyncReady(syncId);
+                    }
                 }
             }
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
+        return syncId;
+    }
+
+    @Override
+    public void transactionReady(int id, SurfaceControl.Transaction sc) {
+        final ITaskOrganizer organizer = mTaskOrganizersByPendingSyncId.get(id);
+        if (organizer == null) {
+            Slog.e(TAG, "Got transaction complete for unexpected ID");
+        }
+        try {
+            organizer.transactionReady(id, sc);
+        } catch (RemoteException e) {
+        }
+
+        mTaskOrganizersByPendingSyncId.remove(id);
+    }
+
+    int startSyncWithOrganizer(ITaskOrganizer organizer) {
+        int id = mBLASTSyncEngine.startSyncSet(this);
+        mTaskOrganizersByPendingSyncId.put(id, organizer);
+        return id;
+    }
+
+    void setSyncReady(int id) {
+        mBLASTSyncEngine.setReady(id);
     }
 }
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 8672315..9acb660 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -94,7 +94,8 @@
  * changes are made to this class.
  */
 class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<E>
-        implements Comparable<WindowContainer>, Animatable {
+        implements Comparable<WindowContainer>, Animatable,
+                   BLASTSyncEngine.TransactionReadyListener {
 
     private static final String TAG = TAG_WITH_CLASS_NAME ? "WindowContainer" : TAG_WM;
 
@@ -260,6 +261,12 @@
      */
     RemoteToken mRemoteToken = null;
 
+    BLASTSyncEngine mBLASTSyncEngine = new BLASTSyncEngine();
+    SurfaceControl.Transaction mBLASTSyncTransaction = new SurfaceControl.Transaction();
+    boolean mUsingBLASTSyncTransaction = false;
+    BLASTSyncEngine.TransactionReadyListener mWaitingListener;
+    int mWaitingSyncId;
+
     WindowContainer(WindowManagerService wms) {
         mWmService = wms;
         mPendingTransaction = wms.mTransactionFactory.get();
@@ -1837,6 +1844,10 @@
 
     @Override
     public Transaction getPendingTransaction() {
+        if (mUsingBLASTSyncTransaction) {
+            return mBLASTSyncTransaction;
+        }
+
         final DisplayContent displayContent = getDisplayContent();
         if (displayContent != null && displayContent != this) {
             return displayContent.getPendingTransaction();
@@ -2316,4 +2327,38 @@
             return sb.toString();
         }
     }
+
+    @Override
+    public void transactionReady(int mSyncId, SurfaceControl.Transaction mergedTransaction) {
+        mergedTransaction.merge(mBLASTSyncTransaction);
+        mUsingBLASTSyncTransaction = false;
+
+        mWaitingListener.transactionReady(mWaitingSyncId, mergedTransaction);
+
+        mWaitingListener = null;
+        mWaitingSyncId = -1;
+    }
+
+    boolean prepareForSync(BLASTSyncEngine.TransactionReadyListener waitingListener,
+            int waitingId) {
+        boolean willSync = false;
+        if (!isVisible()) {
+            return willSync;
+        }
+        mUsingBLASTSyncTransaction = true;
+
+        int localId = mBLASTSyncEngine.startSyncSet(this);
+        for (int i = 0; i < mChildren.size(); i++) {
+            final WindowContainer child = mChildren.get(i);
+            willSync = mBLASTSyncEngine.addToSyncSet(localId, child) | willSync;
+        }
+
+        // Make sure to set these before we call setReady in case the sync was a no-op
+        mWaitingSyncId = waitingId;
+        mWaitingListener = waitingListener;
+
+        mBLASTSyncEngine.setReady(localId);
+
+        return willSync;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 6e243f0..59eee9c 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -562,4 +562,14 @@
      */
     public abstract void setAccessibilityIdToSurfaceMetadata(
             IBinder windowToken, int accessibilityWindowId);
+
+    /**
+     * Transfers input focus from a given input token to that of the IME window.
+     *
+     * @param sourceInputToken The source token.
+     * @param displayId The display hosting the IME window.
+     * @return Whether transfer was successful.
+     */
+    public abstract boolean transferTouchFocusToImeWindow(@NonNull IBinder sourceInputToken,
+            int displayId);
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index a5b99b0..6330985 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2505,7 +2505,7 @@
                 WindowState win = windowForClientLocked(session, client, false);
                 ProtoLog.d(WM_DEBUG_ADD_REMOVE, "finishDrawingWindow: %s mDrawState=%s",
                         win, (win != null ? win.mWinAnimator.drawStateToString() : "null"));
-                if (win != null && win.mWinAnimator.finishDrawingLocked(postDrawTransaction)) {
+                if (win != null && win.finishDrawing(postDrawTransaction)) {
                     if ((win.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0) {
                         win.getDisplayContent().pendingLayoutChanges |=
                                 WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
@@ -5687,6 +5687,12 @@
 
     @Override
     public void setForceShowSystemBars(boolean show) {
+        boolean isAutomotive = mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_AUTOMOTIVE);
+        if (!isAutomotive) {
+            throw new UnsupportedOperationException("Force showing system bars is only supported"
+                    + "for Automotive use cases.");
+        }
         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR)
                 != PackageManager.PERMISSION_GRANTED) {
             throw new SecurityException("Caller does not hold permission "
@@ -7558,6 +7564,29 @@
                 }
             }
         }
+
+        @Override
+        public boolean transferTouchFocusToImeWindow(@NonNull IBinder sourceInputToken,
+                int displayId) {
+            final IBinder destinationInputToken;
+
+            synchronized (mGlobalLock) {
+                final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
+                if (displayContent == null) {
+                    return false;
+                }
+                final WindowState imeWindow = displayContent.mInputMethodWindow;
+                if (imeWindow == null) {
+                    return false;
+                }
+                if (imeWindow.mInputChannel == null) {
+                    return false;
+                }
+                destinationInputToken = imeWindow.mInputChannel.getToken();
+            }
+
+            return mInputManager.transferTouchFocus(sourceInputToken, destinationInputToken);
+        }
     }
 
     void registerAppFreezeListener(AppFreezeListener listener) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 42e5bbc..b336f8d 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -5689,4 +5689,30 @@
     SurfaceControl getDeferTransactionBarrier() {
         return mWinAnimator.getDeferTransactionBarrier();
     }
+
+    @Override
+    boolean prepareForSync(BLASTSyncEngine.TransactionReadyListener waitingListener,
+            int waitingId) {
+        // TODO(b/148871522): Support child window
+        mWaitingListener = waitingListener;
+        mWaitingSyncId = waitingId;
+        mUsingBLASTSyncTransaction = true;
+        return true;
+    }
+
+    boolean finishDrawing(SurfaceControl.Transaction postDrawTransaction) {
+        if (!mUsingBLASTSyncTransaction) {
+            return mWinAnimator.finishDrawingLocked(postDrawTransaction);
+        }
+        if (postDrawTransaction == null) {
+            postDrawTransaction = new SurfaceControl.Transaction();
+        }
+        postDrawTransaction.merge(mBLASTSyncTransaction);
+        mWaitingListener.transactionReady(mWaitingSyncId, postDrawTransaction);
+        mUsingBLASTSyncTransaction = false;
+
+        mWaitingSyncId = 0;
+        mWaitingListener = null;
+        return mWinAnimator.finishDrawingLocked(null);
+    }
 }
diff --git a/services/core/java/com/android/server/wm/utils/DisplayRotationUtil.java b/services/core/java/com/android/server/wm/utils/DisplayRotationUtil.java
index 9f307bb..59abaab 100644
--- a/services/core/java/com/android/server/wm/utils/DisplayRotationUtil.java
+++ b/services/core/java/com/android/server/wm/utils/DisplayRotationUtil.java
@@ -59,7 +59,7 @@
     }
 
     /**
-     * Compute bounds after rotating teh screen.
+     * Compute bounds after rotating the screen.
      *
      * @param bounds Bounds before the rotation. The array must contain exactly 4 non-null elements.
      * @param rotation rotation constant defined in android.view.Surface.
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 390068e..812bc43 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -109,6 +109,7 @@
         "libinputservice",
         "libprotobuf-cpp-lite",
         "libprotoutil",
+        "libstatshidl",
         "libstatspull",
         "libstatssocket",
         "libstatslog",
@@ -155,6 +156,7 @@
         "android.hardware.vr@1.0",
         "android.frameworks.schedulerservice@1.0",
         "android.frameworks.sensorservice@1.0",
+        "android.frameworks.stats@1.0",
         "android.system.suspend@1.0",
         "service.incremental",
         "suspend_control_aidl_interface-cpp",
diff --git a/services/core/jni/com_android_server_GraphicsStatsService.cpp b/services/core/jni/com_android_server_GraphicsStatsService.cpp
index 7644ade..aa7067e 100644
--- a/services/core/jni/com_android_server_GraphicsStatsService.cpp
+++ b/services/core/jni/com_android_server_GraphicsStatsService.cpp
@@ -137,7 +137,7 @@
 #define TIME_MILLIS_BUCKETS_FIELD_NUMBER 1
 #define FRAME_COUNTS_FIELD_NUMBER 2
 
-static void writeCpuHistogram(stats_event* event,
+static void writeCpuHistogram(AStatsEvent* event,
                               const uirenderer::protos::GraphicsStatsProto& stat) {
     util::ProtoOutputStream proto;
     for (int bucketIndex = 0; bucketIndex < stat.histogram_size(); bucketIndex++) {
@@ -154,10 +154,10 @@
     }
     std::vector<uint8_t> outVector;
     proto.serializeToVector(&outVector);
-    stats_event_write_byte_array(event, outVector.data(), outVector.size());
+    AStatsEvent_writeByteArray(event, outVector.data(), outVector.size());
 }
 
-static void writeGpuHistogram(stats_event* event,
+static void writeGpuHistogram(AStatsEvent* event,
                               const uirenderer::protos::GraphicsStatsProto& stat) {
     util::ProtoOutputStream proto;
     for (int bucketIndex = 0; bucketIndex < stat.gpu_histogram_size(); bucketIndex++) {
@@ -174,20 +174,20 @@
     }
     std::vector<uint8_t> outVector;
     proto.serializeToVector(&outVector);
-    stats_event_write_byte_array(event, outVector.data(), outVector.size());
+    AStatsEvent_writeByteArray(event, outVector.data(), outVector.size());
 }
 
 // graphicsStatsPullCallback is invoked by statsd service to pull GRAPHICS_STATS atom.
-static status_pull_atom_return_t graphicsStatsPullCallback(int32_t atom_tag,
-                                                           pulled_stats_event_list* data,
-                                                           void* cookie) {
+static AStatsManager_PullAtomCallbackReturn graphicsStatsPullCallback(int32_t atom_tag,
+                                                                      AStatsEventList* data,
+                                                                      void* cookie) {
     JNIEnv* env = getJNIEnv();
     if (!env) {
         return false;
     }
     if (gGraphicsStatsServiceObject == nullptr) {
         ALOGE("Failed to get graphicsstats service");
-        return STATS_PULL_SKIP;
+        return AStatsManager_PULL_SKIP;
     }
 
     for (bool lastFullDay : {true, false}) {
@@ -199,7 +199,7 @@
             env->ExceptionDescribe();
             env->ExceptionClear();
             ALOGE("Failed to invoke graphicsstats service");
-            return STATS_PULL_SKIP;
+            return AStatsManager_PULL_SKIP;
         }
         if (!jdata) {
             // null means data is not available for that day.
@@ -218,49 +218,51 @@
         if (!success) {
             ALOGW("Parse failed on GraphicsStatsPuller error='%s' dataSize='%d'",
                   serviceDump.InitializationErrorString().c_str(), dataSize);
-            return STATS_PULL_SKIP;
+            return AStatsManager_PULL_SKIP;
         }
 
         for (int stat_index = 0; stat_index < serviceDump.stats_size(); stat_index++) {
             auto& stat = serviceDump.stats(stat_index);
-            stats_event* event = add_stats_event_to_pull_data(data);
-            stats_event_set_atom_id(event, android::util::GRAPHICS_STATS);
-            stats_event_write_string8(event, stat.package_name().c_str());
-            stats_event_write_int64(event, (int64_t)stat.version_code());
-            stats_event_write_int64(event, (int64_t)stat.stats_start());
-            stats_event_write_int64(event, (int64_t)stat.stats_end());
-            stats_event_write_int32(event, (int32_t)stat.pipeline());
-            stats_event_write_int32(event, (int32_t)stat.summary().total_frames());
-            stats_event_write_int32(event, (int32_t)stat.summary().missed_vsync_count());
-            stats_event_write_int32(event, (int32_t)stat.summary().high_input_latency_count());
-            stats_event_write_int32(event, (int32_t)stat.summary().slow_ui_thread_count());
-            stats_event_write_int32(event, (int32_t)stat.summary().slow_bitmap_upload_count());
-            stats_event_write_int32(event, (int32_t)stat.summary().slow_draw_count());
-            stats_event_write_int32(event, (int32_t)stat.summary().missed_deadline_count());
+            AStatsEvent* event = AStatsEventList_addStatsEvent(data);
+            AStatsEvent_setAtomId(event, android::util::GRAPHICS_STATS);
+            AStatsEvent_writeString(event, stat.package_name().c_str());
+            AStatsEvent_writeInt64(event, (int64_t)stat.version_code());
+            AStatsEvent_writeInt64(event, (int64_t)stat.stats_start());
+            AStatsEvent_writeInt64(event, (int64_t)stat.stats_end());
+            AStatsEvent_writeInt32(event, (int32_t)stat.pipeline());
+            AStatsEvent_writeInt32(event, (int32_t)stat.summary().total_frames());
+            AStatsEvent_writeInt32(event, (int32_t)stat.summary().missed_vsync_count());
+            AStatsEvent_writeInt32(event, (int32_t)stat.summary().high_input_latency_count());
+            AStatsEvent_writeInt32(event, (int32_t)stat.summary().slow_ui_thread_count());
+            AStatsEvent_writeInt32(event, (int32_t)stat.summary().slow_bitmap_upload_count());
+            AStatsEvent_writeInt32(event, (int32_t)stat.summary().slow_draw_count());
+            AStatsEvent_writeInt32(event, (int32_t)stat.summary().missed_deadline_count());
             writeCpuHistogram(event, stat);
             writeGpuHistogram(event, stat);
             // TODO: fill in UI mainline module version, when the feature is available.
-            stats_event_write_int64(event, (int64_t)0);
-            stats_event_write_bool(event, !lastFullDay);
-            stats_event_build(event);
+            AStatsEvent_writeInt64(event, (int64_t)0);
+            AStatsEvent_writeBool(event, !lastFullDay);
+            AStatsEvent_build(event);
         }
     }
-    return STATS_PULL_SUCCESS;
+    return AStatsManager_PULL_SUCCESS;
 }
 
 // Register a puller for GRAPHICS_STATS atom with the statsd service.
 static void nativeInit(JNIEnv* env, jobject javaObject) {
     gGraphicsStatsServiceObject = env->NewGlobalRef(javaObject);
-    pull_atom_metadata metadata = {.cool_down_ns = 10 * 1000000, // 10 milliseconds
-                                   .timeout_ns = 2 * NS_PER_SEC, // 2 seconds
-                                   .additive_fields = nullptr,
-                                   .additive_fields_size = 0};
-    register_stats_pull_atom_callback(android::util::GRAPHICS_STATS, &graphicsStatsPullCallback,
-            &metadata, nullptr);
+    AStatsManager_PullAtomMetadata* metadata = AStatsManager_PullAtomMetadata_obtain();
+    AStatsManager_PullAtomMetadata_setCoolDownNs(metadata, 10 * 1000000);  // 10 milliseconds
+    AStatsManager_PullAtomMetadata_setTimeoutNs(metadata, 2 * NS_PER_SEC); // 2 seconds
+
+    AStatsManager_registerPullAtomCallback(android::util::GRAPHICS_STATS,
+                                           &graphicsStatsPullCallback, metadata, nullptr);
+
+    AStatsManager_PullAtomMetadata_release(metadata);
 }
 
 static void nativeDestructor(JNIEnv* env, jobject javaObject) {
-    //TODO: Unregister the puller callback when a new API is available.
+    AStatsManager_unregisterPullAtomCallback(android::util::GRAPHICS_STATS);
     env->DeleteGlobalRef(gGraphicsStatsServiceObject);
     gGraphicsStatsServiceObject = nullptr;
 }
diff --git a/services/core/jni/com_android_server_SystemServer.cpp b/services/core/jni/com_android_server_SystemServer.cpp
index 67254b8..279ea4b 100644
--- a/services/core/jni/com_android_server_SystemServer.cpp
+++ b/services/core/jni/com_android_server_SystemServer.cpp
@@ -29,6 +29,7 @@
 #include <schedulerservice/SchedulingPolicyService.h>
 #include <sensorservice/SensorService.h>
 #include <sensorservicehidl/SensorManager.h>
+#include <stats/StatsHal.h>
 
 #include <bionic/malloc.h>
 #include <bionic/reserved_signals.h>
@@ -59,6 +60,8 @@
     using ::android::frameworks::schedulerservice::V1_0::implementation::SchedulingPolicyService;
     using ::android::frameworks::sensorservice::V1_0::ISensorManager;
     using ::android::frameworks::sensorservice::V1_0::implementation::SensorManager;
+    using ::android::frameworks::stats::V1_0::IStats;
+    using ::android::frameworks::stats::V1_0::implementation::StatsHal;
     using ::android::hardware::configureRpcThreadpool;
 
     status_t err;
@@ -75,6 +78,10 @@
     sp<ISchedulingPolicyService> schedulingService = new SchedulingPolicyService();
     err = schedulingService->registerAsService();
     ALOGE_IF(err != OK, "Cannot register %s: %d", ISchedulingPolicyService::descriptor, err);
+
+    sp<IStats> statsHal = new StatsHal();
+    err = statsHal->registerAsService();
+    ALOGE_IF(err != OK, "Cannot register %s: %d", IStats::descriptor, err);
 }
 
 static void android_server_SystemServer_initZygoteChildHeapProfiling(JNIEnv* /* env */,
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 212a3a6..49db3d5 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -1563,20 +1563,17 @@
 }
 
 static jboolean nativeTransferTouchFocus(JNIEnv* env,
-        jclass /* clazz */, jlong ptr, jobject fromChannelObj, jobject toChannelObj) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
-
-    sp<InputChannel> fromChannel =
-            android_view_InputChannel_getInputChannel(env, fromChannelObj);
-    sp<InputChannel> toChannel =
-            android_view_InputChannel_getInputChannel(env, toChannelObj);
-
-    if (fromChannel == nullptr || toChannel == nullptr) {
+        jclass /* clazz */, jlong ptr, jobject fromChannelTokenObj, jobject toChannelTokenObj) {
+    if (fromChannelTokenObj == nullptr || toChannelTokenObj == nullptr) {
         return JNI_FALSE;
     }
 
+    sp<IBinder> fromChannelToken = ibinderForJavaObject(env, fromChannelTokenObj);
+    sp<IBinder> toChannelToken = ibinderForJavaObject(env, toChannelTokenObj);
+
+    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
     if (im->getInputManager()->getDispatcher()->transferTouchFocus(
-            fromChannel->getConnectionToken(), toChannel->getConnectionToken())) {
+            fromChannelToken, toChannelToken)) {
         return JNI_TRUE;
     } else {
         return JNI_FALSE;
@@ -1784,7 +1781,7 @@
             (void*) nativeSetInputDispatchMode },
     { "nativeSetSystemUiVisibility", "(JI)V",
             (void*) nativeSetSystemUiVisibility },
-    { "nativeTransferTouchFocus", "(JLandroid/view/InputChannel;Landroid/view/InputChannel;)Z",
+    { "nativeTransferTouchFocus", "(JLandroid/os/IBinder;Landroid/os/IBinder;)Z",
             (void*) nativeTransferTouchFocus },
     { "nativeSetPointerSpeed", "(JI)V",
             (void*) nativeSetPointerSpeed },
diff --git a/services/core/jni/com_android_server_stats_pull_StatsPullAtomService.cpp b/services/core/jni/com_android_server_stats_pull_StatsPullAtomService.cpp
index f5b778e..43cd0a2 100644
--- a/services/core/jni/com_android_server_stats_pull_StatsPullAtomService.cpp
+++ b/services/core/jni/com_android_server_stats_pull_StatsPullAtomService.cpp
@@ -31,32 +31,32 @@
 static server::stats::PowerStatsPuller gPowerStatsPuller;
 static server::stats::SubsystemSleepStatePuller gSubsystemSleepStatePuller;
 
-static status_pull_atom_return_t onDevicePowerMeasurementCallback(int32_t atom_tag,
-                                                                  pulled_stats_event_list* data,
-                                                                  void* cookie) {
+static AStatsManager_PullAtomCallbackReturn onDevicePowerMeasurementCallback(int32_t atom_tag,
+                                                                             AStatsEventList* data,
+                                                                             void* cookie) {
     return gPowerStatsPuller.Pull(atom_tag, data);
 }
 
-static status_pull_atom_return_t subsystemSleepStateCallback(int32_t atom_tag,
-                                                             pulled_stats_event_list* data,
-                                                             void* cookie) {
+static AStatsManager_PullAtomCallbackReturn subsystemSleepStateCallback(int32_t atom_tag,
+                                                                        AStatsEventList* data,
+                                                                        void* cookie) {
     return gSubsystemSleepStatePuller.Pull(atom_tag, data);
 }
 
 static void nativeInit(JNIEnv* env, jobject javaObject) {
     // on device power measurement
     gPowerStatsPuller = server::stats::PowerStatsPuller();
-    register_stats_pull_atom_callback(android::util::ON_DEVICE_POWER_MEASUREMENT,
-                                      onDevicePowerMeasurementCallback,
-                                      /* metadata= */ nullptr,
-                                      /* cookie= */ nullptr);
+    AStatsManager_registerPullAtomCallback(android::util::ON_DEVICE_POWER_MEASUREMENT,
+                                           onDevicePowerMeasurementCallback,
+                                           /* metadata= */ nullptr,
+                                           /* cookie= */ nullptr);
 
     // subsystem sleep state
     gSubsystemSleepStatePuller = server::stats::SubsystemSleepStatePuller();
-    register_stats_pull_atom_callback(android::util::SUBSYSTEM_SLEEP_STATE,
-                                      subsystemSleepStateCallback,
-                                      /* metadata= */ nullptr,
-                                      /* cookie= */ nullptr);
+    AStatsManager_registerPullAtomCallback(android::util::SUBSYSTEM_SLEEP_STATE,
+                                           subsystemSleepStateCallback,
+                                           /* metadata= */ nullptr,
+                                           /* cookie= */ nullptr);
 }
 
 static const JNINativeMethod sMethods[] = {{"nativeInit", "()V", (void*)nativeInit}};
diff --git a/services/core/jni/stats/PowerStatsPuller.cpp b/services/core/jni/stats/PowerStatsPuller.cpp
index e80b5cf..d8f6faa 100644
--- a/services/core/jni/stats/PowerStatsPuller.cpp
+++ b/services/core/jni/stats/PowerStatsPuller.cpp
@@ -78,11 +78,12 @@
 
 PowerStatsPuller::PowerStatsPuller() {}
 
-status_pull_atom_return_t PowerStatsPuller::Pull(int32_t atomTag, pulled_stats_event_list* data) {
+AStatsManager_PullAtomCallbackReturn PowerStatsPuller::Pull(int32_t atomTag,
+                                                            AStatsEventList* data) {
     std::lock_guard<std::mutex> lock(gPowerStatsHalMutex);
 
     if (!getPowerStatsHalLocked()) {
-        return STATS_PULL_SKIP;
+        return AStatsManager_PULL_SKIP;
     }
 
     // Pull getRailInfo if necessary
@@ -100,14 +101,14 @@
         if (!resultSuccess || !ret.isOk()) {
             ALOGE("power.stats getRailInfo() failed. Description: %s", ret.description().c_str());
             gPowerStatsHal = nullptr;
-            return STATS_PULL_SKIP;
+            return AStatsManager_PULL_SKIP;
         }
         // If SUCCESS but empty, or if NOT_SUPPORTED, then never try again.
         if (gRailInfo.empty()) {
             ALOGE("power.stats has no rail information");
             gPowerStatsExist = false; // No rail info, so never try again.
             gPowerStatsHal = nullptr;
-            return STATS_PULL_SKIP;
+            return AStatsManager_PULL_SKIP;
         }
     }
 
@@ -134,15 +135,16 @@
                                             }
                                             const RailInfo& rail = gRailInfo[energyData.index];
 
-                                            stats_event* event = add_stats_event_to_pull_data(data);
-                                            stats_event_set_atom_id(event,
-                                                                    android::util::ON_DEVICE_POWER_MEASUREMENT);
-                                            stats_event_write_string8(event,
-                                                                      rail.subsysName.c_str());
-                                            stats_event_write_string8(event, rail.railName.c_str());
-                                            stats_event_write_int64(event, energyData.timestamp);
-                                            stats_event_write_int64(event, energyData.energy);
-                                            stats_event_build(event);
+                                            AStatsEvent* event =
+                                                    AStatsEventList_addStatsEvent(data);
+                                            AStatsEvent_setAtomId(
+                                                    event,
+                                                    android::util::ON_DEVICE_POWER_MEASUREMENT);
+                                            AStatsEvent_writeString(event, rail.subsysName.c_str());
+                                            AStatsEvent_writeString(event, rail.railName.c_str());
+                                            AStatsEvent_writeInt64(event, energyData.timestamp);
+                                            AStatsEvent_writeInt64(event, energyData.energy);
+                                            AStatsEvent_build(event);
 
                                             ALOGV("power.stat: %s.%s: %llu, %llu",
                                                   rail.subsysName.c_str(), rail.railName.c_str(),
@@ -153,9 +155,9 @@
     if (!resultSuccess || !ret.isOk()) {
         ALOGE("power.stats getEnergyData() failed. Description: %s", ret.description().c_str());
         gPowerStatsHal = nullptr;
-        return STATS_PULL_SKIP;
+        return AStatsManager_PULL_SKIP;
     }
-    return STATS_PULL_SUCCESS;
+    return AStatsManager_PULL_SUCCESS;
 }
 
 } // namespace stats
diff --git a/services/core/jni/stats/PowerStatsPuller.h b/services/core/jni/stats/PowerStatsPuller.h
index 048dbb9..db07d60 100644
--- a/services/core/jni/stats/PowerStatsPuller.h
+++ b/services/core/jni/stats/PowerStatsPuller.h
@@ -29,7 +29,7 @@
 class PowerStatsPuller {
 public:
     PowerStatsPuller();
-    status_pull_atom_return_t Pull(int32_t atomTag, pulled_stats_event_list* data);
+    AStatsManager_PullAtomCallbackReturn Pull(int32_t atomTag, AStatsEventList* data);
 };
 
 } // namespace stats
diff --git a/services/core/jni/stats/SubsystemSleepStatePuller.cpp b/services/core/jni/stats/SubsystemSleepStatePuller.cpp
index c6a836c..45afb5e 100644
--- a/services/core/jni/stats/SubsystemSleepStatePuller.cpp
+++ b/services/core/jni/stats/SubsystemSleepStatePuller.cpp
@@ -55,7 +55,7 @@
 namespace server {
 namespace stats {
 
-static std::function<status_pull_atom_return_t(int32_t atomTag, pulled_stats_event_list* data)>
+static std::function<AStatsManager_PullAtomCallbackReturn(int32_t atomTag, AStatsEventList* data)>
         gPuller = {};
 
 static sp<android::hardware::power::V1_0::IPower> gPowerHalV1_0 = nullptr;
@@ -176,12 +176,12 @@
 }
 
 // The caller must be holding gPowerHalMutex.
-static status_pull_atom_return_t getIPowerStatsDataLocked(int32_t atomTag,
-                                                          pulled_stats_event_list* data) {
+static AStatsManager_PullAtomCallbackReturn getIPowerStatsDataLocked(int32_t atomTag,
+                                                                     AStatsEventList* data) {
     using android::hardware::power::stats::V1_0::Status;
 
     if(!getPowerStatsHalLocked()) {
-        return STATS_PULL_SKIP;
+        return AStatsManager_PULL_SKIP;
     }
     // Get power entity state residency data
     bool success = false;
@@ -194,17 +194,17 @@
                 }
                 for (auto result : results) {
                     for (auto stateResidency : result.stateResidencyData) {
-                        stats_event* event = add_stats_event_to_pull_data(data);
-                        stats_event_set_atom_id(event, android::util::SUBSYSTEM_SLEEP_STATE);
-                        stats_event_write_string8(event,
-                                                  gEntityNames.at(result.powerEntityId).c_str());
-                        stats_event_write_string8(event,
-                                                  gStateNames.at(result.powerEntityId)
-                                                          .at(stateResidency.powerEntityStateId)
-                                                          .c_str());
-                        stats_event_write_int64(event, stateResidency.totalStateEntryCount);
-                        stats_event_write_int64(event, stateResidency.totalTimeInStateMs);
-                        stats_event_build(event);
+                        AStatsEvent* event = AStatsEventList_addStatsEvent(data);
+                        AStatsEvent_setAtomId(event, android::util::SUBSYSTEM_SLEEP_STATE);
+                        AStatsEvent_writeString(event,
+                                                gEntityNames.at(result.powerEntityId).c_str());
+                        AStatsEvent_writeString(event,
+                                                gStateNames.at(result.powerEntityId)
+                                                        .at(stateResidency.powerEntityStateId)
+                                                        .c_str());
+                        AStatsEvent_writeInt64(event, stateResidency.totalStateEntryCount);
+                        AStatsEvent_writeInt64(event, stateResidency.totalTimeInStateMs);
+                        AStatsEvent_build(event);
                     }
                 }
                 success = true;
@@ -213,9 +213,9 @@
     // bool success determines if this succeeded or not.
     checkResultLocked(ret, __func__);
     if (!success) {
-        return STATS_PULL_SKIP;
+        return AStatsManager_PULL_SKIP;
     }
-    return STATS_PULL_SUCCESS;
+    return AStatsManager_PULL_SUCCESS;
 }
 
 // The caller must be holding gPowerHalMutex.
@@ -244,12 +244,12 @@
 }
 
 // The caller must be holding gPowerHalMutex.
-static status_pull_atom_return_t getIPowerDataLocked(int32_t atomTag,
-                                                     pulled_stats_event_list* data) {
+static AStatsManager_PullAtomCallbackReturn getIPowerDataLocked(int32_t atomTag,
+                                                                AStatsEventList* data) {
     using android::hardware::power::V1_0::Status;
 
     if(!getPowerHalLocked()) {
-        return STATS_PULL_SKIP;
+        return AStatsManager_PULL_SKIP;
     }
 
         Return<void> ret;
@@ -259,26 +259,26 @@
 
                     for (size_t i = 0; i < states.size(); i++) {
                         const PowerStatePlatformSleepState& state = states[i];
-                        stats_event* event = add_stats_event_to_pull_data(data);
-                        stats_event_set_atom_id(event, android::util::SUBSYSTEM_SLEEP_STATE);
-                        stats_event_write_string8(event, state.name.c_str());
-                        stats_event_write_string8(event, "");
-                        stats_event_write_int64(event, state.totalTransitions);
-                        stats_event_write_int64(event, state.residencyInMsecSinceBoot);
-                        stats_event_build(event);
+                        AStatsEvent* event = AStatsEventList_addStatsEvent(data);
+                        AStatsEvent_setAtomId(event, android::util::SUBSYSTEM_SLEEP_STATE);
+                        AStatsEvent_writeString(event, state.name.c_str());
+                        AStatsEvent_writeString(event, "");
+                        AStatsEvent_writeInt64(event, state.totalTransitions);
+                        AStatsEvent_writeInt64(event, state.residencyInMsecSinceBoot);
+                        AStatsEvent_build(event);
 
                         ALOGV("powerstate: %s, %lld, %lld, %d", state.name.c_str(),
                               (long long)state.residencyInMsecSinceBoot,
                               (long long)state.totalTransitions,
                               state.supportedOnlyInSuspend ? 1 : 0);
                         for (const auto& voter : state.voters) {
-                            stats_event* event = add_stats_event_to_pull_data(data);
-                            stats_event_set_atom_id(event, android::util::SUBSYSTEM_SLEEP_STATE);
-                            stats_event_write_string8(event, state.name.c_str());
-                            stats_event_write_string8(event, voter.name.c_str());
-                            stats_event_write_int64(event, voter.totalNumberOfTimesVotedSinceBoot);
-                            stats_event_write_int64(event, voter.totalTimeInMsecVotedForSinceBoot);
-                            stats_event_build(event);
+                            AStatsEvent* event = AStatsEventList_addStatsEvent(data);
+                            AStatsEvent_setAtomId(event, android::util::SUBSYSTEM_SLEEP_STATE);
+                            AStatsEvent_writeString(event, state.name.c_str());
+                            AStatsEvent_writeString(event, voter.name.c_str());
+                            AStatsEvent_writeInt64(event, voter.totalNumberOfTimesVotedSinceBoot);
+                            AStatsEvent_writeInt64(event, voter.totalTimeInMsecVotedForSinceBoot);
+                            AStatsEvent_build(event);
 
                             ALOGV("powerstatevoter: %s, %s, %lld, %lld", state.name.c_str(),
                                   voter.name.c_str(),
@@ -288,7 +288,7 @@
                     }
                 });
         if (!checkResultLocked(ret, __func__)) {
-            return STATS_PULL_SKIP;
+            return AStatsManager_PULL_SKIP;
         }
 
         // Trying to cast to IPower 1.1, this will succeed only for devices supporting 1.1
@@ -305,14 +305,14 @@
                                 for (size_t j = 0; j < subsystem.states.size(); j++) {
                                     const PowerStateSubsystemSleepState& state =
                                             subsystem.states[j];
-                                    stats_event* event = add_stats_event_to_pull_data(data);
-                                    stats_event_set_atom_id(event,
-                                                            android::util::SUBSYSTEM_SLEEP_STATE);
-                                    stats_event_write_string8(event, subsystem.name.c_str());
-                                    stats_event_write_string8(event, state.name.c_str());
-                                    stats_event_write_int64(event, state.totalTransitions);
-                                    stats_event_write_int64(event, state.residencyInMsecSinceBoot);
-                                    stats_event_build(event);
+                                    AStatsEvent* event = AStatsEventList_addStatsEvent(data);
+                                    AStatsEvent_setAtomId(event,
+                                                          android::util::SUBSYSTEM_SLEEP_STATE);
+                                    AStatsEvent_writeString(event, subsystem.name.c_str());
+                                    AStatsEvent_writeString(event, state.name.c_str());
+                                    AStatsEvent_writeInt64(event, state.totalTransitions);
+                                    AStatsEvent_writeInt64(event, state.residencyInMsecSinceBoot);
+                                    AStatsEvent_build(event);
 
                                     ALOGV("subsystemstate: %s, %s, %lld, %lld, %lld",
                                           subsystem.name.c_str(), state.name.c_str(),
@@ -324,14 +324,14 @@
                         }
                     });
         }
-        return STATS_PULL_SUCCESS;
+        return AStatsManager_PULL_SUCCESS;
 }
 
 // The caller must be holding gPowerHalMutex.
-std::function<status_pull_atom_return_t(int32_t atomTag, pulled_stats_event_list* data)>
+std::function<AStatsManager_PullAtomCallbackReturn(int32_t atomTag, AStatsEventList* data)>
 getPullerLocked() {
-    std::function<status_pull_atom_return_t(int32_t atomTag, pulled_stats_event_list * data)> ret =
-            {};
+    std::function<AStatsManager_PullAtomCallbackReturn(int32_t atomTag, AStatsEventList * data)>
+            ret = {};
 
     // First see if power.stats HAL is available. Fall back to power HAL if
     // power.stats HAL is unavailable.
@@ -346,8 +346,8 @@
     return ret;
 }
 
-status_pull_atom_return_t SubsystemSleepStatePuller::Pull(int32_t atomTag,
-                                                          pulled_stats_event_list* data) {
+AStatsManager_PullAtomCallbackReturn SubsystemSleepStatePuller::Pull(int32_t atomTag,
+                                                                     AStatsEventList* data) {
     std::lock_guard<std::mutex> lock(gPowerHalMutex);
 
     if(!gPuller) {
@@ -359,7 +359,7 @@
     }
 
     ALOGE("Unable to load Power Hal or power.stats HAL");
-    return STATS_PULL_SKIP;
+    return AStatsManager_PULL_SKIP;
 }
 
 } // namespace stats
diff --git a/services/core/jni/stats/SubsystemSleepStatePuller.h b/services/core/jni/stats/SubsystemSleepStatePuller.h
index 59dbbd2..da9679c 100644
--- a/services/core/jni/stats/SubsystemSleepStatePuller.h
+++ b/services/core/jni/stats/SubsystemSleepStatePuller.h
@@ -29,7 +29,7 @@
 class SubsystemSleepStatePuller {
 public:
     SubsystemSleepStatePuller();
-    status_pull_atom_return_t Pull(int32_t atomTag, pulled_stats_event_list* data);
+    AStatsManager_PullAtomCallbackReturn Pull(int32_t atomTag, AStatsEventList* data);
 };
 
 } // namespace stats
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 65cabad..553ec42 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -4680,12 +4680,15 @@
 
     private void ensureMinimumQuality(
             int userId, ActiveAdmin admin, int minimumQuality, String operation) {
-        if (admin.mPasswordPolicy.quality < minimumQuality
-                && passwordQualityInvocationOrderCheckEnabled(admin.info.getPackageName(),
-                userId)) {
-            throw new IllegalStateException(String.format(
-                    "password quality should be at least %d for %s", minimumQuality, operation));
-        }
+        mInjector.binderWithCleanCallingIdentity(() -> {
+            if (admin.mPasswordPolicy.quality < minimumQuality
+                    && passwordQualityInvocationOrderCheckEnabled(admin.info.getPackageName(),
+                    userId)) {
+                throw new IllegalStateException(String.format(
+                        "password quality should be at least %d for %s",
+                        minimumQuality, operation));
+            }
+        });
     }
 
     @Override
@@ -5343,7 +5346,7 @@
         synchronized (getLockObject()) {
             ActiveAdmin admin = getAdminWithMinimumFailedPasswordsForWipeLocked(
                     userHandle, parent);
-            return admin != null ? admin.getUserHandle().getIdentifier() : UserHandle.USER_NULL;
+            return admin != null ? getUserIdToWipeForFailedPasswords(admin) : UserHandle.USER_NULL;
         }
     }
 
@@ -5354,7 +5357,8 @@
      *   <li>this user and all profiles that don't have their own challenge otherwise.
      * </ul>
      * <p>If the policy for the primary and any other profile are equal, it returns the admin for
-     * the primary profile.
+     * the primary profile. Policy of a PO on an organization-owned device applies to the primary
+     * profile.
      * Returns {@code null} if no participating admin has that policy set.
      */
     private ActiveAdmin getAdminWithMinimumFailedPasswordsForWipeLocked(
@@ -5373,7 +5377,7 @@
             }
 
             // We always favor the primary profile if several profiles have the same value set.
-            int userId = admin.getUserHandle().getIdentifier();
+            final int userId = getUserIdToWipeForFailedPasswords(admin);
             if (count == 0 ||
                     count > admin.maximumFailedPasswordsForWipe ||
                     (count == admin.maximumFailedPasswordsForWipe &&
@@ -7170,7 +7174,7 @@
         }
 
         if (wipeData && strictestAdmin != null) {
-            final int userId = strictestAdmin.getUserHandle().getIdentifier();
+            final int userId = getUserIdToWipeForFailedPasswords(strictestAdmin);
             Slog.i(LOG_TAG, "Max failed password attempts policy reached for admin: "
                     + strictestAdmin.info.getComponent().flattenToShortString()
                     + ". Calling wipeData for user " + userId);
@@ -7201,6 +7205,17 @@
         }
     }
 
+    /**
+     * Returns which user should be wiped if this admin's maximum filed password attempts policy is
+     * violated.
+     */
+    private int getUserIdToWipeForFailedPasswords(ActiveAdmin admin) {
+        final int userId = admin.getUserHandle().getIdentifier();
+        final ComponentName component = admin.info.getComponent();
+        return isProfileOwnerOfOrganizationOwnedDevice(component, userId)
+                ? getProfileParentId(userId) : userId;
+    }
+
     @Override
     public void reportSuccessfulPasswordAttempt(int userHandle) {
         enforceFullCrossUsersPermission(userHandle);
@@ -11583,6 +11598,11 @@
                 millis, "DevicePolicyManagerService: setTime");
         mInjector.binderWithCleanCallingIdentity(
                 () -> mInjector.getTimeDetector().suggestManualTime(manualTimeSuggestion));
+
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_TIME)
+                .setAdmin(who)
+                .write();
         return true;
     }
 
@@ -11599,6 +11619,11 @@
                         timeZone, "DevicePolicyManagerService: setTimeZone");
         mInjector.binderWithCleanCallingIdentity(() ->
                 mInjector.getTimeZoneDetector().suggestManualTimeZone(manualTimeZoneSuggestion));
+
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_TIME_ZONE)
+                .setAdmin(who)
+                .write();
         return true;
     }
 
diff --git a/services/incremental/BinderIncrementalService.h b/services/incremental/BinderIncrementalService.h
index a94a75a..8a09977 100644
--- a/services/incremental/BinderIncrementalService.h
+++ b/services/incremental/BinderIncrementalService.h
@@ -20,12 +20,12 @@
 #include <binder/IServiceManager.h>
 
 #include "IncrementalService.h"
-#include "android/os/incremental/BnIncrementalManagerNative.h"
+#include "android/os/incremental/BnIncrementalService.h"
 #include "incremental_service.h"
 
 namespace android::os::incremental {
 
-class BinderIncrementalService : public BnIncrementalManagerNative,
+class BinderIncrementalService : public BnIncrementalService,
                                  public BinderService<BinderIncrementalService> {
 public:
     BinderIncrementalService(const sp<IServiceManager> &sm);
diff --git a/services/people/java/com/android/server/people/data/CallLogQueryHelper.java b/services/people/java/com/android/server/people/data/CallLogQueryHelper.java
index d825b6b..45e0aac 100644
--- a/services/people/java/com/android/server/people/data/CallLogQueryHelper.java
+++ b/services/people/java/com/android/server/people/data/CallLogQueryHelper.java
@@ -107,7 +107,7 @@
         }
         @Event.EventType int eventType  = CALL_TYPE_TO_EVENT_TYPE.get(callType);
         Event event = new Event.Builder(date, eventType)
-                .setCallDetails(new Event.CallDetails(durationSeconds))
+                .setDurationSeconds((int) durationSeconds)
                 .build();
         mEventConsumer.accept(phoneNumber, event);
         return true;
diff --git a/services/people/java/com/android/server/people/data/ConversationStore.java b/services/people/java/com/android/server/people/data/ConversationStore.java
index f17e1b9..3649921 100644
--- a/services/people/java/com/android/server/people/data/ConversationStore.java
+++ b/services/people/java/com/android/server/people/data/ConversationStore.java
@@ -40,6 +40,9 @@
     // Phone Number -> Shortcut ID
     private final Map<String, String> mPhoneNumberToShortcutIdMap = new ArrayMap<>();
 
+    // Notification Channel ID -> Shortcut ID
+    private final Map<String, String> mNotifChannelIdToShortcutIdMap = new ArrayMap<>();
+
     void addOrUpdate(@NonNull ConversationInfo conversationInfo) {
         mConversationInfoMap.put(conversationInfo.getShortcutId(), conversationInfo);
 
@@ -57,6 +60,11 @@
         if (phoneNumber != null) {
             mPhoneNumberToShortcutIdMap.put(phoneNumber, conversationInfo.getShortcutId());
         }
+
+        String notifChannelId = conversationInfo.getNotificationChannelId();
+        if (notifChannelId != null) {
+            mNotifChannelIdToShortcutIdMap.put(notifChannelId, conversationInfo.getShortcutId());
+        }
     }
 
     void deleteConversation(@NonNull String shortcutId) {
@@ -79,6 +87,11 @@
         if (phoneNumber != null) {
             mPhoneNumberToShortcutIdMap.remove(phoneNumber);
         }
+
+        String notifChannelId = conversationInfo.getNotificationChannelId();
+        if (notifChannelId != null) {
+            mNotifChannelIdToShortcutIdMap.remove(notifChannelId);
+        }
     }
 
     void forAllConversations(@NonNull Consumer<ConversationInfo> consumer) {
@@ -106,4 +119,9 @@
     ConversationInfo getConversationByPhoneNumber(@NonNull String phoneNumber) {
         return getConversation(mPhoneNumberToShortcutIdMap.get(phoneNumber));
     }
+
+    @Nullable
+    ConversationInfo getConversationByNotificationChannelId(@NonNull String notifChannelId) {
+        return getConversation(mNotifChannelIdToShortcutIdMap.get(notifChannelId));
+    }
 }
diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java
index 79503f7..7fdcf42 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -24,8 +24,6 @@
 import android.app.Person;
 import android.app.prediction.AppTarget;
 import android.app.prediction.AppTargetEvent;
-import android.app.usage.UsageEvents;
-import android.app.usage.UsageStatsManagerInternal;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -69,6 +67,7 @@
 import java.util.concurrent.TimeUnit;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
+import java.util.function.Function;
 
 /**
  * A class manages the lifecycle of the conversations and associated data, and exposes the methods
@@ -96,7 +95,6 @@
     private final ContentObserver mMmsSmsContentObserver;
 
     private ShortcutServiceInternal mShortcutServiceInternal;
-    private UsageStatsManagerInternal mUsageStatsManagerInternal;
     private ShortcutManager mShortcutManager;
     private UserManager mUserManager;
 
@@ -118,7 +116,6 @@
     /** Initialization. Called when the system services are up running. */
     public void initialize() {
         mShortcutServiceInternal = LocalServices.getService(ShortcutServiceInternal.class);
-        mUsageStatsManagerInternal = LocalServices.getService(UsageStatsManagerInternal.class);
         mShortcutManager = mContext.getSystemService(ShortcutManager.class);
         mUserManager = mContext.getSystemService(UserManager.class);
 
@@ -293,13 +290,14 @@
                 | ShortcutQuery.FLAG_MATCH_PINNED | ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER;
         return mShortcutServiceInternal.getShortcuts(
                 mInjector.getCallingUserId(), /*callingPackage=*/ PLATFORM_PACKAGE_NAME,
-                /*changedSince=*/ 0, packageName, shortcutIds, /*componentName=*/ null, queryFlags,
-                userId, MY_PID, MY_UID);
+                /*changedSince=*/ 0, packageName, shortcutIds, /*locusIds=*/ null,
+                /*componentName=*/ null, queryFlags, userId, MY_PID, MY_UID);
     }
 
     private void forAllUnlockedUsers(Consumer<UserData> consumer) {
         for (int i = 0; i < mUserDataArray.size(); i++) {
-            UserData userData = mUserDataArray.get(i);
+            int userId = mUserDataArray.keyAt(i);
+            UserData userData = mUserDataArray.get(userId);
             if (userData.isUnlocked()) {
                 consumer.accept(userData);
             }
@@ -385,36 +383,6 @@
     }
 
     @VisibleForTesting
-    @WorkerThread
-    void queryUsageStatsService(@UserIdInt int userId, long currentTime, long lastQueryTime) {
-        UsageEvents usageEvents = mUsageStatsManagerInternal.queryEventsForUser(
-                userId, lastQueryTime, currentTime, false, false);
-        if (usageEvents == null) {
-            return;
-        }
-        while (usageEvents.hasNextEvent()) {
-            UsageEvents.Event e = new UsageEvents.Event();
-            usageEvents.getNextEvent(e);
-
-            String packageName = e.getPackageName();
-            PackageData packageData = getPackage(packageName, userId);
-            if (packageData == null) {
-                continue;
-            }
-            if (e.getEventType() == UsageEvents.Event.SHORTCUT_INVOCATION) {
-                String shortcutId = e.getShortcutId();
-                if (packageData.getConversationStore().getConversation(shortcutId) != null) {
-                    EventHistoryImpl eventHistory =
-                            packageData.getEventStore().getOrCreateShortcutEventHistory(
-                                    shortcutId);
-                    eventHistory.addEvent(
-                            new Event(e.getTimeStamp(), Event.TYPE_SHORTCUT_INVOCATION));
-                }
-            }
-        }
-    }
-
-    @VisibleForTesting
     ContentObserver getContactsContentObserverForTesting(@UserIdInt int userId) {
         return mContactsContentObservers.get(userId);
     }
@@ -611,19 +579,20 @@
      */
     private class UsageStatsQueryRunnable implements Runnable {
 
-        private final int mUserId;
-        private long mLastQueryTime;
+        private final UsageStatsQueryHelper mUsageStatsQueryHelper;
+        private long mLastEventTimestamp;
 
         private UsageStatsQueryRunnable(int userId) {
-            mUserId = userId;
-            mLastQueryTime = System.currentTimeMillis() - QUERY_EVENTS_MAX_AGE_MS;
+            mUsageStatsQueryHelper = mInjector.createUsageStatsQueryHelper(userId,
+                    (packageName) -> getPackage(packageName, userId));
+            mLastEventTimestamp = System.currentTimeMillis() - QUERY_EVENTS_MAX_AGE_MS;
         }
 
         @Override
         public void run() {
-            long currentTime = System.currentTimeMillis();
-            queryUsageStatsService(mUserId, currentTime, mLastQueryTime);
-            mLastQueryTime = currentTime;
+            if (mUsageStatsQueryHelper.querySince(mLastEventTimestamp)) {
+                mLastEventTimestamp = mUsageStatsQueryHelper.getLastEventTimestamp();
+            }
         }
     }
 
@@ -679,6 +648,11 @@
             return new SmsQueryHelper(context, eventConsumer);
         }
 
+        UsageStatsQueryHelper createUsageStatsQueryHelper(@UserIdInt int userId,
+                Function<String, PackageData> packageDataGetter) {
+            return new UsageStatsQueryHelper(userId, packageDataGetter);
+        }
+
         int getCallingUserId() {
             return Binder.getCallingUserHandle().getIdentifier();
         }
diff --git a/services/people/java/com/android/server/people/data/Event.java b/services/people/java/com/android/server/people/data/Event.java
index c2364a2..81411c0 100644
--- a/services/people/java/com/android/server/people/data/Event.java
+++ b/services/people/java/com/android/server/people/data/Event.java
@@ -18,14 +18,12 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.text.format.DateFormat;
 import android.util.ArraySet;
 
-import com.android.internal.util.Preconditions;
-
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
 import java.util.Set;
 
 /** An event representing the interaction with a specific conversation or app. */
@@ -55,6 +53,8 @@
 
     public static final int TYPE_CALL_MISSED = 12;
 
+    public static final int TYPE_IN_APP_CONVERSATION = 13;
+
     @IntDef(prefix = { "TYPE_" }, value = {
             TYPE_SHORTCUT_INVOCATION,
             TYPE_NOTIFICATION_POSTED,
@@ -68,6 +68,7 @@
             TYPE_CALL_OUTGOING,
             TYPE_CALL_INCOMING,
             TYPE_CALL_MISSED,
+            TYPE_IN_APP_CONVERSATION,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface EventType {}
@@ -95,6 +96,7 @@
         CALL_EVENT_TYPES.add(TYPE_CALL_MISSED);
 
         ALL_EVENT_TYPES.add(TYPE_SHORTCUT_INVOCATION);
+        ALL_EVENT_TYPES.add(TYPE_IN_APP_CONVERSATION);
         ALL_EVENT_TYPES.addAll(NOTIFICATION_EVENT_TYPES);
         ALL_EVENT_TYPES.addAll(SHARE_EVENT_TYPES);
         ALL_EVENT_TYPES.addAll(SMS_EVENT_TYPES);
@@ -105,18 +107,18 @@
 
     private final int mType;
 
-    private final CallDetails mCallDetails;
+    private final int mDurationSeconds;
 
     Event(long timestamp, @EventType int type) {
         mTimestamp = timestamp;
         mType = type;
-        mCallDetails = null;
+        mDurationSeconds = 0;
     }
 
     private Event(@NonNull Builder builder) {
         mTimestamp = builder.mTimestamp;
         mType = builder.mType;
-        mCallDetails = builder.mCallDetails;
+        mDurationSeconds = builder.mDurationSeconds;
     }
 
     public long getTimestamp() {
@@ -128,12 +130,35 @@
     }
 
     /**
-     * Gets the {@link CallDetails} of the event. It is only available if the event type is one of
-     * {@code CALL_EVENT_TYPES}, otherwise, it's always {@code null}.
+     * Gets the duration of the event in seconds. It is only available for these events:
+     * <ul>
+     *     <li>{@link #TYPE_CALL_INCOMING}
+     *     <li>{@link #TYPE_CALL_OUTGOING}
+     *     <li>{@link #TYPE_IN_APP_CONVERSATION}
+     * </ul>
+     * <p>For the other event types, it always returns {@code 0}.
      */
-    @Nullable
-    public CallDetails getCallDetails() {
-        return mCallDetails;
+    public int getDurationSeconds() {
+        return mDurationSeconds;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!(obj instanceof Event)) {
+            return false;
+        }
+        Event other = (Event) obj;
+        return mTimestamp == other.mTimestamp
+                && mType == other.mType
+                && mDurationSeconds == other.mDurationSeconds;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mTimestamp, mType, mDurationSeconds);
     }
 
     @Override
@@ -142,32 +167,13 @@
         sb.append("Event {");
         sb.append("timestamp=").append(DateFormat.format("yyyy-MM-dd HH:mm:ss", mTimestamp));
         sb.append(", type=").append(mType);
-        if (mCallDetails != null) {
-            sb.append(", callDetails=").append(mCallDetails);
+        if (mDurationSeconds > 0) {
+            sb.append(", durationSeconds=").append(mDurationSeconds);
         }
         sb.append("}");
         return sb.toString();
     }
 
-    /** Type-specific details of a call event. */
-    public static class CallDetails {
-
-        private final long mDurationSeconds;
-
-        CallDetails(long durationSeconds) {
-            mDurationSeconds = durationSeconds;
-        }
-
-        public long getDurationSeconds() {
-            return mDurationSeconds;
-        }
-
-        @Override
-        public String toString() {
-            return "CallDetails {durationSeconds=" + mDurationSeconds + "}";
-        }
-    }
-
     /** Builder class for {@link Event} objects. */
     static class Builder {
 
@@ -175,16 +181,15 @@
 
         private final int mType;
 
-        private CallDetails mCallDetails;
+        private int mDurationSeconds;
 
         Builder(long timestamp, @EventType int type) {
             mTimestamp = timestamp;
             mType = type;
         }
 
-        Builder setCallDetails(CallDetails callDetails) {
-            Preconditions.checkArgument(CALL_EVENT_TYPES.contains(mType));
-            mCallDetails = callDetails;
+        Builder setDurationSeconds(int durationSeconds) {
+            mDurationSeconds = durationSeconds;
             return this;
         }
 
diff --git a/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java b/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java
new file mode 100644
index 0000000..4e37f47
--- /dev/null
+++ b/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java
@@ -0,0 +1,158 @@
+/*
+ * 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.people.data;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.app.usage.UsageEvents;
+import android.app.usage.UsageStatsManagerInternal;
+import android.content.ComponentName;
+import android.content.LocusId;
+import android.text.format.DateUtils;
+import android.util.ArrayMap;
+
+import com.android.server.LocalServices;
+
+import java.util.Map;
+import java.util.function.Function;
+
+/** A helper class that queries {@link UsageStatsManagerInternal}. */
+class UsageStatsQueryHelper {
+
+    private final UsageStatsManagerInternal mUsageStatsManagerInternal;
+    private final int mUserId;
+    private final Function<String, PackageData> mPackageDataGetter;
+    // Activity name -> Conversation start event (LOCUS_ID_SET)
+    private final Map<ComponentName, UsageEvents.Event> mConvoStartEvents = new ArrayMap<>();
+    private long mLastEventTimestamp;
+
+    /**
+     * @param userId The user whose events are to be queried.
+     * @param packageDataGetter The function to get {@link PackageData} with a package name.
+     */
+    UsageStatsQueryHelper(@UserIdInt int userId,
+            Function<String, PackageData> packageDataGetter) {
+        mUsageStatsManagerInternal = LocalServices.getService(UsageStatsManagerInternal.class);
+        mUserId = userId;
+        mPackageDataGetter = packageDataGetter;
+    }
+
+    /**
+     * Queries {@link UsageStatsManagerInternal} for the recent events occurred since {@code
+     * sinceTime} and adds the derived {@link Event}s into the corresponding package's event store,
+     *
+     * @return true if the query runs successfully and at least one event is found.
+     */
+    boolean querySince(long sinceTime) {
+        UsageEvents usageEvents = mUsageStatsManagerInternal.queryEventsForUser(
+                mUserId, sinceTime, System.currentTimeMillis(), false, false);
+        if (usageEvents == null) {
+            return false;
+        }
+        boolean hasEvents = false;
+        while (usageEvents.hasNextEvent()) {
+            UsageEvents.Event e = new UsageEvents.Event();
+            usageEvents.getNextEvent(e);
+
+            hasEvents = true;
+            mLastEventTimestamp = Math.max(mLastEventTimestamp, e.getTimeStamp());
+            String packageName = e.getPackageName();
+            PackageData packageData = mPackageDataGetter.apply(packageName);
+            if (packageData == null) {
+                continue;
+            }
+            switch (e.getEventType()) {
+                case UsageEvents.Event.SHORTCUT_INVOCATION:
+                    addEventByShortcutId(packageData, e.getShortcutId(),
+                            new Event(e.getTimeStamp(), Event.TYPE_SHORTCUT_INVOCATION));
+                    break;
+                case UsageEvents.Event.NOTIFICATION_INTERRUPTION:
+                    addEventByNotificationChannelId(packageData, e.getNotificationChannelId(),
+                            new Event(e.getTimeStamp(), Event.TYPE_NOTIFICATION_POSTED));
+                    break;
+                case UsageEvents.Event.LOCUS_ID_SET:
+                    onInAppConversationEnded(packageData, e);
+                    LocusId locusId = e.getLocusId() != null ? new LocusId(e.getLocusId()) : null;
+                    if (locusId != null) {
+                        if (packageData.getConversationStore().getConversationByLocusId(locusId)
+                                != null) {
+                            ComponentName activityName =
+                                    new ComponentName(packageName, e.getClassName());
+                            mConvoStartEvents.put(activityName, e);
+                        }
+                    }
+                    break;
+                case UsageEvents.Event.ACTIVITY_PAUSED:
+                case UsageEvents.Event.ACTIVITY_STOPPED:
+                case UsageEvents.Event.ACTIVITY_DESTROYED:
+                    onInAppConversationEnded(packageData, e);
+                    break;
+            }
+        }
+        return hasEvents;
+    }
+
+    long getLastEventTimestamp() {
+        return mLastEventTimestamp;
+    }
+
+    private void onInAppConversationEnded(@NonNull PackageData packageData,
+            @NonNull UsageEvents.Event endEvent) {
+        ComponentName activityName =
+                new ComponentName(endEvent.getPackageName(), endEvent.getClassName());
+        UsageEvents.Event startEvent = mConvoStartEvents.remove(activityName);
+        if (startEvent == null || startEvent.getTimeStamp() >= endEvent.getTimeStamp()) {
+            return;
+        }
+        long durationMillis = endEvent.getTimeStamp() - startEvent.getTimeStamp();
+        Event event = new Event.Builder(startEvent.getTimeStamp(), Event.TYPE_IN_APP_CONVERSATION)
+                .setDurationSeconds((int) (durationMillis / DateUtils.SECOND_IN_MILLIS))
+                .build();
+        addEventByLocusId(packageData, new LocusId(startEvent.getLocusId()), event);
+    }
+
+    private void addEventByShortcutId(PackageData packageData, String shortcutId, Event event) {
+        if (packageData.getConversationStore().getConversation(shortcutId) == null) {
+            return;
+        }
+        EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateShortcutEventHistory(
+                shortcutId);
+        eventHistory.addEvent(event);
+    }
+
+    private void addEventByLocusId(PackageData packageData, LocusId locusId, Event event) {
+        if (packageData.getConversationStore().getConversationByLocusId(locusId) == null) {
+            return;
+        }
+        EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateLocusEventHistory(
+                locusId);
+        eventHistory.addEvent(event);
+    }
+
+    private void addEventByNotificationChannelId(PackageData packageData,
+            String notificationChannelId, Event event) {
+        ConversationInfo conversationInfo =
+                packageData.getConversationStore().getConversationByNotificationChannelId(
+                        notificationChannelId);
+        if (conversationInfo == null) {
+            return;
+        }
+        EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateShortcutEventHistory(
+                conversationInfo.getShortcutId());
+        eventHistory.addEvent(event);
+    }
+}
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 3d9f11f..339ff6b 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -22,6 +22,7 @@
         "services.net",
         "service-jobscheduler",
         "service-permission",
+        "service-blobstore",
         "androidx.test.runner",
         "mockito-target-extended-minus-junit4",
         "platform-test-annotations",
diff --git a/services/tests/mockingservicestests/AndroidManifest.xml b/services/tests/mockingservicestests/AndroidManifest.xml
index 0e24b03..44eb828 100644
--- a/services/tests/mockingservicestests/AndroidManifest.xml
+++ b/services/tests/mockingservicestests/AndroidManifest.xml
@@ -17,6 +17,8 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.android.frameworks.mockingservicestests">
 
+    <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE"/>
+    <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG"/>
     <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
     <uses-permission android:name="android.permission.HARDWARE_TEST"/>
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
diff --git a/services/tests/servicestests/assets/AppOpsUpgradeTest/appops-unversioned.xml b/services/tests/mockingservicestests/assets/AppOpsUpgradeTest/appops-unversioned.xml
similarity index 100%
rename from services/tests/servicestests/assets/AppOpsUpgradeTest/appops-unversioned.xml
rename to services/tests/mockingservicestests/assets/AppOpsUpgradeTest/appops-unversioned.xml
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
index 155de3b..2d5fa23 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
@@ -101,9 +101,8 @@
     private StaticMockitoSession mMockingSession;
 
     private void setupAppOpsService() {
-        mAppOpsService = new AppOpsService(mAppOpsFile, mHandler);
+        mAppOpsService = new AppOpsService(mAppOpsFile, mHandler, spy(sContext));
         mAppOpsService.mHistoricalRegistry.systemReady(sContext.getContentResolver());
-        mAppOpsService.mContext = spy(sContext);
 
         // Always approve all permission checks
         doNothing().when(mAppOpsService.mContext).enforcePermission(anyString(), anyInt(),
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsUpgradeTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
similarity index 85%
rename from services/tests/servicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
rename to services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
index 66d2bab..e48b671 100644
--- a/services/tests/servicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -19,9 +19,17 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
 
 import android.app.AppOpsManager;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.content.res.AssetManager;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -133,10 +141,24 @@
         AppOpsDataParser parser = new AppOpsDataParser(mAppOpsFile);
         assertTrue(parser.parse());
         assertEquals(AppOpsDataParser.NO_VERSION, parser.mVersion);
-        AppOpsService testService = new AppOpsService(mAppOpsFile, mHandler); // trigger upgrade
+
+        // Use mock context and package manager to fake permision package manager calls.
+        Context testContext = spy(mContext);
+
+        // Pretent everybody has all permissions
+        doNothing().when(testContext).enforcePermission(anyString(), anyInt(), anyInt(),
+                nullable(String.class));
+
+        PackageManager testPM = mock(PackageManager.class);
+        when(testContext.getPackageManager()).thenReturn(testPM);
+
+        // Stub out package calls to disable AppOpsService#updatePermissionRevokedCompat
+        when(testPM.getPackagesForUid(anyInt())).thenReturn(null);
+
+        AppOpsService testService = spy(
+                new AppOpsService(mAppOpsFile, mHandler, testContext)); // trigger upgrade
         assertSameModes(testService.mUidStates, AppOpsManager.OP_RUN_IN_BACKGROUND,
                 AppOpsManager.OP_RUN_ANY_IN_BACKGROUND);
-        testService.mContext = mContext;
         mHandler.removeCallbacks(testService.mWriteRunner);
         testService.writeState();
         assertTrue(parser.parse());
diff --git a/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java
new file mode 100644
index 0000000..16dde42
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java
@@ -0,0 +1,342 @@
+/*
+ * 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 static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.server.blob.BlobStoreConfig.SESSION_EXPIRY_TIMEOUT_MILLIS;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.app.blob.BlobHandle;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+import android.util.ArrayMap;
+import android.util.LongSparseArray;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.blob.BlobStoreManagerService.Injector;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.io.File;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class BlobStoreManagerServiceTest {
+    private Context mContext;
+    private Handler mHandler;
+    private BlobStoreManagerService mService;
+
+    private MockitoSession mMockitoSession;
+
+    @Mock
+    private File mBlobsDir;
+
+    private LongSparseArray<BlobStoreSession> mUserSessions;
+    private ArrayMap<BlobHandle, BlobMetadata> mUserBlobs;
+
+    private static final String TEST_PKG1 = "com.example1";
+    private static final String TEST_PKG2 = "com.example2";
+    private static final String TEST_PKG3 = "com.example3";
+
+    private static final int TEST_UID1 = 10001;
+    private static final int TEST_UID2 = 10002;
+    private static final int TEST_UID3 = 10003;
+
+    @Before
+    public void setUp() {
+        // Share classloader to allow package private access.
+        System.setProperty("dexmaker.share_classloader", "true");
+
+        mMockitoSession = mockitoSession()
+                .initMocks(this)
+                .strictness(Strictness.LENIENT)
+                .mockStatic(BlobStoreConfig.class)
+                .startMocking();
+
+        doReturn(mBlobsDir).when(() -> BlobStoreConfig.getBlobsDir());
+        doReturn(true).when(mBlobsDir).exists();
+        doReturn(new File[0]).when(mBlobsDir).listFiles();
+
+        mContext = InstrumentationRegistry.getTargetContext();
+        mHandler = new TestHandler(Looper.getMainLooper());
+        mService = new BlobStoreManagerService(mContext, new TestInjector());
+        mUserSessions = new LongSparseArray<>();
+        mUserBlobs = new ArrayMap<>();
+
+        mService.addUserSessionsForTest(mUserSessions, UserHandle.myUserId());
+        mService.addUserBlobsForTest(mUserBlobs, UserHandle.myUserId());
+    }
+
+    @After
+    public void tearDown() {
+        if (mMockitoSession != null) {
+            mMockitoSession.finishMocking();
+        }
+    }
+
+    @Test
+    public void testHandlePackageRemoved() throws Exception {
+        // Setup sessions
+        final File sessionFile1 = mock(File.class);
+        final long sessionId1 = 11;
+        final BlobStoreSession session1 = createBlobStoreSessionMock(TEST_PKG1, TEST_UID1,
+                sessionId1, sessionFile1);
+        mUserSessions.append(sessionId1, session1);
+
+        final File sessionFile2 = mock(File.class);
+        final long sessionId2 = 25;
+        final BlobStoreSession session2 = createBlobStoreSessionMock(TEST_PKG2, TEST_UID2,
+                sessionId2, sessionFile2);
+        mUserSessions.append(sessionId2, session2);
+
+        final File sessionFile3 = mock(File.class);
+        final long sessionId3 = 37;
+        final BlobStoreSession session3 = createBlobStoreSessionMock(TEST_PKG3, TEST_UID3,
+                sessionId3, sessionFile3);
+        mUserSessions.append(sessionId3, session3);
+
+        final File sessionFile4 = mock(File.class);
+        final long sessionId4 = 48;
+        final BlobStoreSession session4 = createBlobStoreSessionMock(TEST_PKG1, TEST_UID1,
+                sessionId4, sessionFile4);
+        mUserSessions.append(sessionId4, session4);
+
+        // Setup blobs
+        final long blobId1 = 978;
+        final File blobFile1 = mock(File.class);
+        final BlobHandle blobHandle1 = BlobHandle.createWithSha256("digest1".getBytes(),
+                "label1", System.currentTimeMillis(), "tag1");
+        final BlobMetadata blobMetadata1 = createBlobMetadataMock(blobId1, blobFile1, true);
+        mUserBlobs.put(blobHandle1, blobMetadata1);
+
+        final long blobId2 = 347;
+        final File blobFile2 = mock(File.class);
+        final BlobHandle blobHandle2 = BlobHandle.createWithSha256("digest2".getBytes(),
+                "label2", System.currentTimeMillis(), "tag2");
+        final BlobMetadata blobMetadata2 = createBlobMetadataMock(blobId2, blobFile2, false);
+        mUserBlobs.put(blobHandle2, blobMetadata2);
+
+        mService.addKnownIdsForTest(sessionId1, sessionId2, sessionId3, sessionId4,
+                blobId1, blobId2);
+
+        // Invoke test method
+        mService.handlePackageRemoved(TEST_PKG1, TEST_UID1);
+
+        // Verify sessions are removed
+        verify(sessionFile1).delete();
+        verify(sessionFile2, never()).delete();
+        verify(sessionFile3, never()).delete();
+        verify(sessionFile4).delete();
+
+        assertThat(mUserSessions.size()).isEqualTo(2);
+        assertThat(mUserSessions.get(sessionId1)).isNull();
+        assertThat(mUserSessions.get(sessionId2)).isNotNull();
+        assertThat(mUserSessions.get(sessionId3)).isNotNull();
+        assertThat(mUserSessions.get(sessionId4)).isNull();
+
+        // Verify blobs are removed
+        verify(blobMetadata1).removeCommitter(TEST_PKG1, TEST_UID1);
+        verify(blobMetadata1).removeLeasee(TEST_PKG1, TEST_UID1);
+        verify(blobMetadata2).removeCommitter(TEST_PKG1, TEST_UID1);
+        verify(blobMetadata2).removeLeasee(TEST_PKG1, TEST_UID1);
+
+        verify(blobFile1, never()).delete();
+        verify(blobFile2).delete();
+
+        assertThat(mUserBlobs.size()).isEqualTo(1);
+        assertThat(mUserBlobs.get(blobHandle1)).isNotNull();
+        assertThat(mUserBlobs.get(blobHandle2)).isNull();
+
+        assertThat(mService.getKnownIdsForTest()).containsExactly(
+                sessionId2, sessionId3, blobId1);
+    }
+
+    @Test
+    public void testHandleIdleMaintenance_deleteUnknownBlobs() throws Exception {
+        // Setup blob files
+        final long testId1 = 286;
+        final File file1 = mock(File.class);
+        doReturn(String.valueOf(testId1)).when(file1).getName();
+        final long testId2 = 349;
+        final File file2 = mock(File.class);
+        doReturn(String.valueOf(testId2)).when(file2).getName();
+        final long testId3 = 7355;
+        final File file3 = mock(File.class);
+        doReturn(String.valueOf(testId3)).when(file3).getName();
+
+        doReturn(new File[] {file1, file2, file3}).when(mBlobsDir).listFiles();
+        mService.addKnownIdsForTest(testId1, testId3);
+
+        // Invoke test method
+        mService.handleIdleMaintenanceLocked();
+
+        // Verify unknown blobs are delete
+        verify(file1, never()).delete();
+        verify(file2).delete();
+        verify(file3, never()).delete();
+    }
+
+    @Test
+    public void testHandleIdleMaintenance_deleteStaleSessions() throws Exception {
+        // Setup sessions
+        final File sessionFile1 = mock(File.class);
+        doReturn(System.currentTimeMillis() - SESSION_EXPIRY_TIMEOUT_MILLIS + 1000)
+                .when(sessionFile1).lastModified();
+        final long sessionId1 = 342;
+        final BlobHandle blobHandle1 = mock(BlobHandle.class);
+        doReturn(System.currentTimeMillis() - 1000).when(blobHandle1).getExpiryTimeMillis();
+        final BlobStoreSession session1 = createBlobStoreSessionMock(TEST_PKG1, TEST_UID1,
+                sessionId1, sessionFile1, blobHandle1);
+        mUserSessions.append(sessionId1, session1);
+
+        final File sessionFile2 = mock(File.class);
+        doReturn(System.currentTimeMillis() - 20000)
+                .when(sessionFile2).lastModified();
+        final long sessionId2 = 4597;
+        final BlobHandle blobHandle2 = mock(BlobHandle.class);
+        doReturn(System.currentTimeMillis() + 20000).when(blobHandle2).getExpiryTimeMillis();
+        final BlobStoreSession session2 = createBlobStoreSessionMock(TEST_PKG2, TEST_UID2,
+                sessionId2, sessionFile2, blobHandle2);
+        mUserSessions.append(sessionId2, session2);
+
+        final File sessionFile3 = mock(File.class);
+        doReturn(System.currentTimeMillis() - SESSION_EXPIRY_TIMEOUT_MILLIS - 2000)
+                .when(sessionFile3).lastModified();
+        final long sessionId3 = 9484;
+        final BlobHandle blobHandle3 = mock(BlobHandle.class);
+        doReturn(System.currentTimeMillis() + 30000).when(blobHandle3).getExpiryTimeMillis();
+        final BlobStoreSession session3 = createBlobStoreSessionMock(TEST_PKG3, TEST_UID3,
+                sessionId3, sessionFile3, blobHandle3);
+        mUserSessions.append(sessionId3, session3);
+
+        mService.addKnownIdsForTest(sessionId1, sessionId2, sessionId3);
+
+        // Invoke test method
+        mService.handleIdleMaintenanceLocked();
+
+        // Verify stale sessions are removed
+        verify(sessionFile1).delete();
+        verify(sessionFile2, never()).delete();
+        verify(sessionFile3).delete();
+
+        assertThat(mUserSessions.size()).isEqualTo(1);
+        assertThat(mUserSessions.get(sessionId2)).isNotNull();
+
+        assertThat(mService.getKnownIdsForTest()).containsExactly(sessionId2);
+    }
+
+    @Test
+    public void testHandleIdleMaintenance_deleteStaleBlobs() throws Exception {
+        // Setup blobs
+        final long blobId1 = 3489;
+        final File blobFile1 = mock(File.class);
+        final BlobHandle blobHandle1 = BlobHandle.createWithSha256("digest1".getBytes(),
+                "label1", System.currentTimeMillis() - 2000, "tag1");
+        final BlobMetadata blobMetadata1 = createBlobMetadataMock(blobId1, blobFile1, true);
+        mUserBlobs.put(blobHandle1, blobMetadata1);
+
+        final long blobId2 = 78974;
+        final File blobFile2 = mock(File.class);
+        final BlobHandle blobHandle2 = BlobHandle.createWithSha256("digest2".getBytes(),
+                "label2", System.currentTimeMillis() + 30000, "tag2");
+        final BlobMetadata blobMetadata2 = createBlobMetadataMock(blobId2, blobFile2, true);
+        mUserBlobs.put(blobHandle2, blobMetadata2);
+
+        final long blobId3 = 97;
+        final File blobFile3 = mock(File.class);
+        final BlobHandle blobHandle3 = BlobHandle.createWithSha256("digest3".getBytes(),
+                "label3", System.currentTimeMillis() + 4400000, "tag3");
+        final BlobMetadata blobMetadata3 = createBlobMetadataMock(blobId3, blobFile3, false);
+        mUserBlobs.put(blobHandle3, blobMetadata3);
+
+        mService.addKnownIdsForTest(blobId1, blobId2, blobId3);
+
+        // Invoke test method
+        mService.handleIdleMaintenanceLocked();
+
+        // Verify stale blobs are removed
+        verify(blobFile1).delete();
+        verify(blobFile2, never()).delete();
+        verify(blobFile3).delete();
+
+        assertThat(mUserBlobs.size()).isEqualTo(1);
+        assertThat(mUserBlobs.get(blobHandle2)).isNotNull();
+
+        assertThat(mService.getKnownIdsForTest()).containsExactly(blobId2);
+    }
+
+    private BlobStoreSession createBlobStoreSessionMock(String ownerPackageName, int ownerUid,
+            long sessionId, File sessionFile) {
+        return createBlobStoreSessionMock(ownerPackageName, ownerUid, sessionId, sessionFile,
+                mock(BlobHandle.class));
+    }
+    private BlobStoreSession createBlobStoreSessionMock(String ownerPackageName, int ownerUid,
+            long sessionId, File sessionFile, BlobHandle blobHandle) {
+        final BlobStoreSession session = mock(BlobStoreSession.class);
+        doReturn(ownerPackageName).when(session).getOwnerPackageName();
+        doReturn(ownerUid).when(session).getOwnerUid();
+        doReturn(sessionId).when(session).getSessionId();
+        doReturn(sessionFile).when(session).getSessionFile();
+        doReturn(blobHandle).when(session).getBlobHandle();
+        return session;
+    }
+
+    private BlobMetadata createBlobMetadataMock(long blobId, File blobFile, boolean hasLeases) {
+        final BlobMetadata blobMetadata = mock(BlobMetadata.class);
+        doReturn(blobId).when(blobMetadata).getBlobId();
+        doReturn(blobFile).when(blobMetadata).getBlobFile();
+        doReturn(hasLeases).when(blobMetadata).hasLeases();
+        return blobMetadata;
+    }
+
+    private class TestHandler extends Handler {
+        TestHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void dispatchMessage(Message msg) {
+            // Ignore all messages
+        }
+    }
+
+    private class TestInjector extends Injector {
+        @Override
+        public Handler initializeMessageHandler() {
+            return mHandler;
+        }
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
index 4a40b80..6d15302 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -20,6 +20,8 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 
@@ -29,6 +31,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
+import android.view.Display;
 import android.view.DisplayAddress;
 import android.view.SurfaceControl;
 
@@ -47,6 +50,7 @@
 import org.mockito.quality.Strictness;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.LinkedList;
 
 
@@ -167,6 +171,7 @@
      */
     @Test
     public void testDpiValues() throws Exception {
+        // needs default one always
         setUpDisplay(new FakeDisplay(PORT_A));
         setUpDisplay(new FakeDisplay(PORT_B));
         updateAvailableDisplays();
@@ -182,6 +187,67 @@
                 16000);
     }
 
+    @Test
+    public void testAfterDisplayChange_ModesAreUpdated() throws Exception {
+        SurfaceControl.DisplayConfig displayInfo = createFakeDisplayConfig(1920, 1080, 60f);
+        SurfaceControl.DisplayConfig[] configs =
+                new SurfaceControl.DisplayConfig[]{displayInfo};
+        FakeDisplay display = new FakeDisplay(PORT_A, configs, 0);
+        setUpDisplay(display);
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays).isEmpty();
+
+        DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(
+                0).getDisplayDeviceInfoLocked();
+
+        assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(configs.length);
+        assertModeIsSupported(displayDeviceInfo.supportedModes, displayInfo);
+
+        Display.Mode defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
+        assertThat(defaultMode.matches(displayInfo.width, displayInfo.height,
+                displayInfo.refreshRate)).isTrue();
+
+        Display.Mode activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId);
+        assertThat(activeMode.matches(displayInfo.width, displayInfo.height,
+                displayInfo.refreshRate)).isTrue();
+
+        // Change the display
+        SurfaceControl.DisplayConfig addedDisplayInfo = createFakeDisplayConfig(3840, 2160,
+                60f);
+        configs = new SurfaceControl.DisplayConfig[]{displayInfo, addedDisplayInfo};
+        display.configs = configs;
+        display.activeConfig = 1;
+        setUpDisplay(display);
+        mAdapter.registerLocked();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertThat(SurfaceControl.getActiveConfig(display.token)).isEqualTo(1);
+        assertThat(SurfaceControl.getDisplayConfigs(display.token).length).isEqualTo(2);
+
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays.size()).isEqualTo(1);
+
+        DisplayDevice displayDevice = mListener.changedDisplays.get(0);
+        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+        displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
+
+        assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(configs.length);
+        assertModeIsSupported(displayDeviceInfo.supportedModes, displayInfo);
+        assertModeIsSupported(displayDeviceInfo.supportedModes, addedDisplayInfo);
+
+        activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId);
+        assertThat(activeMode.matches(addedDisplayInfo.width, addedDisplayInfo.height,
+                addedDisplayInfo.refreshRate)).isTrue();
+
+        defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
+        assertThat(defaultMode.matches(addedDisplayInfo.width, addedDisplayInfo.height,
+                addedDisplayInfo.refreshRate)).isTrue();
+    }
+
     private void assertDisplayDpi(DisplayDeviceInfo info, int expectedPort,
                                   float expectedXdpi,
                                   float expectedYDpi,
@@ -194,16 +260,40 @@
         assertEquals(expectedDensityDpi, info.densityDpi);
     }
 
+    private Display.Mode getModeById(DisplayDeviceInfo displayDeviceInfo, int modeId) {
+        return Arrays.stream(displayDeviceInfo.supportedModes)
+                .filter(mode -> mode.getModeId() == modeId)
+                .findFirst()
+                .get();
+    }
+
+    private void assertModeIsSupported(Display.Mode[] supportedModes,
+            SurfaceControl.DisplayConfig mode) {
+        assertThat(Arrays.stream(supportedModes).anyMatch(
+                x -> x.matches(mode.width, mode.height, mode.refreshRate))).isTrue();
+    }
+
     private static class FakeDisplay {
         public final DisplayAddress.Physical address;
         public final IBinder token = new Binder();
         public final SurfaceControl.DisplayInfo info;
-        public final SurfaceControl.DisplayConfig config;
+        public SurfaceControl.DisplayConfig[] configs;
+        public int activeConfig;
 
         private FakeDisplay(int port) {
             this.address = createDisplayAddress(port);
             this.info = createFakeDisplayInfo();
-            this.config = createFakeDisplayConfig();
+            this.configs = new SurfaceControl.DisplayConfig[]{
+                    createFakeDisplayConfig(800, 600, 60f)
+            };
+            this.activeConfig = 0;
+        }
+
+        private FakeDisplay(int port, SurfaceControl.DisplayConfig[] configs, int activeConfig) {
+            this.address = createDisplayAddress(port);
+            this.info = createFakeDisplayInfo();
+            this.configs = configs;
+            this.activeConfig = activeConfig;
         }
     }
 
@@ -212,9 +302,9 @@
         doReturn(display.token).when(() ->
                 SurfaceControl.getPhysicalDisplayToken(display.address.getPhysicalDisplayId()));
         doReturn(display.info).when(() -> SurfaceControl.getDisplayInfo(display.token));
-        doReturn(new SurfaceControl.DisplayConfig[] { display.config }).when(
+        doReturn(display.configs).when(
                 () -> SurfaceControl.getDisplayConfigs(display.token));
-        doReturn(0).when(() -> SurfaceControl.getActiveConfig(display.token));
+        doReturn(display.activeConfig).when(() -> SurfaceControl.getActiveConfig(display.token));
         doReturn(0).when(() -> SurfaceControl.getActiveColorMode(display.token));
         doReturn(new int[] { 0 }).when(
                 () -> SurfaceControl.getDisplayColorModes(display.token));
@@ -242,10 +332,12 @@
         return info;
     }
 
-    private static SurfaceControl.DisplayConfig createFakeDisplayConfig() {
+    private static SurfaceControl.DisplayConfig createFakeDisplayConfig(int width, int height,
+            float refreshRate) {
         final SurfaceControl.DisplayConfig config = new SurfaceControl.DisplayConfig();
-        config.width = 800;
-        config.height = 600;
+        config.width = width;
+        config.height = height;
+        config.refreshRate = refreshRate;
         config.xDpi = 100;
         config.yDpi = 100;
         return config;
@@ -266,17 +358,19 @@
 
     private class TestListener implements DisplayAdapter.Listener {
         public ArrayList<DisplayDevice> addedDisplays = new ArrayList<>();
+        public ArrayList<DisplayDevice> changedDisplays = new ArrayList<>();
 
         @Override
         public void onDisplayDeviceEvent(DisplayDevice device, int event) {
             if (event == DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED) {
                 addedDisplays.add(device);
+            } else if (event == DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED) {
+                changedDisplays.add(device);
             }
         }
 
         @Override
         public void onTraversalRequested() {
-
         }
     }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/testables/StaticMockFixtureRule.java b/services/tests/mockingservicestests/src/com/android/server/testables/StaticMockFixtureRule.java
index 8e9d7ee..3566aee 100644
--- a/services/tests/mockingservicestests/src/com/android/server/testables/StaticMockFixtureRule.java
+++ b/services/tests/mockingservicestests/src/com/android/server/testables/StaticMockFixtureRule.java
@@ -21,6 +21,7 @@
 import com.android.dx.mockito.inline.extended.StaticMockitoSession;
 import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
 
+import org.junit.AssumptionViolatedException;
 import org.junit.rules.TestRule;
 import org.junit.rules.TestWatcher;
 import org.junit.runner.Description;
@@ -100,6 +101,11 @@
             }
 
             @Override
+            protected void skipped(AssumptionViolatedException e, Description description) {
+                tearDown(e);
+            }
+
+            @Override
             protected void failed(Throwable e, Description description) {
                 tearDown(e);
             }
diff --git a/services/tests/mockingservicestests/src/com/android/server/testables/StaticMockFixtureRuleTest.java b/services/tests/mockingservicestests/src/com/android/server/testables/StaticMockFixtureRuleTest.java
index b7e71de..8e0ccf0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/testables/StaticMockFixtureRuleTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/testables/StaticMockFixtureRuleTest.java
@@ -16,8 +16,10 @@
 
 package com.android.server.testables;
 
+import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
@@ -30,6 +32,7 @@
 import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
 
 import org.junit.After;
+import org.junit.AssumptionViolatedException;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.Description;
@@ -57,6 +60,8 @@
     @Mock private Supplier<StaticMockFixture> mSupplyA;
     @Mock private Supplier<StaticMockFixture> mSupplyB;
     @Mock private Statement mStatement;
+    @Mock private Statement mSkipStatement;
+    @Mock private Statement mThrowStatement;
     @Mock private Description mDescription;
 
     @Before
@@ -91,17 +96,22 @@
         when(mB1.setUpMockedClasses(any())).thenAnswer(invocation -> invocation.getArgument(0));
         doNothing().when(mB1).setUpMockBehaviors();
         doNothing().when(mStatement).evaluate();
+        doThrow(new AssumptionViolatedException("bad assumption, test should be skipped"))
+                .when(mSkipStatement).evaluate();
+        doThrow(new IllegalArgumentException("bad argument, test should be failed"))
+                .when(mThrowStatement).evaluate();
         doNothing().when(mA1).tearDown();
         doNothing().when(mB1).tearDown();
     }
 
     private InOrder mocksInOrder()  {
-        return inOrder(mSessionBuilder, mSession, mSupplyA, mSupplyB,
-                mA1, mA2, mB1, mB2, mStatement, mDescription);
+        return inOrder(mSessionBuilder, mSession, mSupplyA, mSupplyB, mA1, mA2, mB1, mB2,
+                mStatement, mSkipStatement, mThrowStatement, mDescription);
     }
 
     private void verifyNoMoreImportantMockInteractions()  {
-        verifyNoMoreInteractions(mSupplyA, mSupplyB, mA1, mA2, mB1, mB2, mStatement);
+        verifyNoMoreInteractions(mSupplyA, mSupplyB, mA1, mA2, mB1, mB2, mStatement,
+                mSkipStatement, mThrowStatement);
     }
 
     @Test
@@ -183,4 +193,66 @@
 
         verifyNoMoreImportantMockInteractions();
     }
+
+    @Test
+    public void testTearDownOnSkippedTests() throws Throwable {
+        InOrder inOrder = mocksInOrder();
+
+        StaticMockFixtureRule rule = new StaticMockFixtureRule(mA1, mB1) {
+            @Override public StaticMockitoSessionBuilder getSessionBuilder() {
+                return mSessionBuilder;
+            }
+        };
+        Statement skipStatement = rule.apply(mSkipStatement, mDescription);
+
+        inOrder.verify(mA1).setUpMockedClasses(any(StaticMockitoSessionBuilder.class));
+        inOrder.verify(mB1).setUpMockedClasses(any(StaticMockitoSessionBuilder.class));
+        inOrder.verify(mA1).setUpMockBehaviors();
+        inOrder.verify(mB1).setUpMockBehaviors();
+
+        try {
+            skipStatement.evaluate();
+            fail("AssumptionViolatedException should have been thrown");
+        } catch (AssumptionViolatedException e) {
+            // expected
+        }
+
+        inOrder.verify(mSkipStatement).evaluate();
+        // note: tearDown in reverse order
+        inOrder.verify(mB1).tearDown();
+        inOrder.verify(mA1).tearDown();
+
+        verifyNoMoreImportantMockInteractions();
+    }
+
+    @Test
+    public void testTearDownOnFailedTests() throws Throwable {
+        InOrder inOrder = mocksInOrder();
+
+        StaticMockFixtureRule rule = new StaticMockFixtureRule(mA1, mB1) {
+            @Override public StaticMockitoSessionBuilder getSessionBuilder() {
+                return mSessionBuilder;
+            }
+        };
+        Statement failStatement = rule.apply(mThrowStatement, mDescription);
+
+        inOrder.verify(mA1).setUpMockedClasses(any(StaticMockitoSessionBuilder.class));
+        inOrder.verify(mB1).setUpMockedClasses(any(StaticMockitoSessionBuilder.class));
+        inOrder.verify(mA1).setUpMockBehaviors();
+        inOrder.verify(mB1).setUpMockBehaviors();
+
+        try {
+            failStatement.evaluate();
+            fail("IllegalArgumentException should have been thrown");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+        inOrder.verify(mThrowStatement).evaluate();
+        // note: tearDown in reverse order
+        inOrder.verify(mB1).tearDown();
+        inOrder.verify(mA1).tearDown();
+
+        verifyNoMoreImportantMockInteractions();
+    }
 }
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 8381205..f990810 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -46,7 +46,6 @@
         "service-appsearch",
         "service-jobscheduler",
         "service-permission",
-        "service-blobstore",
         // TODO: remove once Android migrates to JUnit 4.12,
         // which provides assertThrows
         "testng",
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index d2ddff3..1212f20 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -65,6 +65,8 @@
     <uses-permission android:name="android.permission.WATCH_APPOPS" />
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
     <uses-permission android:name="android.permission.SUSPEND_APPS"/>
+    <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE" />
+    <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
     <uses-permission android:name="android.permission.CONTROL_KEYGUARD"/>
     <uses-permission android:name="android.permission.MANAGE_BIND_INSTANT_SERVICE"/>
     <uses-permission android:name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS" />
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
index cf10559..dfe950e 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
@@ -47,6 +47,7 @@
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.nullValue;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThat;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -703,14 +704,20 @@
 
     @Test
     public void takeScreenshot_returnNull() {
-        // no canTakeScreenshot, should return null.
-        when(mMockSecurityPolicy.canTakeScreenshotLocked(mServiceConnection)).thenReturn(false);
-        assertThat(mServiceConnection.takeScreenshot(Display.DEFAULT_DISPLAY), is(nullValue()));
-
         // no checkAccessibilityAccess, should return null.
         when(mMockSecurityPolicy.canTakeScreenshotLocked(mServiceConnection)).thenReturn(true);
         when(mMockSecurityPolicy.checkAccessibilityAccess(mServiceConnection)).thenReturn(false);
-        assertThat(mServiceConnection.takeScreenshot(Display.DEFAULT_DISPLAY), is(nullValue()));
+        mServiceConnection.takeScreenshot(Display.DEFAULT_DISPLAY, new RemoteCallback((result) -> {
+            assertNull(result);
+        }));
+    }
+
+    @Test (expected = SecurityException.class)
+    public void takeScreenshot_throwSecurityException() {
+        // no canTakeScreenshot, should throw security exception.
+        when(mMockSecurityPolicy.canTakeScreenshotLocked(mServiceConnection)).thenReturn(false);
+        mServiceConnection.takeScreenshot(Display.DEFAULT_DISPLAY, new RemoteCallback((result) -> {
+        }));
     }
 
     private void updateServiceInfo(AccessibilityServiceInfo serviceInfo, int eventType,
@@ -852,8 +859,5 @@
 
         @Override
         public void onFingerprintGesture(int gesture) {}
-
-        @Override
-        public void takeScreenshotWithCallback(int displayId, RemoteCallback callback) {}
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
index d38c80c..6aa9287 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
@@ -24,6 +24,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -32,6 +33,9 @@
 import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
 import android.hardware.biometrics.IBiometricService;
 import android.hardware.biometrics.IBiometricServiceReceiver;
+import android.hardware.face.IFaceService;
+import android.hardware.fingerprint.IFingerprintService;
+import android.hardware.iris.IIrisService;
 import android.os.Binder;
 import android.os.Bundle;
 
@@ -61,6 +65,12 @@
     AuthService.Injector mInjector;
     @Mock
     IBiometricService mBiometricService;
+    @Mock
+    IFingerprintService mFingerprintService;
+    @Mock
+    IIrisService mIrisService;
+    @Mock
+    IFaceService mFaceService;
 
     @Before
     public void setUp() {
@@ -76,10 +86,28 @@
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
         when(mInjector.getBiometricService()).thenReturn(mBiometricService);
         when(mInjector.getConfiguration(any())).thenReturn(config);
-        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
-                .thenReturn(true);
-        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_IRIS)).thenReturn(true);
-        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true);
+        when(mInjector.getFingerprintService()).thenReturn(mFingerprintService);
+        when(mInjector.getFaceService()).thenReturn(mFaceService);
+        when(mInjector.getIrisService()).thenReturn(mIrisService);
+    }
+
+    @Test
+    public void testRegisterNullService_doesNotRegister() throws Exception {
+
+        // Config contains Fingerprint, Iris, Face, but services are all null
+
+        when(mInjector.getFingerprintService()).thenReturn(null);
+        when(mInjector.getFaceService()).thenReturn(null);
+        when(mInjector.getIrisService()).thenReturn(null);
+
+        mAuthService = new AuthService(mContext, mInjector);
+        mAuthService.onStart();
+
+        verify(mBiometricService, never()).registerAuthenticator(
+                anyInt(),
+                anyInt(),
+                anyInt(),
+                any());
     }
 
 
diff --git a/services/tests/servicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java
deleted file mode 100644
index ff728e7..0000000
--- a/services/tests/servicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * 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 static com.google.common.truth.Truth.assertThat;
-
-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.app.blob.BlobHandle;
-import android.content.Context;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.UserHandle;
-import android.platform.test.annotations.Presubmit;
-import android.util.ArrayMap;
-import android.util.LongSparseArray;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.server.blob.BlobStoreManagerService.Injector;
-import com.android.server.blob.BlobStoreManagerService.SessionStateChangeListener;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-@Presubmit
-public class BlobStoreManagerServiceTest {
-    private Context mContext;
-    private Handler mHandler;
-    private BlobStoreManagerService mService;
-
-    private LongSparseArray<BlobStoreSession> mUserSessions;
-    private ArrayMap<BlobHandle, BlobMetadata> mUserBlobs;
-
-    private SessionStateChangeListener mStateChangeListener;
-
-    private static final String TEST_PKG1 = "com.example1";
-    private static final String TEST_PKG2 = "com.example2";
-    private static final String TEST_PKG3 = "com.example3";
-
-    private static final int TEST_UID1 = 10001;
-    private static final int TEST_UID2 = 10002;
-    private static final int TEST_UID3 = 10003;
-
-    @Before
-    public void setUp() {
-        // Share classloader to allow package private access.
-        System.setProperty("dexmaker.share_classloader", "true");
-
-        mContext = InstrumentationRegistry.getTargetContext();
-        mHandler = new TestHandler(Looper.getMainLooper());
-        mService = new BlobStoreManagerService(mContext, new TestInjector());
-        mUserSessions = new LongSparseArray<>();
-        mUserBlobs = new ArrayMap<>();
-
-        mService.addUserSessionsForTest(mUserSessions, UserHandle.myUserId());
-        mService.addUserBlobsForTest(mUserBlobs, UserHandle.myUserId());
-
-        mStateChangeListener = mService.new SessionStateChangeListener();
-    }
-
-    @Test
-    public void testHandlePackageRemoved() throws Exception {
-        // Setup sessions
-        final File sessionFile1 = mock(File.class);
-        final long sessionId1 = 11;
-        final BlobStoreSession session1 = createBlobStoreSessionMock(TEST_PKG1, TEST_UID1,
-                sessionId1, sessionFile1);
-        mUserSessions.append(sessionId1, session1);
-
-        final File sessionFile2 = mock(File.class);
-        final long sessionId2 = 25;
-        final BlobStoreSession session2 = createBlobStoreSessionMock(TEST_PKG2, TEST_UID2,
-                sessionId2, sessionFile2);
-        mUserSessions.append(sessionId2, session2);
-
-        final File sessionFile3 = mock(File.class);
-        final long sessionId3 = 37;
-        final BlobStoreSession session3 = createBlobStoreSessionMock(TEST_PKG3, TEST_UID3,
-                sessionId3, sessionFile3);
-        mUserSessions.append(sessionId3, session3);
-
-        final File sessionFile4 = mock(File.class);
-        final long sessionId4 = 48;
-        final BlobStoreSession session4 = createBlobStoreSessionMock(TEST_PKG1, TEST_UID1,
-                sessionId4, sessionFile4);
-        mUserSessions.append(sessionId4, session4);
-
-        // Setup blobs
-        final File blobFile1 = mock(File.class);
-        final BlobHandle blobHandle1 = BlobHandle.createWithSha256("digest1".getBytes(),
-                "label1", System.currentTimeMillis(), "tag1");
-        final BlobMetadata blobMetadata1 = createBlobMetadataMock(blobFile1, true);
-        mUserBlobs.put(blobHandle1, blobMetadata1);
-
-        final File blobFile2 = mock(File.class);
-        final BlobHandle blobHandle2 = BlobHandle.createWithSha256("digest2".getBytes(),
-                "label2", System.currentTimeMillis(), "tag2");
-        final BlobMetadata blobMetadata2 = createBlobMetadataMock(blobFile2, false);
-        mUserBlobs.put(blobHandle2, blobMetadata2);
-
-        // Invoke test method
-        mService.handlePackageRemoved(TEST_PKG1, TEST_UID1);
-
-        // Verify sessions are removed
-        verify(sessionFile1).delete();
-        verify(sessionFile2, never()).delete();
-        verify(sessionFile3, never()).delete();
-        verify(sessionFile4).delete();
-
-        assertThat(mUserSessions.size()).isEqualTo(2);
-        assertThat(mUserSessions.get(sessionId1)).isNull();
-        assertThat(mUserSessions.get(sessionId2)).isNotNull();
-        assertThat(mUserSessions.get(sessionId3)).isNotNull();
-        assertThat(mUserSessions.get(sessionId4)).isNull();
-
-        // Verify blobs are removed
-        verify(blobMetadata1).removeCommitter(TEST_PKG1, TEST_UID1);
-        verify(blobMetadata1).removeLeasee(TEST_PKG1, TEST_UID1);
-        verify(blobMetadata2).removeCommitter(TEST_PKG1, TEST_UID1);
-        verify(blobMetadata2).removeLeasee(TEST_PKG1, TEST_UID1);
-
-        verify(blobFile1, never()).delete();
-        verify(blobFile2).delete();
-
-        assertThat(mUserBlobs.size()).isEqualTo(1);
-        assertThat(mUserBlobs.get(blobHandle1)).isNotNull();
-        assertThat(mUserBlobs.get(blobHandle2)).isNull();
-    }
-
-    private BlobStoreSession createBlobStoreSessionMock(String ownerPackageName, int ownerUid,
-            long sessionId, File sessionFile) {
-        final BlobStoreSession session = mock(BlobStoreSession.class);
-        when(session.getOwnerPackageName()).thenReturn(ownerPackageName);
-        when(session.getOwnerUid()).thenReturn(ownerUid);
-        when(session.getSessionId()).thenReturn(sessionId);
-        when(session.getSessionFile()).thenReturn(sessionFile);
-        return session;
-    }
-
-    private BlobMetadata createBlobMetadataMock(File blobFile, boolean hasLeases) {
-        final BlobMetadata blobMetadata = mock(BlobMetadata.class);
-        when(blobMetadata.getBlobFile()).thenReturn(blobFile);
-        when(blobMetadata.hasLeases()).thenReturn(hasLeases);
-        return blobMetadata;
-    }
-
-    private class TestHandler extends Handler {
-        TestHandler(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void dispatchMessage(Message msg) {
-            // Ignore all messages
-        }
-    }
-
-    private class TestInjector extends Injector {
-        @Override
-        public Handler initializeMessageHandler() {
-            return mHandler;
-        }
-    }
-}
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 b193a34..39a749f 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -96,6 +96,7 @@
 import android.util.ArraySet;
 import android.util.Pair;
 
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.R;
@@ -4535,6 +4536,86 @@
                 .removeUserEvenWhenDisallowed(anyInt());
     }
 
+    public void testMaximumFailedDevicePasswordAttemptsReachedOrgOwnedManagedProfile()
+            throws Exception {
+        final int MANAGED_PROFILE_USER_ID = 15;
+        final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 19436);
+        addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
+
+        // Even if the caller is the managed profile, the current user is the user 0
+        when(getServices().iactivityManager.getCurrentUser())
+                .thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0));
+
+        configureProfileOwnerOfOrgOwnedDevice(admin1, MANAGED_PROFILE_USER_ID);
+
+        mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
+        dpm.setMaximumFailedPasswordsForWipe(admin1, 3);
+
+        assertEquals(3, dpm.getMaximumFailedPasswordsForWipe(admin1));
+        assertEquals(3, dpm.getMaximumFailedPasswordsForWipe(null));
+
+        mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+        mContext.callerPermissions.add(permission.BIND_DEVICE_ADMIN);
+
+        assertEquals(3, dpm.getMaximumFailedPasswordsForWipe(null, UserHandle.USER_SYSTEM));
+        // Check that primary will be wiped as a result of failed primary user unlock attempts.
+        assertEquals(UserHandle.USER_SYSTEM,
+                dpm.getProfileWithMinimumFailedPasswordsForWipe(UserHandle.USER_SYSTEM));
+
+        // Failed password attempts on the parent user are taken into account, as there isn't a
+        // separate work challenge.
+        dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM);
+        dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM);
+        dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM);
+
+        // For managed profile on an organization owned device, the whole device should be wiped.
+        verify(getServices().recoverySystem).rebootWipeUserData(
+                /*shutdown=*/ eq(false), anyString(), /*force=*/ eq(true),
+                /*wipeEuicc=*/ eq(false));
+    }
+
+    public void testMaximumFailedProfilePasswordAttemptsReachedOrgOwnedManagedProfile()
+            throws Exception {
+        final int MANAGED_PROFILE_USER_ID = 15;
+        final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 19436);
+        addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
+
+        // Even if the caller is the managed profile, the current user is the user 0
+        when(getServices().iactivityManager.getCurrentUser())
+                .thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0));
+
+        doReturn(true).when(getServices().lockPatternUtils)
+                .isSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID);
+
+        // Configure separate challenge.
+        configureProfileOwnerOfOrgOwnedDevice(admin1, MANAGED_PROFILE_USER_ID);
+
+        mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
+        dpm.setMaximumFailedPasswordsForWipe(admin1, 3);
+
+        mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+        mContext.callerPermissions.add(permission.BIND_DEVICE_ADMIN);
+
+        assertEquals(0, dpm.getMaximumFailedPasswordsForWipe(null, UserHandle.USER_SYSTEM));
+        assertEquals(3, dpm.getMaximumFailedPasswordsForWipe(null, MANAGED_PROFILE_USER_ID));
+        // Check that the policy is not affecting primary profile challenge.
+        assertEquals(UserHandle.USER_NULL,
+                dpm.getProfileWithMinimumFailedPasswordsForWipe(UserHandle.USER_SYSTEM));
+        // Check that primary will be wiped as a result of failed profile unlock attempts.
+        assertEquals(UserHandle.USER_SYSTEM,
+                dpm.getProfileWithMinimumFailedPasswordsForWipe(MANAGED_PROFILE_USER_ID));
+
+        // Simulate three failed attempts at solving the separate challenge.
+        dpm.reportFailedPasswordAttempt(MANAGED_PROFILE_USER_ID);
+        dpm.reportFailedPasswordAttempt(MANAGED_PROFILE_USER_ID);
+        dpm.reportFailedPasswordAttempt(MANAGED_PROFILE_USER_ID);
+
+        // For managed profile on an organization owned device, the whole device should be wiped.
+        verify(getServices().recoverySystem).rebootWipeUserData(
+                /*shutdown=*/ eq(false), anyString(), /*force=*/ eq(true),
+                /*wipeEuicc=*/ eq(false));
+    }
+
     public void testGetPermissionGrantState() throws Exception {
         final String permission = "some.permission";
         final String app1 = "com.example.app1";
@@ -5366,26 +5447,28 @@
         assertTrue(dpms.isAdminActive(admin1, UserHandle.USER_SYSTEM));
     }
 
-    public void testRevertDeviceOwnership_adminAndDeviceMigrated() throws Exception {
-        DpmTestUtils.writeInputStreamToFile(
-                getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_migrated),
-                getDeviceOwnerPoliciesFile());
-        DpmTestUtils.writeInputStreamToFile(
-                getRawStream(com.android.frameworks.servicestests.R.raw.device_owner_migrated),
-                getDeviceOwnerFile());
-        assertDeviceOwnershipRevertedWithFakeTransferMetadata();
-    }
+    // @FlakyTest(bugId = 148934649)
+    // public void testRevertDeviceOwnership_adminAndDeviceMigrated() throws Exception {
+    //     DpmTestUtils.writeInputStreamToFile(
+    //             getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_migrated),
+    //             getDeviceOwnerPoliciesFile());
+    //     DpmTestUtils.writeInputStreamToFile(
+    //             getRawStream(com.android.frameworks.servicestests.R.raw.device_owner_migrated),
+    //             getDeviceOwnerFile());
+    //     assertDeviceOwnershipRevertedWithFakeTransferMetadata();
+    // }
 
-    public void testRevertDeviceOwnership_deviceNotMigrated()
-            throws Exception {
-        DpmTestUtils.writeInputStreamToFile(
-                getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_migrated),
-                getDeviceOwnerPoliciesFile());
-        DpmTestUtils.writeInputStreamToFile(
-                getRawStream(com.android.frameworks.servicestests.R.raw.device_owner_not_migrated),
-                getDeviceOwnerFile());
-        assertDeviceOwnershipRevertedWithFakeTransferMetadata();
-    }
+    // @FlakyTest(bugId = 148934649)
+    // public void testRevertDeviceOwnership_deviceNotMigrated()
+    //         throws Exception {
+    //     DpmTestUtils.writeInputStreamToFile(
+    //             getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_migrated),
+    //             getDeviceOwnerPoliciesFile());
+    //     DpmTestUtils.writeInputStreamToFile(
+    //             getRawStream(com.android.frameworks.servicestests.R.raw.device_owner_not_migrated),
+    //             getDeviceOwnerFile());
+    //     assertDeviceOwnershipRevertedWithFakeTransferMetadata();
+    // }
 
     public void testRevertDeviceOwnership_adminAndDeviceNotMigrated()
             throws Exception {
@@ -5407,29 +5490,31 @@
         UserHandle userHandle = UserHandle.of(DpmMockContext.CALLER_USER_HANDLE);
     }
 
-    public void testRevertProfileOwnership_adminAndProfileMigrated() throws Exception {
-        getServices().addUser(DpmMockContext.CALLER_USER_HANDLE, 0,
-                UserManager.USER_TYPE_PROFILE_MANAGED, UserHandle.USER_SYSTEM);
-        DpmTestUtils.writeInputStreamToFile(
-                getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_migrated),
-                getProfileOwnerPoliciesFile());
-        DpmTestUtils.writeInputStreamToFile(
-                getRawStream(com.android.frameworks.servicestests.R.raw.profile_owner_migrated),
-                getProfileOwnerFile());
-        assertProfileOwnershipRevertedWithFakeTransferMetadata();
-    }
+    // @FlakyTest(bugId = 148934649)
+    // public void testRevertProfileOwnership_adminAndProfileMigrated() throws Exception {
+    //     getServices().addUser(DpmMockContext.CALLER_USER_HANDLE, 0,
+    //             UserManager.USER_TYPE_PROFILE_MANAGED, UserHandle.USER_SYSTEM);
+    //     DpmTestUtils.writeInputStreamToFile(
+    //             getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_migrated),
+    //             getProfileOwnerPoliciesFile());
+    //     DpmTestUtils.writeInputStreamToFile(
+    //             getRawStream(com.android.frameworks.servicestests.R.raw.profile_owner_migrated),
+    //             getProfileOwnerFile());
+    //     assertProfileOwnershipRevertedWithFakeTransferMetadata();
+    // }
 
-    public void testRevertProfileOwnership_profileNotMigrated() throws Exception {
-        getServices().addUser(DpmMockContext.CALLER_USER_HANDLE, 0,
-                UserManager.USER_TYPE_PROFILE_MANAGED, UserHandle.USER_SYSTEM);
-        DpmTestUtils.writeInputStreamToFile(
-                getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_migrated),
-                getProfileOwnerPoliciesFile());
-        DpmTestUtils.writeInputStreamToFile(
-                getRawStream(com.android.frameworks.servicestests.R.raw.profile_owner_not_migrated),
-                getProfileOwnerFile());
-        assertProfileOwnershipRevertedWithFakeTransferMetadata();
-    }
+    // @FlakyTest(bugId = 148934649)
+    // public void testRevertProfileOwnership_profileNotMigrated() throws Exception {
+    //     getServices().addUser(DpmMockContext.CALLER_USER_HANDLE, 0,
+    //             UserManager.USER_TYPE_PROFILE_MANAGED, UserHandle.USER_SYSTEM);
+    //     DpmTestUtils.writeInputStreamToFile(
+    //             getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_migrated),
+    //             getProfileOwnerPoliciesFile());
+    //     DpmTestUtils.writeInputStreamToFile(
+    //             getRawStream(com.android.frameworks.servicestests.R.raw.profile_owner_not_migrated),
+    //             getProfileOwnerFile());
+    //     assertProfileOwnershipRevertedWithFakeTransferMetadata();
+    // }
 
     public void testRevertProfileOwnership_adminAndProfileNotMigrated() throws Exception {
         getServices().addUser(DpmMockContext.CALLER_USER_HANDLE, 0,
@@ -5770,8 +5855,7 @@
         when(getServices().userManager.getProfileParent(eq(UserHandle.of(userId))))
                 .thenReturn(UserHandle.SYSTEM);
         final long ident = mServiceContext.binder.clearCallingIdentity();
-        mServiceContext.binder.callingUid =
-                UserHandle.getUid(DpmMockContext.CALLER_USER_HANDLE, DpmMockContext.SYSTEM_UID);
+        mServiceContext.binder.callingUid = UserHandle.getUid(userId, DpmMockContext.SYSTEM_UID);
 
         configureContextForAccess(mServiceContext, true);
         runAsCaller(mServiceContext, dpms, dpm -> {
diff --git a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
index 4a7636a..c9ec874 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
@@ -423,7 +423,8 @@
         PackageInfo packageInfo =
                 mRealContext
                         .getPackageManager()
-                        .getPackageInfo(TEST_FRAMEWORK_PACKAGE, PackageManager.GET_SIGNATURES);
+                        .getPackageInfo(TEST_FRAMEWORK_PACKAGE,
+                                PackageManager.GET_SIGNING_CERTIFICATES);
         doReturn(packageInfo).when(mSpyPackageManager).getPackageInfo(eq(INSTALLER), anyInt());
         doReturn(1).when(mSpyPackageManager).getPackageUid(eq(INSTALLER), anyInt());
         return makeVerificationIntent(INSTALLER);
diff --git a/services/tests/servicestests/src/com/android/server/people/data/CallLogQueryHelperTest.java b/services/tests/servicestests/src/com/android/server/people/data/CallLogQueryHelperTest.java
index 7a16d17..a545010 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/CallLogQueryHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/CallLogQueryHelperTest.java
@@ -92,7 +92,7 @@
         assertEquals(1, events.size());
         assertEquals(Event.TYPE_CALL_INCOMING, events.get(0).getType());
         assertEquals(100L, events.get(0).getTimestamp());
-        assertEquals(30L, events.get(0).getCallDetails().getDurationSeconds());
+        assertEquals(30L, events.get(0).getDurationSeconds());
     }
 
     @Test
@@ -108,7 +108,7 @@
         assertEquals(1, events.size());
         assertEquals(Event.TYPE_CALL_OUTGOING, events.get(0).getType());
         assertEquals(100L, events.get(0).getTimestamp());
-        assertEquals(40L, events.get(0).getCallDetails().getDurationSeconds());
+        assertEquals(40L, events.get(0).getDurationSeconds());
     }
 
     @Test
@@ -124,7 +124,7 @@
         assertEquals(1, events.size());
         assertEquals(Event.TYPE_CALL_MISSED, events.get(0).getType());
         assertEquals(100L, events.get(0).getTimestamp());
-        assertEquals(0L, events.get(0).getCallDetails().getDurationSeconds());
+        assertEquals(0L, events.get(0).getDurationSeconds());
     }
 
     @Test
@@ -145,7 +145,7 @@
         assertEquals(100L, events.get(0).getTimestamp());
         assertEquals(Event.TYPE_CALL_OUTGOING, events.get(1).getType());
         assertEquals(110L, events.get(1).getTimestamp());
-        assertEquals(40L, events.get(1).getCallDetails().getDurationSeconds());
+        assertEquals(40L, events.get(1).getDurationSeconds());
     }
 
     private class EventConsumer implements BiConsumer<String, Event> {
diff --git a/services/tests/servicestests/src/com/android/server/people/data/ConversationStoreTest.java b/services/tests/servicestests/src/com/android/server/people/data/ConversationStoreTest.java
index a40c6ab..bbcb54e 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/ConversationStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/ConversationStoreTest.java
@@ -37,6 +37,7 @@
 public final class ConversationStoreTest {
 
     private static final String SHORTCUT_ID = "abc";
+    private static final String NOTIFICATION_CHANNEL_ID = "test : abc";
     private static final LocusId LOCUS_ID = new LocusId("def");
     private static final Uri CONTACT_URI = Uri.parse("tel:+1234567890");
     private static final String PHONE_NUMBER = "+1234567890";
@@ -59,16 +60,19 @@
 
     @Test
     public void testUpdateConversation() {
-        ConversationInfo original =
-                buildConversationInfo(SHORTCUT_ID, LOCUS_ID, CONTACT_URI, PHONE_NUMBER);
+        ConversationInfo original = buildConversationInfo(SHORTCUT_ID, LOCUS_ID, CONTACT_URI,
+                PHONE_NUMBER, null);
         mConversationStore.addOrUpdate(original);
         assertEquals(LOCUS_ID, mConversationStore.getConversation(SHORTCUT_ID).getLocusId());
+        assertNull(mConversationStore.getConversation(SHORTCUT_ID).getNotificationChannelId());
 
         LocusId newLocusId = new LocusId("ghi");
         ConversationInfo update = buildConversationInfo(
-                SHORTCUT_ID, newLocusId, CONTACT_URI, PHONE_NUMBER);
+                SHORTCUT_ID, newLocusId, CONTACT_URI, PHONE_NUMBER, NOTIFICATION_CHANNEL_ID);
         mConversationStore.addOrUpdate(update);
-        assertEquals(newLocusId, mConversationStore.getConversation(SHORTCUT_ID).getLocusId());
+        ConversationInfo updated = mConversationStore.getConversation(SHORTCUT_ID);
+        assertEquals(newLocusId, updated.getLocusId());
+        assertEquals(NOTIFICATION_CHANNEL_ID, updated.getNotificationChannelId());
     }
 
     @Test
@@ -97,8 +101,8 @@
 
     @Test
     public void testGetConversationByLocusId() {
-        ConversationInfo in =
-                buildConversationInfo(SHORTCUT_ID, LOCUS_ID, CONTACT_URI, PHONE_NUMBER);
+        ConversationInfo in = buildConversationInfo(SHORTCUT_ID, LOCUS_ID, CONTACT_URI,
+                PHONE_NUMBER, NOTIFICATION_CHANNEL_ID);
         mConversationStore.addOrUpdate(in);
         ConversationInfo out = mConversationStore.getConversationByLocusId(LOCUS_ID);
         assertNotNull(out);
@@ -110,8 +114,8 @@
 
     @Test
     public void testGetConversationByContactUri() {
-        ConversationInfo in =
-                buildConversationInfo(SHORTCUT_ID, LOCUS_ID, CONTACT_URI, PHONE_NUMBER);
+        ConversationInfo in = buildConversationInfo(SHORTCUT_ID, LOCUS_ID, CONTACT_URI,
+                PHONE_NUMBER, NOTIFICATION_CHANNEL_ID);
         mConversationStore.addOrUpdate(in);
         ConversationInfo out = mConversationStore.getConversationByContactUri(CONTACT_URI);
         assertNotNull(out);
@@ -123,8 +127,8 @@
 
     @Test
     public void testGetConversationByPhoneNumber() {
-        ConversationInfo in =
-                buildConversationInfo(SHORTCUT_ID, LOCUS_ID, CONTACT_URI, PHONE_NUMBER);
+        ConversationInfo in = buildConversationInfo(SHORTCUT_ID, LOCUS_ID, CONTACT_URI,
+                PHONE_NUMBER, NOTIFICATION_CHANNEL_ID);
         mConversationStore.addOrUpdate(in);
         ConversationInfo out = mConversationStore.getConversationByPhoneNumber(PHONE_NUMBER);
         assertNotNull(out);
@@ -134,17 +138,34 @@
         assertNull(mConversationStore.getConversationByPhoneNumber(PHONE_NUMBER));
     }
 
+    @Test
+    public void testGetConversationByNotificationChannelId() {
+        ConversationInfo in = buildConversationInfo(SHORTCUT_ID, LOCUS_ID, CONTACT_URI,
+                PHONE_NUMBER, NOTIFICATION_CHANNEL_ID);
+        mConversationStore.addOrUpdate(in);
+        ConversationInfo out = mConversationStore.getConversationByNotificationChannelId(
+                NOTIFICATION_CHANNEL_ID);
+        assertNotNull(out);
+        assertEquals(SHORTCUT_ID, out.getShortcutId());
+
+        mConversationStore.deleteConversation(SHORTCUT_ID);
+        assertNull(
+                mConversationStore.getConversationByNotificationChannelId(NOTIFICATION_CHANNEL_ID));
+    }
+
     private static ConversationInfo buildConversationInfo(String shortcutId) {
-        return buildConversationInfo(shortcutId, null, null, null);
+        return buildConversationInfo(shortcutId, null, null, null, null);
     }
 
     private static ConversationInfo buildConversationInfo(
-            String shortcutId, LocusId locusId, Uri contactUri, String phoneNumber) {
+            String shortcutId, LocusId locusId, Uri contactUri, String phoneNumber,
+            String notificationChannelId) {
         return new ConversationInfo.Builder()
                 .setShortcutId(shortcutId)
                 .setLocusId(locusId)
                 .setContactUri(contactUri)
                 .setContactPhoneNumber(phoneNumber)
+                .setNotificationChannelId(notificationChannelId)
                 .setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED)
                 .setVip(true)
                 .setBubbled(true)
diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
index 62ea425..ad5c57d 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
@@ -16,15 +16,12 @@
 
 package com.android.server.people.data;
 
-import static android.app.usage.UsageEvents.Event.SHORTCUT_INVOCATION;
-
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
@@ -40,7 +37,6 @@
 import android.app.prediction.AppTarget;
 import android.app.prediction.AppTargetEvent;
 import android.app.prediction.AppTargetId;
-import android.app.usage.UsageEvents;
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -232,7 +228,7 @@
         mDataManager.getShortcut(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID);
         verify(mShortcutServiceInternal).getShortcuts(anyInt(), anyString(), anyLong(),
                 eq(TEST_PKG_NAME), eq(Collections.singletonList(TEST_SHORTCUT_ID)),
-                eq(null), anyInt(), eq(USER_ID_PRIMARY), anyInt(), anyInt());
+                eq(null), eq(null), anyInt(), eq(USER_ID_PRIMARY), anyInt(), anyInt());
     }
 
     @Test
@@ -263,6 +259,7 @@
     @Test
     public void testContactsChanged() {
         mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+        mDataManager.onUserUnlocked(USER_ID_SECONDARY);
 
         ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
                 buildPerson());
@@ -289,6 +286,7 @@
     @Test
     public void testNotificationListener() {
         mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+        mDataManager.onUserUnlocked(USER_ID_SECONDARY);
 
         ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
                 buildPerson());
@@ -310,37 +308,9 @@
     }
 
     @Test
-    public void testQueryUsageStatsService() {
-        UsageEvents.Event e = new UsageEvents.Event(SHORTCUT_INVOCATION,
-                System.currentTimeMillis());
-        e.mPackage = TEST_PKG_NAME;
-        e.mShortcutId = TEST_SHORTCUT_ID;
-        List<UsageEvents.Event> events = new ArrayList<>();
-        events.add(e);
-        UsageEvents usageEvents = new UsageEvents(events, new String[]{});
-        when(mUsageStatsManagerInternal.queryEventsForUser(anyInt(), anyLong(), anyLong(),
-                anyBoolean(), anyBoolean())).thenReturn(usageEvents);
-
-        mDataManager.onUserUnlocked(USER_ID_PRIMARY);
-
-        ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
-                buildPerson());
-        mDataManager.onShortcutAddedOrUpdated(shortcut);
-
-        mDataManager.queryUsageStatsService(USER_ID_PRIMARY, 0L, Long.MAX_VALUE);
-
-        List<Range<Long>> activeShortcutInvocationTimeSlots = new ArrayList<>();
-        mDataManager.forAllPackages(packageData ->
-                activeShortcutInvocationTimeSlots.addAll(
-                        packageData.getEventHistory(TEST_SHORTCUT_ID)
-                                .getEventIndex(Event.TYPE_SHORTCUT_INVOCATION)
-                                .getActiveTimeSlots()));
-        assertEquals(1, activeShortcutInvocationTimeSlots.size());
-    }
-
-    @Test
     public void testCallLogContentObserver() {
         mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+        mDataManager.onUserUnlocked(USER_ID_SECONDARY);
 
         ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
                 buildPerson());
@@ -368,6 +338,7 @@
     @Test
     public void testMmsSmsContentObserver() {
         mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+        mDataManager.onUserUnlocked(USER_ID_SECONDARY);
 
         ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
                 buildPerson());
diff --git a/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java b/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java
new file mode 100644
index 0000000..e4248a0
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java
@@ -0,0 +1,295 @@
+/*
+ * 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.people.data;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.when;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.usage.UsageEvents;
+import android.app.usage.UsageStatsManagerInternal;
+import android.content.LocusId;
+
+import com.android.server.LocalServices;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Predicate;
+
+@RunWith(JUnit4.class)
+public final class UsageStatsQueryHelperTest {
+
+    private static final int USER_ID_PRIMARY = 0;
+    private static final String PKG_NAME = "pkg";
+    private static final String ACTIVITY_NAME = "TestActivity";
+    private static final String SHORTCUT_ID = "abc";
+    private static final String NOTIFICATION_CHANNEL_ID = "test : abc";
+    private static final LocusId LOCUS_ID_1 = new LocusId("locus_1");
+    private static final LocusId LOCUS_ID_2 = new LocusId("locus_2");
+
+    @Mock private UsageStatsManagerInternal mUsageStatsManagerInternal;
+
+    private TestPackageData mPackageData;
+    private UsageStatsQueryHelper mHelper;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        addLocalServiceMock(UsageStatsManagerInternal.class, mUsageStatsManagerInternal);
+
+        mPackageData = new TestPackageData(PKG_NAME, USER_ID_PRIMARY, pkg -> false, pkg -> false);
+        mPackageData.mConversationStore.mConversationInfo = new ConversationInfo.Builder()
+                .setShortcutId(SHORTCUT_ID)
+                .setNotificationChannelId(NOTIFICATION_CHANNEL_ID)
+                .setLocusId(LOCUS_ID_1)
+                .build();
+
+        mHelper = new UsageStatsQueryHelper(USER_ID_PRIMARY, pkg -> mPackageData);
+    }
+
+    @After
+    public void tearDown() {
+        LocalServices.removeServiceForTest(UsageStatsManagerInternal.class);
+    }
+
+    @Test
+    public void testQueryNoEvents() {
+        assertFalse(mHelper.querySince(50L));
+    }
+
+    @Test
+    public void testQueryShortcutInvocationEvent() {
+        addUsageEvents(createShortcutInvocationEvent(100L));
+
+        assertTrue(mHelper.querySince(50L));
+        assertEquals(100L, mHelper.getLastEventTimestamp());
+        Event expectedEvent = new Event(100L, Event.TYPE_SHORTCUT_INVOCATION);
+        List<Event> events = mPackageData.mEventStore.mShortcutEventHistory.queryEvents(
+                Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE);
+        assertEquals(1, events.size());
+        assertEquals(expectedEvent, events.get(0));
+    }
+
+    @Test
+    public void testQueryNotificationInterruptionEvent() {
+        addUsageEvents(createNotificationInterruptionEvent(100L));
+
+        assertTrue(mHelper.querySince(50L));
+        assertEquals(100L, mHelper.getLastEventTimestamp());
+        Event expectedEvent = new Event(100L, Event.TYPE_NOTIFICATION_POSTED);
+        List<Event> events = mPackageData.mEventStore.mShortcutEventHistory.queryEvents(
+                Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE);
+        assertEquals(1, events.size());
+        assertEquals(expectedEvent, events.get(0));
+    }
+
+    @Test
+    public void testInAppConversationSwitch() {
+        addUsageEvents(
+                createLocusIdSetEvent(100_000L, LOCUS_ID_1.getId()),
+                createLocusIdSetEvent(110_000L, LOCUS_ID_2.getId()));
+
+        assertTrue(mHelper.querySince(50_000L));
+        assertEquals(110_000L, mHelper.getLastEventTimestamp());
+        List<Event> events = mPackageData.mEventStore.mLocusEventHistory.queryEvents(
+                Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE);
+        assertEquals(1, events.size());
+        assertEquals(createInAppConversationEvent(100_000L, 10), events.get(0));
+    }
+
+    @Test
+    public void testInAppConversationExplicitlyEnd() {
+        addUsageEvents(
+                createLocusIdSetEvent(100_000L, LOCUS_ID_1.getId()),
+                createLocusIdSetEvent(110_000L, null));
+
+        assertTrue(mHelper.querySince(50_000L));
+        assertEquals(110_000L, mHelper.getLastEventTimestamp());
+        List<Event> events = mPackageData.mEventStore.mLocusEventHistory.queryEvents(
+                Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE);
+        assertEquals(1, events.size());
+        assertEquals(createInAppConversationEvent(100_000L, 10), events.get(0));
+    }
+
+    @Test
+    public void testInAppConversationImplicitlyEnd() {
+        addUsageEvents(
+                createLocusIdSetEvent(100_000L, LOCUS_ID_1.getId()),
+                createActivityStoppedEvent(110_000L));
+
+        assertTrue(mHelper.querySince(50_000L));
+        assertEquals(110_000L, mHelper.getLastEventTimestamp());
+        List<Event> events = mPackageData.mEventStore.mLocusEventHistory.queryEvents(
+                Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE);
+        assertEquals(1, events.size());
+        assertEquals(createInAppConversationEvent(100_000L, 10), events.get(0));
+    }
+
+    @Test
+    public void testMultipleInAppConversations() {
+        addUsageEvents(
+                createLocusIdSetEvent(100_000L, LOCUS_ID_1.getId()),
+                createLocusIdSetEvent(110_000L, LOCUS_ID_2.getId()),
+                createLocusIdSetEvent(130_000L, LOCUS_ID_1.getId()),
+                createActivityStoppedEvent(160_000L));
+
+        assertTrue(mHelper.querySince(50_000L));
+        assertEquals(160_000L, mHelper.getLastEventTimestamp());
+        List<Event> events = mPackageData.mEventStore.mLocusEventHistory.queryEvents(
+                Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE);
+        assertEquals(3, events.size());
+        assertEquals(createInAppConversationEvent(100_000L, 10), events.get(0));
+        assertEquals(createInAppConversationEvent(110_000L, 20), events.get(1));
+        assertEquals(createInAppConversationEvent(130_000L, 30), events.get(2));
+    }
+
+    private void addUsageEvents(UsageEvents.Event ... events) {
+        UsageEvents usageEvents = new UsageEvents(Arrays.asList(events), new String[]{});
+        when(mUsageStatsManagerInternal.queryEventsForUser(anyInt(), anyLong(), anyLong(),
+                anyBoolean(), anyBoolean())).thenReturn(usageEvents);
+    }
+
+    private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
+        LocalServices.removeServiceForTest(clazz);
+        LocalServices.addService(clazz, mock);
+    }
+
+    private static UsageEvents.Event createShortcutInvocationEvent(long timestamp) {
+        UsageEvents.Event e = createUsageEvent(UsageEvents.Event.SHORTCUT_INVOCATION, timestamp);
+        e.mShortcutId = SHORTCUT_ID;
+        return e;
+    }
+
+    private static UsageEvents.Event createNotificationInterruptionEvent(long timestamp) {
+        UsageEvents.Event e = createUsageEvent(UsageEvents.Event.NOTIFICATION_INTERRUPTION,
+                timestamp);
+        e.mNotificationChannelId = NOTIFICATION_CHANNEL_ID;
+        return e;
+    }
+
+    private static UsageEvents.Event createLocusIdSetEvent(long timestamp, String locusId) {
+        UsageEvents.Event e = createUsageEvent(UsageEvents.Event.LOCUS_ID_SET, timestamp);
+        e.mClass = ACTIVITY_NAME;
+        e.mLocusId = locusId;
+        return e;
+    }
+
+    private static UsageEvents.Event createActivityStoppedEvent(long timestamp) {
+        UsageEvents.Event e = createUsageEvent(UsageEvents.Event.ACTIVITY_STOPPED, timestamp);
+        e.mClass = ACTIVITY_NAME;
+        return e;
+    }
+
+    private static UsageEvents.Event createUsageEvent(int eventType, long timestamp) {
+        UsageEvents.Event e = new UsageEvents.Event(eventType, timestamp);
+        e.mPackage = PKG_NAME;
+        return e;
+    }
+
+    private static Event createInAppConversationEvent(long timestamp, int durationSeconds) {
+        return new Event.Builder(timestamp, Event.TYPE_IN_APP_CONVERSATION)
+                .setDurationSeconds(durationSeconds)
+                .build();
+    }
+
+    private static class TestConversationStore extends ConversationStore {
+
+        private ConversationInfo mConversationInfo;
+
+        @Override
+        @Nullable
+        ConversationInfo getConversation(@Nullable String shortcutId) {
+            return mConversationInfo;
+        }
+    }
+
+    private static class TestPackageData extends PackageData {
+
+        private final TestConversationStore mConversationStore = new TestConversationStore();
+        private final TestEventStore mEventStore = new TestEventStore();
+
+        TestPackageData(@NonNull String packageName, @UserIdInt int userId,
+                @NonNull Predicate<String> isDefaultDialerPredicate,
+                @NonNull Predicate<String> isDefaultSmsAppPredicate) {
+            super(packageName, userId, isDefaultDialerPredicate, isDefaultSmsAppPredicate);
+        }
+
+        @Override
+        @NonNull
+        ConversationStore getConversationStore() {
+            return mConversationStore;
+        }
+
+        @Override
+        @NonNull
+        EventStore getEventStore() {
+            return mEventStore;
+        }
+    }
+
+    private static class TestEventStore extends EventStore {
+
+        private final EventHistoryImpl mShortcutEventHistory = new TestEventHistoryImpl();
+        private final EventHistoryImpl mLocusEventHistory = new TestEventHistoryImpl();
+
+        @Override
+        @NonNull
+        EventHistoryImpl getOrCreateShortcutEventHistory(String shortcutId) {
+            return mShortcutEventHistory;
+        }
+
+        @Override
+        @NonNull
+        EventHistoryImpl getOrCreateLocusEventHistory(LocusId locusId) {
+            return mLocusEventHistory;
+        }
+    }
+
+    private static class TestEventHistoryImpl extends EventHistoryImpl {
+
+        private final List<Event> mEvents = new ArrayList<>();
+
+        @Override
+        @NonNull
+        public List<Event> queryEvents(Set<Integer> eventTypes, long startTime, long endTime) {
+            return mEvents;
+        }
+
+        @Override
+        void addEvent(Event event) {
+            mEvents.add(event);
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index 41416f1..3d190be 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -52,6 +52,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.IntentSender;
+import android.content.LocusId;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ILauncherApps;
@@ -1600,6 +1601,22 @@
     }
 
     /**
+     * Make a shortcut with an ID and a locus ID.
+     */
+    protected ShortcutInfo makeShortcutWithLocusId(String id, LocusId locusId) {
+        final ShortcutInfo.Builder  b = new ShortcutInfo.Builder(mClientContext, id)
+                .setActivity(new ComponentName(mClientContext.getPackageName(), "main"))
+                .setShortLabel("title-" + id)
+                .setIntent(makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class))
+                .setLocusId(locusId);
+        final ShortcutInfo s = b.build();
+
+        s.setTimestamp(mInjectedCurrentTimeMillis); // HACK
+
+        return s;
+    }
+
+    /**
      * Make an intent.
      */
     protected Intent makeIntent(String action, Class<?> clazz, Object... bundleKeysAndValues) {
@@ -1618,6 +1635,13 @@
     }
 
     /**
+     * Make a LocusId.
+     */
+    protected LocusId makeLocusId(String id) {
+        return new LocusId(id);
+    }
+
+    /**
      * Make an component name, with the client context.
      */
     @NonNull
@@ -1955,16 +1979,17 @@
     protected static ShortcutQuery buildQuery(long changedSince,
             String packageName, ComponentName componentName,
             /* @ShortcutQuery.QueryFlags */ int flags) {
-        return buildQuery(changedSince, packageName, null, componentName, flags);
+        return buildQuery(changedSince, packageName, null, null, componentName, flags);
     }
 
     protected static ShortcutQuery buildQuery(long changedSince,
-            String packageName, List<String> shortcutIds, ComponentName componentName,
-            /* @ShortcutQuery.QueryFlags */ int flags) {
+            String packageName, List<String> shortcutIds, List<LocusId> locusIds,
+            ComponentName componentName, /* @ShortcutQuery.QueryFlags */ int flags) {
         final ShortcutQuery q = new ShortcutQuery();
         q.setChangedSince(changedSince);
         q.setPackage(packageName);
         q.setShortcutIds(shortcutIds);
+        q.setLocusIds(locusIds);
         q.setActivity(componentName);
         q.setQueryFlags(flags);
         return q;
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
index bfe0c15..d4edab4 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
@@ -24,6 +24,7 @@
 import static org.xmlpull.v1.XmlPullParser.START_TAG;
 
 import android.content.pm.PackageInstaller;
+import android.platform.test.annotations.Presubmit;
 import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.Xml;
@@ -57,6 +58,7 @@
 import java.util.List;
 
 @RunWith(AndroidJUnit4.class)
+@Presubmit
 public class PackageInstallerSessionTest {
     @Rule
     public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index 798420e..63da5fb 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -1372,7 +1372,7 @@
 
         setCaller(CALLING_PACKAGE_1);
         final ShortcutInfo s1_1 = makeShortcut("s1");
-        final ShortcutInfo s1_2 = makeShortcut("s2");
+        final ShortcutInfo s1_2 = makeShortcutWithLocusId("s2", makeLocusId("l1"));
 
         assertTrue(mManager.setDynamicShortcuts(list(s1_1, s1_2)));
 
@@ -1394,7 +1394,7 @@
         getCallerShortcut("s4").setTimestamp(500);
 
         setCaller(CALLING_PACKAGE_3);
-        final ShortcutInfo s3_2 = makeShortcut("s3");
+        final ShortcutInfo s3_2 = makeShortcutWithLocusId("s3", makeLocusId("l2"));
         assertTrue(mManager.setDynamicShortcuts(list(s3_2)));
 
         getCallerShortcut("s3").setTimestamp(START_TIME + 5000);
@@ -1446,7 +1446,7 @@
         // With ID.
         assertAllDynamic(assertAllNotHaveTitle(assertAllNotHaveIntents(assertShortcutIds(
                 assertAllKeyFieldsOnly(mLauncherApps.getShortcuts(buildQuery(
-                        /* time =*/ 1000, CALLING_PACKAGE_2, list("s3"),
+                        /* time =*/ 1000, CALLING_PACKAGE_2, list("s3"), /* locusIds =*/ null,
                         /* activity =*/ null,
                         ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY),
                         getCallingUser())),
@@ -1454,20 +1454,51 @@
         assertAllDynamic(assertAllNotHaveTitle(assertAllNotHaveIntents(assertShortcutIds(
                 assertAllKeyFieldsOnly(mLauncherApps.getShortcuts(buildQuery(
                         /* time =*/ 1000, CALLING_PACKAGE_2, list("s3", "s2", "ss"),
-                        /* activity =*/ null,
+                        /* locusIds =*/ null, /* activity =*/ null,
                         ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY),
                         getCallingUser())),
                 "s2", "s3"))));
         assertAllDynamic(assertAllNotHaveTitle(assertAllNotHaveIntents(assertShortcutIds(
                 assertAllKeyFieldsOnly(mLauncherApps.getShortcuts(buildQuery(
                         /* time =*/ 1000, CALLING_PACKAGE_2, list("s3x", "s2x"),
-                        /* activity =*/ null,
+                        /* locusIds =*/ null, /* activity =*/ null,
                         ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY),
                         getCallingUser()))
                 /* empty */))));
         assertAllDynamic(assertAllNotHaveTitle(assertAllNotHaveIntents(assertShortcutIds(
                 assertAllKeyFieldsOnly(mLauncherApps.getShortcuts(buildQuery(
-                        /* time =*/ 1000, CALLING_PACKAGE_2, list(),
+                        /* time =*/ 1000, CALLING_PACKAGE_2, list(), /* locusIds =*/ null,
+                        /* activity =*/ null,
+                        ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY),
+                        getCallingUser()))
+                /* empty */))));
+
+        // With locus ID.
+        assertAllDynamic(assertAllNotHaveTitle(assertAllNotHaveIntents(assertShortcutIds(
+                assertAllKeyFieldsOnly(mLauncherApps.getShortcuts(buildQuery(
+                        /* time =*/ 1000, CALLING_PACKAGE_3, /* shortcutIds =*/ null,
+                        list(makeLocusId("l2")), /* activity =*/ null,
+                        ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY),
+                        getCallingUser())),
+                "s3"))));
+        assertAllDynamic(assertAllNotHaveTitle(assertAllNotHaveIntents(assertShortcutIds(
+                assertAllKeyFieldsOnly(mLauncherApps.getShortcuts(buildQuery(
+                        /* time =*/ 1000, CALLING_PACKAGE_1, /* shortcutIds =*/ null,
+                        list(makeLocusId("l1"), makeLocusId("l2"), makeLocusId("l3")),
+                        /* activity =*/ null,
+                        ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY),
+                        getCallingUser())),
+                "s2"))));
+        assertAllDynamic(assertAllNotHaveTitle(assertAllNotHaveIntents(assertShortcutIds(
+                assertAllKeyFieldsOnly(mLauncherApps.getShortcuts(buildQuery(
+                        /* time =*/ 1000, CALLING_PACKAGE_1, /* shortcutIds =*/ null,
+                        list(makeLocusId("lx1"), makeLocusId("lx2")), /* activity =*/ null,
+                        ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY),
+                        getCallingUser()))
+                /* empty */))));
+        assertAllDynamic(assertAllNotHaveTitle(assertAllNotHaveIntents(assertShortcutIds(
+                assertAllKeyFieldsOnly(mLauncherApps.getShortcuts(buildQuery(
+                        /* time =*/ 1000, CALLING_PACKAGE_3, /* shortcutIds =*/ null, list(),
                         /* activity =*/ null,
                         ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY),
                         getCallingUser()))
@@ -1498,7 +1529,7 @@
         assertExpectException(
                 IllegalArgumentException.class, "package name must also be set", () -> {
                     mLauncherApps.getShortcuts(buildQuery(
-                    /* time =*/ 0, /* package= */ null, list("id"),
+                    /* time =*/ 0, /* package= */ null, list("id"), /* locusIds =*/ null,
                     /* activity =*/ null, /* flags */ 0), getCallingUser());
                 });
 
@@ -1537,7 +1568,7 @@
         assertExpectException(
                 IllegalArgumentException.class, "package name must also be set", () -> {
                     mLauncherApps.getShortcuts(buildQuery(
-                            /* time =*/ 0, /* package= */ null, list("id"),
+                            /* time =*/ 0, /* package= */ null, list("id"), /* locusIds= */ null,
                             /* activity =*/ null, /* flags */ 0), getCallingUser());
                 });
 
diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java b/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java
index d0d2edc..64d05f0 100644
--- a/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java
@@ -119,7 +119,7 @@
 
     @Test
     public void createNonStaged() {
-        Rollback rollback = mRollbackStore.createNonStagedRollback(ID, USER, INSTALLER);
+        Rollback rollback = mRollbackStore.createNonStagedRollback(ID, USER, INSTALLER, null);
 
         assertThat(rollback.getBackupDir().getAbsolutePath())
                 .isEqualTo(mFolder.getRoot().getAbsolutePath() + "/" + ID);
@@ -132,7 +132,7 @@
 
     @Test
     public void createStaged() {
-        Rollback rollback = mRollbackStore.createStagedRollback(ID, 897, USER, INSTALLER);
+        Rollback rollback = mRollbackStore.createStagedRollback(ID, 897, USER, INSTALLER, null);
 
         assertThat(rollback.getBackupDir().getAbsolutePath())
                 .isEqualTo(mFolder.getRoot().getAbsolutePath() + "/" + ID);
@@ -147,7 +147,7 @@
 
     @Test
     public void saveAndLoadRollback() {
-        Rollback origRb = mRollbackStore.createNonStagedRollback(ID, USER, INSTALLER);
+        Rollback origRb = mRollbackStore.createNonStagedRollback(ID, USER, INSTALLER, null);
 
         origRb.setRestoreUserDataInProgress(true);
         origRb.info.getCausePackages().add(new VersionedPackage("com.made.up", 2));
@@ -197,7 +197,7 @@
 
     @Test
     public void loadFromJson() throws Exception {
-        Rollback expectedRb = mRollbackStore.createNonStagedRollback(ID, USER, INSTALLER);
+        Rollback expectedRb = mRollbackStore.createNonStagedRollback(ID, USER, INSTALLER, null);
 
         expectedRb.setTimestamp(Instant.parse("2019-10-01T12:29:08.855Z"));
         expectedRb.setRestoreUserDataInProgress(true);
@@ -246,7 +246,7 @@
 
     @Test
     public void saveAndDelete() {
-        Rollback rollback = mRollbackStore.createNonStagedRollback(ID, USER, INSTALLER);
+        Rollback rollback = mRollbackStore.createNonStagedRollback(ID, USER, INSTALLER, null);
 
         RollbackStore.saveRollback(rollback);
 
diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java
index 9719509..804c1b9 100644
--- a/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java
+++ b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java
@@ -302,6 +302,22 @@
         assertThat(rollback.notifySessionWithSuccess()).isTrue();
     }
 
+    @Test
+    public void allPackagesEnabled() {
+        int[] sessionIds = new int[]{ 7777, 8888 };
+        Rollback rollback = new Rollback(123, new File("/test/testing"), -1, USER, INSTALLER,
+                sessionIds);
+        // #allPackagesEnabled returns false when 1 out of 2 packages is enabled.
+        rollback.info.getPackages().add(newPkgInfoFor(PKG_1, 12, 10, false));
+        assertThat(rollback.allPackagesEnabled()).isFalse();
+        // #allPackagesEnabled returns false for ApkInApex doesn't count.
+        rollback.info.getPackages().add(newPkgInfoForApkInApex(PKG_3, 157, 156));
+        assertThat(rollback.allPackagesEnabled()).isFalse();
+        // #allPackagesEnabled returns true when 2 out of 2 packages are enabled.
+        rollback.info.getPackages().add(newPkgInfoFor(PKG_2, 18, 12, true));
+        assertThat(rollback.allPackagesEnabled()).isTrue();
+    }
+
     private static PackageRollbackInfo newPkgInfoFor(
             String packageName, long fromVersion, long toVersion, boolean isApex) {
         return new PackageRollbackInfo(new VersionedPackage(packageName, fromVersion),
@@ -310,6 +326,20 @@
                 new SparseLongArray());
     }
 
+    /**
+     * TODO: merge newPkgInfoFor and newPkgInfoForApkInApex by using enums to specify
+     * 1. IS_APK
+     * 2. IS_APEX
+     * 3. IS_APK_IN_APEX
+     */
+    private static PackageRollbackInfo newPkgInfoForApkInApex(
+            String packageName, long fromVersion, long toVersion) {
+        return new PackageRollbackInfo(new VersionedPackage(packageName, fromVersion),
+                new VersionedPackage(packageName, toVersion),
+                new IntArray(), new ArrayList<>(), false, true, new IntArray(),
+                new SparseLongArray());
+    }
+
     private static class PackageRollbackInfoForPackage implements
             ArgumentMatcher<PackageRollbackInfo> {
         private final String mPkg;
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
index ae53692..2eeeb3e 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
@@ -30,17 +30,16 @@
 
 import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.NetworkTimeSuggestion;
-import android.app.timedetector.PhoneTimeSuggestion;
+import android.app.timedetector.TelephonyTimeSuggestion;
 import android.content.Context;
 import android.content.pm.PackageManager;
-import android.os.Handler;
 import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Message;
 import android.os.TimestampedValue;
 
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.timezonedetector.TestHandler;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -81,35 +80,35 @@
     }
 
     @Test(expected = SecurityException.class)
-    public void testSuggestPhoneTime_withoutPermission() {
+    public void testSuggestTelephonyTime_withoutPermission() {
         doThrow(new SecurityException("Mock"))
                 .when(mMockContext).enforceCallingPermission(anyString(), any());
-        PhoneTimeSuggestion phoneTimeSuggestion = createPhoneTimeSuggestion();
+        TelephonyTimeSuggestion timeSuggestion = createTelephonyTimeSuggestion();
 
         try {
-            mTimeDetectorService.suggestPhoneTime(phoneTimeSuggestion);
+            mTimeDetectorService.suggestTelephonyTime(timeSuggestion);
             fail();
         } finally {
             verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.SUGGEST_PHONE_TIME_AND_ZONE),
+                    eq(android.Manifest.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE),
                     anyString());
         }
     }
 
     @Test
-    public void testSuggestPhoneTime() throws Exception {
+    public void testSuggestTelephonyTime() throws Exception {
         doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
 
-        PhoneTimeSuggestion phoneTimeSuggestion = createPhoneTimeSuggestion();
-        mTimeDetectorService.suggestPhoneTime(phoneTimeSuggestion);
+        TelephonyTimeSuggestion timeSuggestion = createTelephonyTimeSuggestion();
+        mTimeDetectorService.suggestTelephonyTime(timeSuggestion);
         mTestHandler.assertTotalMessagesEnqueued(1);
 
         verify(mMockContext).enforceCallingPermission(
-                eq(android.Manifest.permission.SUGGEST_PHONE_TIME_AND_ZONE),
+                eq(android.Manifest.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE),
                 anyString());
 
-        mTestHandler.waitForEmptyQueue();
-        mStubbedTimeDetectorStrategy.verifySuggestPhoneTimeCalled(phoneTimeSuggestion);
+        mTestHandler.waitForMessagesToBeProcessed();
+        mStubbedTimeDetectorStrategy.verifySuggestTelephonyTimeCalled(timeSuggestion);
     }
 
     @Test(expected = SecurityException.class)
@@ -140,7 +139,7 @@
                 eq(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE),
                 anyString());
 
-        mTestHandler.waitForEmptyQueue();
+        mTestHandler.waitForMessagesToBeProcessed();
         mStubbedTimeDetectorStrategy.verifySuggestManualTimeCalled(manualTimeSuggestion);
     }
 
@@ -170,7 +169,7 @@
         verify(mMockContext).enforceCallingOrSelfPermission(
                 eq(android.Manifest.permission.SET_TIME), anyString());
 
-        mTestHandler.waitForEmptyQueue();
+        mTestHandler.waitForMessagesToBeProcessed();
         mStubbedTimeDetectorStrategy.verifySuggestNetworkTimeCalled(NetworkTimeSuggestion);
     }
 
@@ -187,21 +186,23 @@
 
     @Test
     public void testAutoTimeDetectionToggle() throws Exception {
-        mTimeDetectorService.handleAutoTimeDetectionToggle();
+        mTimeDetectorService.handleAutoTimeDetectionChanged();
         mTestHandler.assertTotalMessagesEnqueued(1);
-        mTestHandler.waitForEmptyQueue();
-        mStubbedTimeDetectorStrategy.verifyHandleAutoTimeDetectionToggleCalled();
+        mTestHandler.waitForMessagesToBeProcessed();
+        mStubbedTimeDetectorStrategy.verifyHandleAutoTimeDetectionChangedCalled();
 
-        mTimeDetectorService.handleAutoTimeDetectionToggle();
+        mStubbedTimeDetectorStrategy.resetCallTracking();
+
+        mTimeDetectorService.handleAutoTimeDetectionChanged();
         mTestHandler.assertTotalMessagesEnqueued(2);
-        mTestHandler.waitForEmptyQueue();
-        mStubbedTimeDetectorStrategy.verifyHandleAutoTimeDetectionToggleCalled();
+        mTestHandler.waitForMessagesToBeProcessed();
+        mStubbedTimeDetectorStrategy.verifyHandleAutoTimeDetectionChangedCalled();
     }
 
-    private static PhoneTimeSuggestion createPhoneTimeSuggestion() {
-        int phoneId = 1234;
+    private static TelephonyTimeSuggestion createTelephonyTimeSuggestion() {
+        int slotIndex = 1234;
         TimestampedValue<Long> timeValue = new TimestampedValue<>(100L, 1_000_000L);
-        return new PhoneTimeSuggestion.Builder(phoneId)
+        return new TelephonyTimeSuggestion.Builder(slotIndex)
                 .setUtcTime(timeValue)
                 .build();
     }
@@ -219,10 +220,10 @@
     private static class StubbedTimeDetectorStrategy implements TimeDetectorStrategy {
 
         // Call tracking.
-        private PhoneTimeSuggestion mLastPhoneSuggestion;
+        private TelephonyTimeSuggestion mLastTelephonySuggestion;
         private ManualTimeSuggestion mLastManualSuggestion;
         private NetworkTimeSuggestion mLastNetworkSuggestion;
-        private boolean mLastAutoTimeDetectionToggleCalled;
+        private boolean mHandleAutoTimeDetectionChangedCalled;
         private boolean mDumpCalled;
 
         @Override
@@ -230,45 +231,40 @@
         }
 
         @Override
-        public void suggestPhoneTime(PhoneTimeSuggestion timeSuggestion) {
-            resetCallTracking();
-            mLastPhoneSuggestion = timeSuggestion;
+        public void suggestTelephonyTime(TelephonyTimeSuggestion timeSuggestion) {
+            mLastTelephonySuggestion = timeSuggestion;
         }
 
         @Override
         public void suggestManualTime(ManualTimeSuggestion timeSuggestion) {
-            resetCallTracking();
             mLastManualSuggestion = timeSuggestion;
         }
 
         @Override
         public void suggestNetworkTime(NetworkTimeSuggestion timeSuggestion) {
-            resetCallTracking();
             mLastNetworkSuggestion = timeSuggestion;
         }
 
         @Override
         public void handleAutoTimeDetectionChanged() {
-            resetCallTracking();
-            mLastAutoTimeDetectionToggleCalled = true;
+            mHandleAutoTimeDetectionChangedCalled = true;
         }
 
         @Override
         public void dump(PrintWriter pw, String[] args) {
-            resetCallTracking();
             mDumpCalled = true;
         }
 
         void resetCallTracking() {
-            mLastPhoneSuggestion = null;
+            mLastTelephonySuggestion = null;
             mLastManualSuggestion = null;
             mLastNetworkSuggestion = null;
-            mLastAutoTimeDetectionToggleCalled = false;
+            mHandleAutoTimeDetectionChangedCalled = false;
             mDumpCalled = false;
         }
 
-        void verifySuggestPhoneTimeCalled(PhoneTimeSuggestion expectedSuggestion) {
-            assertEquals(expectedSuggestion, mLastPhoneSuggestion);
+        void verifySuggestTelephonyTimeCalled(TelephonyTimeSuggestion expectedSuggestion) {
+            assertEquals(expectedSuggestion, mLastTelephonySuggestion);
         }
 
         public void verifySuggestManualTimeCalled(ManualTimeSuggestion expectedSuggestion) {
@@ -279,45 +275,12 @@
             assertEquals(expectedSuggestion, mLastNetworkSuggestion);
         }
 
-        void verifyHandleAutoTimeDetectionToggleCalled() {
-            assertTrue(mLastAutoTimeDetectionToggleCalled);
+        void verifyHandleAutoTimeDetectionChangedCalled() {
+            assertTrue(mHandleAutoTimeDetectionChangedCalled);
         }
 
         void verifyDumpCalled() {
             assertTrue(mDumpCalled);
         }
     }
-
-    /**
-     * A Handler that can track posts/sends and wait for work to be completed.
-     */
-    private static class TestHandler extends Handler {
-
-        private int mMessagesSent;
-
-        TestHandler(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
-            mMessagesSent++;
-            return super.sendMessageAtTime(msg, uptimeMillis);
-        }
-
-        /** Asserts the number of messages posted or sent is as expected. */
-        void assertTotalMessagesEnqueued(int expected) {
-            assertEquals(expected, mMessagesSent);
-        }
-
-        /**
-         * Waits for all currently enqueued work due to be processed to be completed before
-         * returning.
-         */
-        void waitForEmptyQueue() throws InterruptedException {
-            while (!getLooper().getQueue().isIdle()) {
-                Thread.sleep(100);
-            }
-        }
-    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
index d940a6a..803b245 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
@@ -24,7 +24,7 @@
 
 import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.NetworkTimeSuggestion;
-import android.app.timedetector.PhoneTimeSuggestion;
+import android.app.timedetector.TelephonyTimeSuggestion;
 import android.icu.util.Calendar;
 import android.icu.util.GregorianCalendar;
 import android.icu.util.TimeZone;
@@ -52,7 +52,7 @@
      */
     private static final long ARBITRARY_TEST_TIME_MILLIS = createUtcTime(2018, 1, 1, 12, 0, 0);
 
-    private static final int ARBITRARY_PHONE_ID = 123456;
+    private static final int ARBITRARY_SLOT_INDEX = 123456;
 
     private Script mScript;
 
@@ -62,51 +62,51 @@
     }
 
     @Test
-    public void testSuggestPhoneTime_autoTimeEnabled() {
+    public void testSuggestTelephonyTime_autoTimeEnabled() {
         mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
                 .pokeAutoTimeDetectionEnabled(true);
 
-        int phoneId = ARBITRARY_PHONE_ID;
+        int slotIndex = ARBITRARY_SLOT_INDEX;
         long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS;
 
-        PhoneTimeSuggestion timeSuggestion =
-                mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis);
+        TelephonyTimeSuggestion timeSuggestion =
+                mScript.generateTelephonyTimeSuggestion(slotIndex, testTimeMillis);
         mScript.simulateTimePassing()
-                .simulatePhoneTimeSuggestion(timeSuggestion);
+                .simulateTelephonyTimeSuggestion(timeSuggestion);
 
         long expectedSystemClockMillis =
                 mScript.calculateTimeInMillisForNow(timeSuggestion.getUtcTime());
         mScript.verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis)
-                .assertLatestPhoneSuggestion(phoneId, timeSuggestion);
+                .assertLatestTelephonySuggestion(slotIndex, timeSuggestion);
     }
 
     @Test
-    public void testSuggestPhoneTime_emptySuggestionIgnored() {
+    public void testSuggestTelephonyTime_emptySuggestionIgnored() {
         mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
                 .pokeAutoTimeDetectionEnabled(true);
 
-        int phoneId = ARBITRARY_PHONE_ID;
-        PhoneTimeSuggestion timeSuggestion =
-                mScript.generatePhoneTimeSuggestion(phoneId, null);
-        mScript.simulatePhoneTimeSuggestion(timeSuggestion)
+        int slotIndex = ARBITRARY_SLOT_INDEX;
+        TelephonyTimeSuggestion timeSuggestion =
+                mScript.generateTelephonyTimeSuggestion(slotIndex, null);
+        mScript.simulateTelephonyTimeSuggestion(timeSuggestion)
                 .verifySystemClockWasNotSetAndResetCallTracking()
-                .assertLatestPhoneSuggestion(phoneId, null);
+                .assertLatestTelephonySuggestion(slotIndex, null);
     }
 
     @Test
-    public void testSuggestPhoneTime_systemClockThreshold() {
+    public void testSuggestTelephonyTime_systemClockThreshold() {
         final int systemClockUpdateThresholdMillis = 1000;
         final int clockIncrementMillis = 100;
         mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
                 .pokeThresholds(systemClockUpdateThresholdMillis)
                 .pokeAutoTimeDetectionEnabled(true);
 
-        int phoneId = ARBITRARY_PHONE_ID;
+        int slotIndex = ARBITRARY_SLOT_INDEX;
 
         // Send the first time signal. It should be used.
         {
-            PhoneTimeSuggestion timeSuggestion1 =
-                    mScript.generatePhoneTimeSuggestion(phoneId, ARBITRARY_TEST_TIME_MILLIS);
+            TelephonyTimeSuggestion timeSuggestion1 =
+                    mScript.generateTelephonyTimeSuggestion(slotIndex, ARBITRARY_TEST_TIME_MILLIS);
 
             // Increment the the device clocks to simulate the passage of time.
             mScript.simulateTimePassing(clockIncrementMillis);
@@ -114,151 +114,151 @@
             long expectedSystemClockMillis1 =
                     mScript.calculateTimeInMillisForNow(timeSuggestion1.getUtcTime());
 
-            mScript.simulatePhoneTimeSuggestion(timeSuggestion1)
+            mScript.simulateTelephonyTimeSuggestion(timeSuggestion1)
                     .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1)
-                    .assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
+                    .assertLatestTelephonySuggestion(slotIndex, timeSuggestion1);
         }
 
         // Now send another time signal, but one that is too similar to the last one and should be
         // stored, but not used to set the system clock.
         {
             int underThresholdMillis = systemClockUpdateThresholdMillis - 1;
-            PhoneTimeSuggestion timeSuggestion2 = mScript.generatePhoneTimeSuggestion(
-                    phoneId, mScript.peekSystemClockMillis() + underThresholdMillis);
+            TelephonyTimeSuggestion timeSuggestion2 = mScript.generateTelephonyTimeSuggestion(
+                    slotIndex, mScript.peekSystemClockMillis() + underThresholdMillis);
             mScript.simulateTimePassing(clockIncrementMillis)
-                    .simulatePhoneTimeSuggestion(timeSuggestion2)
+                    .simulateTelephonyTimeSuggestion(timeSuggestion2)
                     .verifySystemClockWasNotSetAndResetCallTracking()
-                    .assertLatestPhoneSuggestion(phoneId, timeSuggestion2);
+                    .assertLatestTelephonySuggestion(slotIndex, timeSuggestion2);
         }
 
         // Now send another time signal, but one that is on the threshold and so should be used.
         {
-            PhoneTimeSuggestion timeSuggestion3 = mScript.generatePhoneTimeSuggestion(
-                    phoneId,
+            TelephonyTimeSuggestion timeSuggestion3 = mScript.generateTelephonyTimeSuggestion(
+                    slotIndex,
                     mScript.peekSystemClockMillis() + systemClockUpdateThresholdMillis);
             mScript.simulateTimePassing(clockIncrementMillis);
 
             long expectedSystemClockMillis3 =
                     mScript.calculateTimeInMillisForNow(timeSuggestion3.getUtcTime());
 
-            mScript.simulatePhoneTimeSuggestion(timeSuggestion3)
+            mScript.simulateTelephonyTimeSuggestion(timeSuggestion3)
                     .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis3)
-                    .assertLatestPhoneSuggestion(phoneId, timeSuggestion3);
+                    .assertLatestTelephonySuggestion(slotIndex, timeSuggestion3);
         }
     }
 
     @Test
-    public void testSuggestPhoneTime_multiplePhoneIdsAndBucketing() {
+    public void testSuggestTelephonyTime_multipleSlotIndexsAndBucketing() {
         mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
                 .pokeAutoTimeDetectionEnabled(true);
 
-        // There are 2 phones in this test. Phone 2 has a different idea of the current time.
-        // phone1Id < phone2Id (which is important because the strategy uses the lowest ID when
-        // multiple phone suggestions are available.
-        int phone1Id = ARBITRARY_PHONE_ID;
-        int phone2Id = ARBITRARY_PHONE_ID + 1;
-        long phone1TimeMillis = ARBITRARY_TEST_TIME_MILLIS;
-        long phone2TimeMillis = ARBITRARY_TEST_TIME_MILLIS + Duration.ofDays(1).toMillis();
+        // There are 2 slotIndexes in this test. slotIndex1 and slotIndex2 have different opinions
+        // about the current time. slotIndex1 < slotIndex2 (which is important because the strategy
+        // uses the lowest slotIndex when multiple telephony suggestions are available.
+        int slotIndex1 = ARBITRARY_SLOT_INDEX;
+        int slotIndex2 = ARBITRARY_SLOT_INDEX + 1;
+        long slotIndex1TimeMillis = ARBITRARY_TEST_TIME_MILLIS;
+        long slotIndex2TimeMillis = ARBITRARY_TEST_TIME_MILLIS + Duration.ofDays(1).toMillis();
 
-        // Make a suggestion with phone2Id.
+        // Make a suggestion with slotIndex2.
         {
-            PhoneTimeSuggestion phone2TimeSuggestion =
-                    mScript.generatePhoneTimeSuggestion(phone2Id, phone2TimeMillis);
+            TelephonyTimeSuggestion slotIndex2TimeSuggestion =
+                    mScript.generateTelephonyTimeSuggestion(slotIndex2, slotIndex2TimeMillis);
             mScript.simulateTimePassing();
 
             long expectedSystemClockMillis =
-                    mScript.calculateTimeInMillisForNow(phone2TimeSuggestion.getUtcTime());
+                    mScript.calculateTimeInMillisForNow(slotIndex2TimeSuggestion.getUtcTime());
 
-            mScript.simulatePhoneTimeSuggestion(phone2TimeSuggestion)
+            mScript.simulateTelephonyTimeSuggestion(slotIndex2TimeSuggestion)
                     .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis)
-                    .assertLatestPhoneSuggestion(phone1Id, null)
-                    .assertLatestPhoneSuggestion(phone2Id, phone2TimeSuggestion);
+                    .assertLatestTelephonySuggestion(slotIndex1, null)
+                    .assertLatestTelephonySuggestion(slotIndex2, slotIndex2TimeSuggestion);
         }
 
         mScript.simulateTimePassing();
 
-        // Now make a different suggestion with phone1Id.
+        // Now make a different suggestion with slotIndex1.
         {
-            PhoneTimeSuggestion phone1TimeSuggestion =
-                    mScript.generatePhoneTimeSuggestion(phone1Id, phone1TimeMillis);
+            TelephonyTimeSuggestion slotIndex1TimeSuggestion =
+                    mScript.generateTelephonyTimeSuggestion(slotIndex1, slotIndex1TimeMillis);
             mScript.simulateTimePassing();
 
             long expectedSystemClockMillis =
-                    mScript.calculateTimeInMillisForNow(phone1TimeSuggestion.getUtcTime());
+                    mScript.calculateTimeInMillisForNow(slotIndex1TimeSuggestion.getUtcTime());
 
-            mScript.simulatePhoneTimeSuggestion(phone1TimeSuggestion)
+            mScript.simulateTelephonyTimeSuggestion(slotIndex1TimeSuggestion)
                     .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis)
-                    .assertLatestPhoneSuggestion(phone1Id, phone1TimeSuggestion);
+                    .assertLatestTelephonySuggestion(slotIndex1, slotIndex1TimeSuggestion);
 
         }
 
         mScript.simulateTimePassing();
 
-        // Make another suggestion with phone2Id. It should be stored but not used because the
-        // phone1Id suggestion will still "win".
+        // Make another suggestion with slotIndex2. It should be stored but not used because the
+        // slotIndex1 suggestion will still "win".
         {
-            PhoneTimeSuggestion phone2TimeSuggestion =
-                    mScript.generatePhoneTimeSuggestion(phone2Id, phone2TimeMillis);
+            TelephonyTimeSuggestion slotIndex2TimeSuggestion =
+                    mScript.generateTelephonyTimeSuggestion(slotIndex2, slotIndex2TimeMillis);
             mScript.simulateTimePassing();
 
-            mScript.simulatePhoneTimeSuggestion(phone2TimeSuggestion)
+            mScript.simulateTelephonyTimeSuggestion(slotIndex2TimeSuggestion)
                     .verifySystemClockWasNotSetAndResetCallTracking()
-                    .assertLatestPhoneSuggestion(phone2Id, phone2TimeSuggestion);
+                    .assertLatestTelephonySuggestion(slotIndex2, slotIndex2TimeSuggestion);
         }
 
-        // Let enough time pass that phone1Id's suggestion should now be too old.
-        mScript.simulateTimePassing(TimeDetectorStrategyImpl.PHONE_BUCKET_SIZE_MILLIS);
+        // Let enough time pass that slotIndex1's suggestion should now be too old.
+        mScript.simulateTimePassing(TimeDetectorStrategyImpl.TELEPHONY_BUCKET_SIZE_MILLIS);
 
-        // Make another suggestion with phone2Id. It should be used because the phoneId1
+        // Make another suggestion with slotIndex2. It should be used because the slotIndex1
         // is in an older "bucket".
         {
-            PhoneTimeSuggestion phone2TimeSuggestion =
-                    mScript.generatePhoneTimeSuggestion(phone2Id, phone2TimeMillis);
+            TelephonyTimeSuggestion slotIndex2TimeSuggestion =
+                    mScript.generateTelephonyTimeSuggestion(slotIndex2, slotIndex2TimeMillis);
             mScript.simulateTimePassing();
 
             long expectedSystemClockMillis =
-                    mScript.calculateTimeInMillisForNow(phone2TimeSuggestion.getUtcTime());
+                    mScript.calculateTimeInMillisForNow(slotIndex2TimeSuggestion.getUtcTime());
 
-            mScript.simulatePhoneTimeSuggestion(phone2TimeSuggestion)
+            mScript.simulateTelephonyTimeSuggestion(slotIndex2TimeSuggestion)
                     .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis)
-                    .assertLatestPhoneSuggestion(phone2Id, phone2TimeSuggestion);
+                    .assertLatestTelephonySuggestion(slotIndex2, slotIndex2TimeSuggestion);
         }
     }
 
     @Test
-    public void testSuggestPhoneTime_autoTimeDisabled() {
+    public void testSuggestTelephonyTime_autoTimeDisabled() {
         mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
                 .pokeAutoTimeDetectionEnabled(false);
 
-        int phoneId = ARBITRARY_PHONE_ID;
-        PhoneTimeSuggestion timeSuggestion =
-                mScript.generatePhoneTimeSuggestion(phoneId, ARBITRARY_TEST_TIME_MILLIS);
+        int slotIndex = ARBITRARY_SLOT_INDEX;
+        TelephonyTimeSuggestion timeSuggestion =
+                mScript.generateTelephonyTimeSuggestion(slotIndex, ARBITRARY_TEST_TIME_MILLIS);
         mScript.simulateTimePassing()
-                .simulatePhoneTimeSuggestion(timeSuggestion)
+                .simulateTelephonyTimeSuggestion(timeSuggestion)
                 .verifySystemClockWasNotSetAndResetCallTracking()
-                .assertLatestPhoneSuggestion(phoneId, timeSuggestion);
+                .assertLatestTelephonySuggestion(slotIndex, timeSuggestion);
     }
 
     @Test
-    public void testSuggestPhoneTime_invalidNitzReferenceTimesIgnored() {
+    public void testSuggestTelephonyTime_invalidNitzReferenceTimesIgnored() {
         final int systemClockUpdateThreshold = 2000;
         mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
                 .pokeThresholds(systemClockUpdateThreshold)
                 .pokeAutoTimeDetectionEnabled(true);
 
         long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS;
-        int phoneId = ARBITRARY_PHONE_ID;
+        int slotIndex = ARBITRARY_SLOT_INDEX;
 
-        PhoneTimeSuggestion timeSuggestion1 =
-                mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis);
+        TelephonyTimeSuggestion timeSuggestion1 =
+                mScript.generateTelephonyTimeSuggestion(slotIndex, testTimeMillis);
         TimestampedValue<Long> utcTime1 = timeSuggestion1.getUtcTime();
 
-        // Initialize the strategy / device with a time set from a phone suggestion.
+        // Initialize the strategy / device with a time set from a telephony suggestion.
         mScript.simulateTimePassing();
         long expectedSystemClockMillis1 = mScript.calculateTimeInMillisForNow(utcTime1);
-        mScript.simulatePhoneTimeSuggestion(timeSuggestion1)
+        mScript.simulateTelephonyTimeSuggestion(timeSuggestion1)
                 .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1)
-                .assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
+                .assertLatestTelephonySuggestion(slotIndex, timeSuggestion1);
 
         // The UTC time increment should be larger than the system clock update threshold so we
         // know it shouldn't be ignored for other reasons.
@@ -269,11 +269,11 @@
         long referenceTimeBeforeLastSignalMillis = utcTime1.getReferenceTimeMillis() - 1;
         TimestampedValue<Long> utcTime2 = new TimestampedValue<>(
                 referenceTimeBeforeLastSignalMillis, validUtcTimeMillis);
-        PhoneTimeSuggestion timeSuggestion2 =
-                createPhoneTimeSuggestion(phoneId, utcTime2);
-        mScript.simulatePhoneTimeSuggestion(timeSuggestion2)
+        TelephonyTimeSuggestion timeSuggestion2 =
+                createTelephonyTimeSuggestion(slotIndex, utcTime2);
+        mScript.simulateTelephonyTimeSuggestion(timeSuggestion2)
                 .verifySystemClockWasNotSetAndResetCallTracking()
-                .assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
+                .assertLatestTelephonySuggestion(slotIndex, timeSuggestion1);
 
         // Now supply a new signal that has an obviously bogus reference time : substantially in the
         // future.
@@ -281,36 +281,36 @@
                 utcTime1.getReferenceTimeMillis() + Integer.MAX_VALUE + 1;
         TimestampedValue<Long> utcTime3 = new TimestampedValue<>(
                 referenceTimeInFutureMillis, validUtcTimeMillis);
-        PhoneTimeSuggestion timeSuggestion3 =
-                createPhoneTimeSuggestion(phoneId, utcTime3);
-        mScript.simulatePhoneTimeSuggestion(timeSuggestion3)
+        TelephonyTimeSuggestion timeSuggestion3 =
+                createTelephonyTimeSuggestion(slotIndex, utcTime3);
+        mScript.simulateTelephonyTimeSuggestion(timeSuggestion3)
                 .verifySystemClockWasNotSetAndResetCallTracking()
-                .assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
+                .assertLatestTelephonySuggestion(slotIndex, timeSuggestion1);
 
         // Just to prove validUtcTimeMillis is valid.
         long validReferenceTimeMillis = utcTime1.getReferenceTimeMillis() + 100;
         TimestampedValue<Long> utcTime4 = new TimestampedValue<>(
                 validReferenceTimeMillis, validUtcTimeMillis);
         long expectedSystemClockMillis4 = mScript.calculateTimeInMillisForNow(utcTime4);
-        PhoneTimeSuggestion timeSuggestion4 =
-                createPhoneTimeSuggestion(phoneId, utcTime4);
-        mScript.simulatePhoneTimeSuggestion(timeSuggestion4)
+        TelephonyTimeSuggestion timeSuggestion4 =
+                createTelephonyTimeSuggestion(slotIndex, utcTime4);
+        mScript.simulateTelephonyTimeSuggestion(timeSuggestion4)
                 .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis4)
-                .assertLatestPhoneSuggestion(phoneId, timeSuggestion4);
+                .assertLatestTelephonySuggestion(slotIndex, timeSuggestion4);
     }
 
     @Test
-    public void testSuggestPhoneTime_timeDetectionToggled() {
+    public void testSuggestTelephonyTime_timeDetectionToggled() {
         final int clockIncrementMillis = 100;
         final int systemClockUpdateThreshold = 2000;
         mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
                 .pokeThresholds(systemClockUpdateThreshold)
                 .pokeAutoTimeDetectionEnabled(false);
 
-        int phoneId = ARBITRARY_PHONE_ID;
+        int slotIndex = ARBITRARY_SLOT_INDEX;
         long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS;
-        PhoneTimeSuggestion timeSuggestion1 =
-                mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis);
+        TelephonyTimeSuggestion timeSuggestion1 =
+                mScript.generateTelephonyTimeSuggestion(slotIndex, testTimeMillis);
         TimestampedValue<Long> utcTime1 = timeSuggestion1.getUtcTime();
 
         // Simulate time passing.
@@ -318,9 +318,9 @@
 
         // Simulate the time signal being received. It should not be used because auto time
         // detection is off but it should be recorded.
-        mScript.simulatePhoneTimeSuggestion(timeSuggestion1)
+        mScript.simulateTelephonyTimeSuggestion(timeSuggestion1)
                 .verifySystemClockWasNotSetAndResetCallTracking()
-                .assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
+                .assertLatestTelephonySuggestion(slotIndex, timeSuggestion1);
 
         // Simulate more time passing.
         mScript.simulateTimePassing(clockIncrementMillis);
@@ -330,17 +330,17 @@
         // Turn on auto time detection.
         mScript.simulateAutoTimeDetectionToggle()
                 .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1)
-                .assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
+                .assertLatestTelephonySuggestion(slotIndex, timeSuggestion1);
 
         // Turn off auto time detection.
         mScript.simulateAutoTimeDetectionToggle()
                 .verifySystemClockWasNotSetAndResetCallTracking()
-                .assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
+                .assertLatestTelephonySuggestion(slotIndex, timeSuggestion1);
 
         // Receive another valid time signal.
         // It should be on the threshold and accounting for the clock increments.
-        PhoneTimeSuggestion timeSuggestion2 = mScript.generatePhoneTimeSuggestion(
-                phoneId, mScript.peekSystemClockMillis() + systemClockUpdateThreshold);
+        TelephonyTimeSuggestion timeSuggestion2 = mScript.generateTelephonyTimeSuggestion(
+                slotIndex, mScript.peekSystemClockMillis() + systemClockUpdateThreshold);
 
         // Simulate more time passing.
         mScript.simulateTimePassing(clockIncrementMillis);
@@ -350,45 +350,45 @@
 
         // The new time, though valid, should not be set in the system clock because auto time is
         // disabled.
-        mScript.simulatePhoneTimeSuggestion(timeSuggestion2)
+        mScript.simulateTelephonyTimeSuggestion(timeSuggestion2)
                 .verifySystemClockWasNotSetAndResetCallTracking()
-                .assertLatestPhoneSuggestion(phoneId, timeSuggestion2);
+                .assertLatestTelephonySuggestion(slotIndex, timeSuggestion2);
 
         // Turn on auto time detection.
         mScript.simulateAutoTimeDetectionToggle()
                 .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis2)
-                .assertLatestPhoneSuggestion(phoneId, timeSuggestion2);
+                .assertLatestTelephonySuggestion(slotIndex, timeSuggestion2);
     }
 
     @Test
-    public void testSuggestPhoneTime_maxSuggestionAge() {
+    public void testSuggestTelephonyTime_maxSuggestionAge() {
         mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
                 .pokeAutoTimeDetectionEnabled(true);
 
-        int phoneId = ARBITRARY_PHONE_ID;
+        int slotIndex = ARBITRARY_SLOT_INDEX;
         long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS;
-        PhoneTimeSuggestion phoneSuggestion =
-                mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis);
+        TelephonyTimeSuggestion telephonySuggestion =
+                mScript.generateTelephonyTimeSuggestion(slotIndex, testTimeMillis);
 
         mScript.simulateTimePassing();
 
         long expectedSystemClockMillis =
-                mScript.calculateTimeInMillisForNow(phoneSuggestion.getUtcTime());
-        mScript.simulatePhoneTimeSuggestion(phoneSuggestion)
+                mScript.calculateTimeInMillisForNow(telephonySuggestion.getUtcTime());
+        mScript.simulateTelephonyTimeSuggestion(telephonySuggestion)
                 .verifySystemClockWasSetAndResetCallTracking(
                         expectedSystemClockMillis  /* expectedNetworkBroadcast */)
-                .assertLatestPhoneSuggestion(phoneId, phoneSuggestion);
+                .assertLatestTelephonySuggestion(slotIndex, telephonySuggestion);
 
-        // Look inside and check what the strategy considers the current best phone suggestion.
-        assertEquals(phoneSuggestion, mScript.peekBestPhoneSuggestion());
+        // Look inside and check what the strategy considers the current best telephony suggestion.
+        assertEquals(telephonySuggestion, mScript.peekBestTelephonySuggestion());
 
-        // Simulate time passing, long enough that phoneSuggestion is now too old.
+        // Simulate time passing, long enough that telephonySuggestion is now too old.
         mScript.simulateTimePassing(TimeDetectorStrategyImpl.MAX_UTC_TIME_AGE_MILLIS);
 
-        // Look inside and check what the strategy considers the current best phone suggestion. It
-        // should still be the, it's just no longer used.
-        assertNull(mScript.peekBestPhoneSuggestion());
-        mScript.assertLatestPhoneSuggestion(phoneId, phoneSuggestion);
+        // Look inside and check what the strategy considers the current best telephony suggestion.
+        // It should still be the, it's just no longer used.
+        assertNull(mScript.peekBestTelephonySuggestion());
+        mScript.assertLatestTelephonySuggestion(slotIndex, telephonySuggestion);
     }
 
     @Test
@@ -413,21 +413,21 @@
         mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
                 .pokeAutoTimeDetectionEnabled(true);
 
-        int phoneId = ARBITRARY_PHONE_ID;
+        int slotIndex = ARBITRARY_SLOT_INDEX;
 
-        // Simulate a phone suggestion.
+        // Simulate a telephony suggestion.
         long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS;
-        PhoneTimeSuggestion phoneTimeSuggestion =
-                mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis);
+        TelephonyTimeSuggestion telephonyTimeSuggestion =
+                mScript.generateTelephonyTimeSuggestion(slotIndex, testTimeMillis);
 
         // Simulate the passage of time.
         mScript.simulateTimePassing();
 
         long expectedAutoClockMillis =
-                mScript.calculateTimeInMillisForNow(phoneTimeSuggestion.getUtcTime());
-        mScript.simulatePhoneTimeSuggestion(phoneTimeSuggestion)
+                mScript.calculateTimeInMillisForNow(telephonyTimeSuggestion.getUtcTime());
+        mScript.simulateTelephonyTimeSuggestion(telephonyTimeSuggestion)
                 .verifySystemClockWasSetAndResetCallTracking(expectedAutoClockMillis)
-                .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion);
+                .assertLatestTelephonySuggestion(slotIndex, telephonyTimeSuggestion);
 
         // Simulate the passage of time.
         mScript.simulateTimePassing();
@@ -435,7 +435,7 @@
         // Switch to manual.
         mScript.simulateAutoTimeDetectionToggle()
                 .verifySystemClockWasNotSetAndResetCallTracking()
-                .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion);
+                .assertLatestTelephonySuggestion(slotIndex, telephonyTimeSuggestion);
 
         // Simulate the passage of time.
         mScript.simulateTimePassing();
@@ -450,7 +450,7 @@
                 mScript.calculateTimeInMillisForNow(manualTimeSuggestion.getUtcTime());
         mScript.simulateManualTimeSuggestion(manualTimeSuggestion)
                 .verifySystemClockWasSetAndResetCallTracking(expectedManualClockMillis)
-                .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion);
+                .assertLatestTelephonySuggestion(slotIndex, telephonyTimeSuggestion);
 
         // Simulate the passage of time.
         mScript.simulateTimePassing();
@@ -459,14 +459,14 @@
         mScript.simulateAutoTimeDetectionToggle();
 
         expectedAutoClockMillis =
-                mScript.calculateTimeInMillisForNow(phoneTimeSuggestion.getUtcTime());
+                mScript.calculateTimeInMillisForNow(telephonyTimeSuggestion.getUtcTime());
         mScript.verifySystemClockWasSetAndResetCallTracking(expectedAutoClockMillis)
-                .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion);
+                .assertLatestTelephonySuggestion(slotIndex, telephonyTimeSuggestion);
 
         // Switch back to manual - nothing should happen to the clock.
         mScript.simulateAutoTimeDetectionToggle()
                 .verifySystemClockWasNotSetAndResetCallTracking()
-                .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion);
+                .assertLatestTelephonySuggestion(slotIndex, telephonyTimeSuggestion);
     }
 
     /**
@@ -515,19 +515,19 @@
     }
 
     @Test
-    public void testSuggestNetworkTime_phoneSuggestionsBeatNetworkSuggestions() {
+    public void testSuggestNetworkTime_telephonySuggestionsBeatNetworkSuggestions() {
         mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
                 .pokeAutoTimeDetectionEnabled(true);
 
         // Three obviously different times that could not be mistaken for each other.
         long networkTimeMillis1 = ARBITRARY_TEST_TIME_MILLIS;
         long networkTimeMillis2 = ARBITRARY_TEST_TIME_MILLIS + Duration.ofDays(30).toMillis();
-        long phoneTimeMillis = ARBITRARY_TEST_TIME_MILLIS + Duration.ofDays(60).toMillis();
+        long telephonyTimeMillis = ARBITRARY_TEST_TIME_MILLIS + Duration.ofDays(60).toMillis();
         // A small increment used to simulate the passage of time, but not enough to interfere with
         // macro-level time changes associated with suggestion age.
         final long smallTimeIncrementMillis = 101;
 
-        // A network suggestion is made. It should be used because there is no phone suggestion.
+        // A network suggestion is made. It should be used because there is no telephony suggestion.
         NetworkTimeSuggestion networkTimeSuggestion1 =
                 mScript.generateNetworkTimeSuggestion(networkTimeMillis1);
         mScript.simulateTimePassing(smallTimeIncrementMillis)
@@ -536,37 +536,37 @@
                         mScript.calculateTimeInMillisForNow(networkTimeSuggestion1.getUtcTime()));
 
         // Check internal state.
-        mScript.assertLatestPhoneSuggestion(ARBITRARY_PHONE_ID, null)
+        mScript.assertLatestTelephonySuggestion(ARBITRARY_SLOT_INDEX, null)
                 .assertLatestNetworkSuggestion(networkTimeSuggestion1);
         assertEquals(networkTimeSuggestion1, mScript.peekLatestValidNetworkSuggestion());
-        assertNull(mScript.peekBestPhoneSuggestion());
+        assertNull(mScript.peekBestTelephonySuggestion());
 
         // Simulate a little time passing.
         mScript.simulateTimePassing(smallTimeIncrementMillis)
                 .verifySystemClockWasNotSetAndResetCallTracking();
 
-        // Now a phone suggestion is made. Phone suggestions are prioritized over network
+        // Now a telephony suggestion is made. Telephony suggestions are prioritized over network
         // suggestions so it should "win".
-        PhoneTimeSuggestion phoneTimeSuggestion =
-                mScript.generatePhoneTimeSuggestion(ARBITRARY_PHONE_ID, phoneTimeMillis);
+        TelephonyTimeSuggestion telephonyTimeSuggestion =
+                mScript.generateTelephonyTimeSuggestion(ARBITRARY_SLOT_INDEX, telephonyTimeMillis);
         mScript.simulateTimePassing(smallTimeIncrementMillis)
-                .simulatePhoneTimeSuggestion(phoneTimeSuggestion)
+                .simulateTelephonyTimeSuggestion(telephonyTimeSuggestion)
                 .verifySystemClockWasSetAndResetCallTracking(
-                        mScript.calculateTimeInMillisForNow(phoneTimeSuggestion.getUtcTime()));
+                        mScript.calculateTimeInMillisForNow(telephonyTimeSuggestion.getUtcTime()));
 
         // Check internal state.
-        mScript.assertLatestPhoneSuggestion(ARBITRARY_PHONE_ID, phoneTimeSuggestion)
+        mScript.assertLatestTelephonySuggestion(ARBITRARY_SLOT_INDEX, telephonyTimeSuggestion)
                 .assertLatestNetworkSuggestion(networkTimeSuggestion1);
         assertEquals(networkTimeSuggestion1, mScript.peekLatestValidNetworkSuggestion());
-        assertEquals(phoneTimeSuggestion, mScript.peekBestPhoneSuggestion());
+        assertEquals(telephonyTimeSuggestion, mScript.peekBestTelephonySuggestion());
 
         // Simulate some significant time passing: half the time allowed before a time signal
         // becomes "too old to use".
         mScript.simulateTimePassing(TimeDetectorStrategyImpl.MAX_UTC_TIME_AGE_MILLIS / 2)
                 .verifySystemClockWasNotSetAndResetCallTracking();
 
-        // Now another network suggestion is made. Phone suggestions are prioritized over network
-        // suggestions so the latest phone suggestion should still "win".
+        // Now another network suggestion is made. Telephony suggestions are prioritized over
+        // network suggestions so the latest telephony suggestion should still "win".
         NetworkTimeSuggestion networkTimeSuggestion2 =
                 mScript.generateNetworkTimeSuggestion(networkTimeMillis2);
         mScript.simulateTimePassing(smallTimeIncrementMillis)
@@ -574,14 +574,14 @@
                 .verifySystemClockWasNotSetAndResetCallTracking();
 
         // Check internal state.
-        mScript.assertLatestPhoneSuggestion(ARBITRARY_PHONE_ID, phoneTimeSuggestion)
+        mScript.assertLatestTelephonySuggestion(ARBITRARY_SLOT_INDEX, telephonyTimeSuggestion)
                 .assertLatestNetworkSuggestion(networkTimeSuggestion2);
         assertEquals(networkTimeSuggestion2, mScript.peekLatestValidNetworkSuggestion());
-        assertEquals(phoneTimeSuggestion, mScript.peekBestPhoneSuggestion());
+        assertEquals(telephonyTimeSuggestion, mScript.peekBestTelephonySuggestion());
 
         // Simulate some significant time passing: half the time allowed before a time signal
-        // becomes "too old to use". This should mean that phoneTimeSuggestion is now too old to be
-        // used but networkTimeSuggestion2 is not.
+        // becomes "too old to use". This should mean that telephonyTimeSuggestion is now too old to
+        // be used but networkTimeSuggestion2 is not.
         mScript.simulateTimePassing(TimeDetectorStrategyImpl.MAX_UTC_TIME_AGE_MILLIS / 2);
 
         // NOTE: The TimeDetectorStrategyImpl doesn't set an alarm for the point when the last
@@ -591,10 +591,10 @@
         mScript.verifySystemClockWasNotSetAndResetCallTracking();
 
         // Check internal state.
-        mScript.assertLatestPhoneSuggestion(ARBITRARY_PHONE_ID, phoneTimeSuggestion)
+        mScript.assertLatestTelephonySuggestion(ARBITRARY_SLOT_INDEX, telephonyTimeSuggestion)
                 .assertLatestNetworkSuggestion(networkTimeSuggestion2);
         assertEquals(networkTimeSuggestion2, mScript.peekLatestValidNetworkSuggestion());
-        assertNull(mScript.peekBestPhoneSuggestion());
+        assertNull(mScript.peekBestTelephonySuggestion());
 
         // Toggle auto-time off and on to force the detection logic to run.
         mScript.simulateAutoTimeDetectionToggle()
@@ -606,10 +606,10 @@
                 mScript.calculateTimeInMillisForNow(networkTimeSuggestion2.getUtcTime()));
 
         // Check internal state.
-        mScript.assertLatestPhoneSuggestion(ARBITRARY_PHONE_ID, phoneTimeSuggestion)
+        mScript.assertLatestTelephonySuggestion(ARBITRARY_SLOT_INDEX, telephonyTimeSuggestion)
                 .assertLatestNetworkSuggestion(networkTimeSuggestion2);
         assertEquals(networkTimeSuggestion2, mScript.peekLatestValidNetworkSuggestion());
-        assertNull(mScript.peekBestPhoneSuggestion());
+        assertNull(mScript.peekBestTelephonySuggestion());
     }
 
     /**
@@ -760,8 +760,8 @@
             return mFakeCallback.peekSystemClockMillis();
         }
 
-        Script simulatePhoneTimeSuggestion(PhoneTimeSuggestion timeSuggestion) {
-            mTimeDetectorStrategy.suggestPhoneTime(timeSuggestion);
+        Script simulateTelephonyTimeSuggestion(TelephonyTimeSuggestion timeSuggestion) {
+            mTimeDetectorStrategy.suggestTelephonyTime(timeSuggestion);
             return this;
         }
 
@@ -806,10 +806,10 @@
         }
 
         /**
-         * White box test info: Asserts the latest suggestion for the phone ID is as expected.
+         * White box test info: Asserts the latest suggestion for the slotIndex is as expected.
          */
-        Script assertLatestPhoneSuggestion(int phoneId, PhoneTimeSuggestion expected) {
-            assertEquals(expected, mTimeDetectorStrategy.getLatestPhoneSuggestion(phoneId));
+        Script assertLatestTelephonySuggestion(int slotIndex, TelephonyTimeSuggestion expected) {
+            assertEquals(expected, mTimeDetectorStrategy.getLatestTelephonySuggestion(slotIndex));
             return this;
         }
 
@@ -822,11 +822,11 @@
         }
 
         /**
-         * White box test info: Returns the phone suggestion that would be used, if any, given the
-         * current elapsed real time clock and regardless of origin prioritization.
+         * White box test info: Returns the telephony suggestion that would be used, if any, given
+         * the current elapsed real time clock and regardless of origin prioritization.
          */
-        PhoneTimeSuggestion peekBestPhoneSuggestion() {
-            return mTimeDetectorStrategy.findBestPhoneSuggestionForTests();
+        TelephonyTimeSuggestion peekBestTelephonySuggestion() {
+            return mTimeDetectorStrategy.findBestTelephonySuggestionForTests();
         }
 
         /**
@@ -848,15 +848,15 @@
         }
 
         /**
-         * Generates a PhoneTimeSuggestion using the current elapsed realtime clock for the
-         * reference time.
+         * Generates a {@link TelephonyTimeSuggestion} using the current elapsed realtime clock for
+         * the reference time.
          */
-        PhoneTimeSuggestion generatePhoneTimeSuggestion(int phoneId, Long timeMillis) {
+        TelephonyTimeSuggestion generateTelephonyTimeSuggestion(int slotIndex, Long timeMillis) {
             TimestampedValue<Long> time = null;
             if (timeMillis != null) {
                 time = new TimestampedValue<>(peekElapsedRealtimeMillis(), timeMillis);
             }
-            return createPhoneTimeSuggestion(phoneId, time);
+            return createTelephonyTimeSuggestion(slotIndex, time);
         }
 
         /**
@@ -878,9 +878,9 @@
         }
     }
 
-    private static PhoneTimeSuggestion createPhoneTimeSuggestion(int phoneId,
+    private static TelephonyTimeSuggestion createTelephonyTimeSuggestion(int slotIndex,
             TimestampedValue<Long> utcTime) {
-        return new PhoneTimeSuggestion.Builder(phoneId)
+        return new TelephonyTimeSuggestion.Builder(slotIndex)
                 .setUtcTime(utcTime)
                 .build();
     }
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TestHandler.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TestHandler.java
new file mode 100644
index 0000000..21c9685
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TestHandler.java
@@ -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.server.timezonedetector;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+/**
+ * A Handler that can track posts/sends and wait for them to be completed.
+ */
+public class TestHandler extends Handler {
+
+    private final Object mMonitor = new Object();
+    private int mMessagesProcessed = 0;
+    private int mMessagesSent = 0;
+
+    public TestHandler(Looper looper) {
+        super(looper);
+    }
+
+    @Override
+    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
+        synchronized (mMonitor) {
+            mMessagesSent++;
+        }
+
+        Runnable callback = msg.getCallback();
+        // Have the callback increment the mMessagesProcessed when it is done. It will notify
+        // any threads waiting for all messages to be processed if appropriate.
+        Runnable newCallback = () -> {
+            callback.run();
+            synchronized (mMonitor) {
+                mMessagesProcessed++;
+                if (mMessagesSent == mMessagesProcessed) {
+                    mMonitor.notifyAll();
+                }
+            }
+        };
+        msg.setCallback(newCallback);
+        return super.sendMessageAtTime(msg, uptimeMillis);
+    }
+
+    /** Asserts the number of messages posted or sent is as expected. */
+    public void assertTotalMessagesEnqueued(int expected) {
+        synchronized (mMonitor) {
+            assertEquals(expected, mMessagesSent);
+        }
+    }
+
+    /**
+     * Waits for all enqueued work to be completed before returning.
+     */
+    public void waitForMessagesToBeProcessed() throws InterruptedException {
+        synchronized (mMonitor) {
+            if (mMessagesSent != mMessagesProcessed) {
+                mMonitor.wait();
+            }
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
new file mode 100644
index 0000000..039c2b4
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
@@ -0,0 +1,233 @@
+/*
+ * 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.timezonedetector;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.timezonedetector.ManualTimeZoneSuggestion;
+import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.HandlerThread;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.PrintWriter;
+
+@RunWith(AndroidJUnit4.class)
+public class TimeZoneDetectorServiceTest {
+
+    private Context mMockContext;
+    private StubbedTimeZoneDetectorStrategy mStubbedTimeZoneDetectorStrategy;
+
+    private TimeZoneDetectorService mTimeZoneDetectorService;
+    private HandlerThread mHandlerThread;
+    private TestHandler mTestHandler;
+
+
+    @Before
+    public void setUp() {
+        mMockContext = mock(Context.class);
+
+        // Create a thread + handler for processing the work that the service posts.
+        mHandlerThread = new HandlerThread("TimeZoneDetectorServiceTest");
+        mHandlerThread.start();
+        mTestHandler = new TestHandler(mHandlerThread.getLooper());
+
+        mStubbedTimeZoneDetectorStrategy = new StubbedTimeZoneDetectorStrategy();
+
+        mTimeZoneDetectorService = new TimeZoneDetectorService(
+                mMockContext, mTestHandler, mStubbedTimeZoneDetectorStrategy);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mHandlerThread.quit();
+        mHandlerThread.join();
+    }
+
+    @Test(expected = SecurityException.class)
+    public void testSuggestTelephonyTime_withoutPermission() {
+        doThrow(new SecurityException("Mock"))
+                .when(mMockContext).enforceCallingPermission(anyString(), any());
+        TelephonyTimeZoneSuggestion timeZoneSuggestion = createTelephonyTimeZoneSuggestion();
+
+        try {
+            mTimeZoneDetectorService.suggestTelephonyTimeZone(timeZoneSuggestion);
+            fail();
+        } finally {
+            verify(mMockContext).enforceCallingPermission(
+                    eq(android.Manifest.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE),
+                    anyString());
+        }
+    }
+
+    @Test
+    public void testSuggestTelephonyTimeZone() throws Exception {
+        doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
+
+        TelephonyTimeZoneSuggestion timeZoneSuggestion = createTelephonyTimeZoneSuggestion();
+        mTimeZoneDetectorService.suggestTelephonyTimeZone(timeZoneSuggestion);
+        mTestHandler.assertTotalMessagesEnqueued(1);
+
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE),
+                anyString());
+
+        mTestHandler.waitForMessagesToBeProcessed();
+        mStubbedTimeZoneDetectorStrategy.verifySuggestTelephonyTimeZoneCalled(timeZoneSuggestion);
+    }
+
+    @Test(expected = SecurityException.class)
+    public void testSuggestManualTime_withoutPermission() {
+        doThrow(new SecurityException("Mock"))
+                .when(mMockContext).enforceCallingOrSelfPermission(anyString(), any());
+        ManualTimeZoneSuggestion timeZoneSuggestion = createManualTimeZoneSuggestion();
+
+        try {
+            mTimeZoneDetectorService.suggestManualTimeZone(timeZoneSuggestion);
+            fail();
+        } finally {
+            verify(mMockContext).enforceCallingOrSelfPermission(
+                    eq(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE),
+                    anyString());
+        }
+    }
+
+    @Test
+    public void testSuggestManualTimeZone() throws Exception {
+        doNothing().when(mMockContext).enforceCallingOrSelfPermission(anyString(), any());
+
+        ManualTimeZoneSuggestion timeZoneSuggestion = createManualTimeZoneSuggestion();
+        mTimeZoneDetectorService.suggestManualTimeZone(timeZoneSuggestion);
+        mTestHandler.assertTotalMessagesEnqueued(1);
+
+        verify(mMockContext).enforceCallingOrSelfPermission(
+                eq(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE),
+                anyString());
+
+        mTestHandler.waitForMessagesToBeProcessed();
+        mStubbedTimeZoneDetectorStrategy.verifySuggestManualTimeZoneCalled(timeZoneSuggestion);
+    }
+
+    @Test
+    public void testDump() {
+        when(mMockContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP))
+                .thenReturn(PackageManager.PERMISSION_GRANTED);
+
+        mTimeZoneDetectorService.dump(null, null, null);
+
+        verify(mMockContext).checkCallingOrSelfPermission(eq(android.Manifest.permission.DUMP));
+        mStubbedTimeZoneDetectorStrategy.verifyDumpCalled();
+    }
+
+    @Test
+    public void testAutoTimeZoneDetectionChanged() throws Exception {
+        mTimeZoneDetectorService.handleAutoTimeZoneDetectionChanged();
+        mTestHandler.assertTotalMessagesEnqueued(1);
+        mTestHandler.waitForMessagesToBeProcessed();
+        mStubbedTimeZoneDetectorStrategy.verifyHandleAutoTimeZoneDetectionChangedCalled();
+
+        mStubbedTimeZoneDetectorStrategy.resetCallTracking();
+
+        mTimeZoneDetectorService.handleAutoTimeZoneDetectionChanged();
+        mTestHandler.assertTotalMessagesEnqueued(2);
+        mTestHandler.waitForMessagesToBeProcessed();
+        mStubbedTimeZoneDetectorStrategy.verifyHandleAutoTimeZoneDetectionChangedCalled();
+    }
+
+    private static TelephonyTimeZoneSuggestion createTelephonyTimeZoneSuggestion() {
+        int slotIndex = 1234;
+        return new TelephonyTimeZoneSuggestion.Builder(slotIndex)
+                .setZoneId("TestZoneId")
+                .setMatchType(TelephonyTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET)
+                .setQuality(TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE)
+                .build();
+    }
+
+    private static ManualTimeZoneSuggestion createManualTimeZoneSuggestion() {
+        return new ManualTimeZoneSuggestion("TestZoneId");
+    }
+
+    private static class StubbedTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
+
+        // Call tracking.
+        private TelephonyTimeZoneSuggestion mLastTelephonySuggestion;
+        private ManualTimeZoneSuggestion mLastManualSuggestion;
+        private boolean mHandleAutoTimeZoneDetectionChangedCalled;
+        private boolean mDumpCalled;
+
+        @Override
+        public void suggestTelephonyTimeZone(TelephonyTimeZoneSuggestion timeZoneSuggestion) {
+            mLastTelephonySuggestion = timeZoneSuggestion;
+        }
+
+        @Override
+        public void suggestManualTimeZone(ManualTimeZoneSuggestion timeZoneSuggestion) {
+            mLastManualSuggestion = timeZoneSuggestion;
+        }
+
+        @Override
+        public void handleAutoTimeZoneDetectionChanged() {
+            mHandleAutoTimeZoneDetectionChangedCalled = true;
+        }
+
+        @Override
+        public void dump(PrintWriter pw, String[] args) {
+            mDumpCalled = true;
+        }
+
+        void resetCallTracking() {
+            mLastTelephonySuggestion = null;
+            mLastManualSuggestion = null;
+            mHandleAutoTimeZoneDetectionChangedCalled = false;
+            mDumpCalled = false;
+        }
+
+        void verifySuggestTelephonyTimeZoneCalled(TelephonyTimeZoneSuggestion expectedSuggestion) {
+            assertEquals(expectedSuggestion, mLastTelephonySuggestion);
+        }
+
+        public void verifySuggestManualTimeZoneCalled(ManualTimeZoneSuggestion expectedSuggestion) {
+            assertEquals(expectedSuggestion, mLastManualSuggestion);
+        }
+
+        void verifyHandleAutoTimeZoneDetectionChangedCalled() {
+            assertTrue(mHandleAutoTimeZoneDetectionChangedCalled);
+        }
+
+        void verifyDumpCalled() {
+            assertTrue(mDumpCalled);
+        }
+    }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
new file mode 100644
index 0000000..ba30967
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
@@ -0,0 +1,638 @@
+/*
+ * Copyright 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.server.timezonedetector;
+
+import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID;
+import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET;
+import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY;
+import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY;
+import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS;
+import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET;
+import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE;
+
+import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.TELEPHONY_SCORE_HIGH;
+import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.TELEPHONY_SCORE_HIGHEST;
+import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.TELEPHONY_SCORE_LOW;
+import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.TELEPHONY_SCORE_MEDIUM;
+import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.TELEPHONY_SCORE_NONE;
+import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.TELEPHONY_SCORE_USAGE_THRESHOLD;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.app.timezonedetector.ManualTimeZoneSuggestion;
+import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
+import android.app.timezonedetector.TelephonyTimeZoneSuggestion.MatchType;
+import android.app.timezonedetector.TelephonyTimeZoneSuggestion.Quality;
+
+import com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.QualifiedTelephonyTimeZoneSuggestion;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedList;
+
+/**
+ * White-box unit tests for {@link TimeZoneDetectorStrategyImpl}.
+ */
+public class TimeZoneDetectorStrategyImplTest {
+
+    /** A time zone used for initialization that does not occur elsewhere in tests. */
+    private static final String ARBITRARY_TIME_ZONE_ID = "Etc/UTC";
+    private static final int SLOT_INDEX1 = 10000;
+    private static final int SLOT_INDEX2 = 20000;
+
+    // Suggestion test cases are ordered so that each successive one is of the same or higher score
+    // than the previous.
+    private static final SuggestionTestCase[] TEST_CASES = new SuggestionTestCase[] {
+            newTestCase(MATCH_TYPE_NETWORK_COUNTRY_ONLY,
+                    QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS, TELEPHONY_SCORE_LOW),
+            newTestCase(MATCH_TYPE_NETWORK_COUNTRY_ONLY, QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET,
+                    TELEPHONY_SCORE_MEDIUM),
+            newTestCase(MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET,
+                    QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET, TELEPHONY_SCORE_MEDIUM),
+            newTestCase(MATCH_TYPE_NETWORK_COUNTRY_ONLY, QUALITY_SINGLE_ZONE, TELEPHONY_SCORE_HIGH),
+            newTestCase(MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE,
+                    TELEPHONY_SCORE_HIGH),
+            newTestCase(MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY,
+                    QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET, TELEPHONY_SCORE_HIGHEST),
+            newTestCase(MATCH_TYPE_EMULATOR_ZONE_ID, QUALITY_SINGLE_ZONE, TELEPHONY_SCORE_HIGHEST),
+    };
+
+    private TimeZoneDetectorStrategyImpl mTimeZoneDetectorStrategy;
+    private FakeTimeZoneDetectorStrategyCallback mFakeTimeZoneDetectorStrategyCallback;
+
+    @Before
+    public void setUp() {
+        mFakeTimeZoneDetectorStrategyCallback = new FakeTimeZoneDetectorStrategyCallback();
+        mTimeZoneDetectorStrategy =
+                new TimeZoneDetectorStrategyImpl(mFakeTimeZoneDetectorStrategyCallback);
+    }
+
+    @Test
+    public void testEmptyTelephonySuggestions() {
+        TelephonyTimeZoneSuggestion slotIndex1TimeZoneSuggestion =
+                createEmptySlotIndex1Suggestion();
+        TelephonyTimeZoneSuggestion slotIndex2TimeZoneSuggestion =
+                createEmptySlotIndex2Suggestion();
+        Script script = new Script()
+                .initializeAutoTimeZoneDetection(true)
+                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
+
+        script.suggestTelephonyTimeZone(slotIndex1TimeZoneSuggestion)
+                .verifyTimeZoneNotSet();
+
+        // Assert internal service state.
+        QualifiedTelephonyTimeZoneSuggestion expectedSlotIndex1ScoredSuggestion =
+                new QualifiedTelephonyTimeZoneSuggestion(slotIndex1TimeZoneSuggestion,
+                        TELEPHONY_SCORE_NONE);
+        assertEquals(expectedSlotIndex1ScoredSuggestion,
+                mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
+        assertNull(mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX2));
+        assertEquals(expectedSlotIndex1ScoredSuggestion,
+                mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
+
+        script.suggestTelephonyTimeZone(slotIndex2TimeZoneSuggestion)
+                .verifyTimeZoneNotSet();
+
+        // Assert internal service state.
+        QualifiedTelephonyTimeZoneSuggestion expectedSlotIndex2ScoredSuggestion =
+                new QualifiedTelephonyTimeZoneSuggestion(slotIndex2TimeZoneSuggestion,
+                        TELEPHONY_SCORE_NONE);
+        assertEquals(expectedSlotIndex1ScoredSuggestion,
+                mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
+        assertEquals(expectedSlotIndex2ScoredSuggestion,
+                mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX2));
+        // SlotIndex1 should always beat slotIndex2, all other things being equal.
+        assertEquals(expectedSlotIndex1ScoredSuggestion,
+                mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
+    }
+
+    @Test
+    public void testFirstPlausibleTelephonySuggestionAcceptedWhenTimeZoneUninitialized() {
+        SuggestionTestCase testCase = newTestCase(MATCH_TYPE_NETWORK_COUNTRY_ONLY,
+                QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS, TELEPHONY_SCORE_LOW);
+        TelephonyTimeZoneSuggestion lowQualitySuggestion =
+                testCase.createSuggestion(SLOT_INDEX1, "America/New_York");
+
+        // The device time zone setting is left uninitialized.
+        Script script = new Script()
+                .initializeAutoTimeZoneDetection(true);
+
+        // The very first suggestion will be taken.
+        script.suggestTelephonyTimeZone(lowQualitySuggestion)
+                .verifyTimeZoneSetAndReset(lowQualitySuggestion);
+
+        // Assert internal service state.
+        QualifiedTelephonyTimeZoneSuggestion expectedScoredSuggestion =
+                new QualifiedTelephonyTimeZoneSuggestion(
+                        lowQualitySuggestion, testCase.expectedScore);
+        assertEquals(expectedScoredSuggestion,
+                mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
+        assertEquals(expectedScoredSuggestion,
+                mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
+
+        // Another low quality suggestion will be ignored now that the setting is initialized.
+        TelephonyTimeZoneSuggestion lowQualitySuggestion2 =
+                testCase.createSuggestion(SLOT_INDEX1, "America/Los_Angeles");
+        script.suggestTelephonyTimeZone(lowQualitySuggestion2)
+                .verifyTimeZoneNotSet();
+
+        // Assert internal service state.
+        QualifiedTelephonyTimeZoneSuggestion expectedScoredSuggestion2 =
+                new QualifiedTelephonyTimeZoneSuggestion(
+                        lowQualitySuggestion2, testCase.expectedScore);
+        assertEquals(expectedScoredSuggestion2,
+                mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
+        assertEquals(expectedScoredSuggestion2,
+                mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
+    }
+
+    /**
+     * Confirms that toggling the auto time zone detection setting has the expected behavior when
+     * the strategy is "opinionated".
+     */
+    @Test
+    public void testTogglingAutoTimeZoneDetection() {
+        Script script = new Script();
+
+        for (SuggestionTestCase testCase : TEST_CASES) {
+            // Start with the device in a known state.
+            script.initializeAutoTimeZoneDetection(false)
+                    .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
+
+            TelephonyTimeZoneSuggestion suggestion =
+                    testCase.createSuggestion(SLOT_INDEX1, "Europe/London");
+            script.suggestTelephonyTimeZone(suggestion);
+
+            // When time zone detection is not enabled, the time zone suggestion will not be set
+            // regardless of the score.
+            script.verifyTimeZoneNotSet();
+
+            // Assert internal service state.
+            QualifiedTelephonyTimeZoneSuggestion expectedScoredSuggestion =
+                    new QualifiedTelephonyTimeZoneSuggestion(suggestion, testCase.expectedScore);
+            assertEquals(expectedScoredSuggestion,
+                    mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
+            assertEquals(expectedScoredSuggestion,
+                    mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
+
+            // Toggling the time zone setting on should cause the device setting to be set.
+            script.autoTimeZoneDetectionEnabled(true);
+
+            // When time zone detection is already enabled the suggestion (if it scores highly
+            // enough) should be set immediately.
+            if (testCase.expectedScore >= TELEPHONY_SCORE_USAGE_THRESHOLD) {
+                script.verifyTimeZoneSetAndReset(suggestion);
+            } else {
+                script.verifyTimeZoneNotSet();
+            }
+
+            // Assert internal service state.
+            assertEquals(expectedScoredSuggestion,
+                    mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
+            assertEquals(expectedScoredSuggestion,
+                    mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
+
+            // Toggling the time zone setting should off should do nothing.
+            script.autoTimeZoneDetectionEnabled(false)
+                    .verifyTimeZoneNotSet();
+
+            // Assert internal service state.
+            assertEquals(expectedScoredSuggestion,
+                    mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
+            assertEquals(expectedScoredSuggestion,
+                    mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
+        }
+    }
+
+    @Test
+    public void testTelephonySuggestionsSingleSlotId() {
+        Script script = new Script()
+                .initializeAutoTimeZoneDetection(true)
+                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
+
+        for (SuggestionTestCase testCase : TEST_CASES) {
+            makeSlotIndex1SuggestionAndCheckState(script, testCase);
+        }
+
+        /*
+         * This is the same test as above but the test cases are in
+         * reverse order of their expected score. New suggestions always replace previous ones:
+         * there's effectively no history and so ordering shouldn't make any difference.
+         */
+
+        // Each test case will have the same or lower score than the last.
+        ArrayList<SuggestionTestCase> descendingCasesByScore =
+                new ArrayList<>(Arrays.asList(TEST_CASES));
+        Collections.reverse(descendingCasesByScore);
+
+        for (SuggestionTestCase testCase : descendingCasesByScore) {
+            makeSlotIndex1SuggestionAndCheckState(script, testCase);
+        }
+    }
+
+    private void makeSlotIndex1SuggestionAndCheckState(Script script, SuggestionTestCase testCase) {
+        // Give the next suggestion a different zone from the currently set device time zone;
+        String currentZoneId = mFakeTimeZoneDetectorStrategyCallback.getDeviceTimeZone();
+        String suggestionZoneId =
+                "Europe/London".equals(currentZoneId) ? "Europe/Paris" : "Europe/London";
+        TelephonyTimeZoneSuggestion zoneSlotIndex1Suggestion =
+                testCase.createSuggestion(SLOT_INDEX1, suggestionZoneId);
+        QualifiedTelephonyTimeZoneSuggestion expectedZoneSlotIndex1ScoredSuggestion =
+                new QualifiedTelephonyTimeZoneSuggestion(
+                        zoneSlotIndex1Suggestion, testCase.expectedScore);
+
+        script.suggestTelephonyTimeZone(zoneSlotIndex1Suggestion);
+        if (testCase.expectedScore >= TELEPHONY_SCORE_USAGE_THRESHOLD) {
+            script.verifyTimeZoneSetAndReset(zoneSlotIndex1Suggestion);
+        } else {
+            script.verifyTimeZoneNotSet();
+        }
+
+        // Assert internal service state.
+        assertEquals(expectedZoneSlotIndex1ScoredSuggestion,
+                mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
+        assertEquals(expectedZoneSlotIndex1ScoredSuggestion,
+                mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
+    }
+
+    /**
+     * Tries a set of test cases to see if the slotIndex with the lowest numeric value is given
+     * preference. This test also confirms that the time zone setting would only be set if a
+     * suggestion is of sufficient quality.
+     */
+    @Test
+    public void testMultipleSlotIndexSuggestionScoringAndSlotIndexBias() {
+        String[] zoneIds = { "Europe/London", "Europe/Paris" };
+        TelephonyTimeZoneSuggestion emptySlotIndex1Suggestion = createEmptySlotIndex1Suggestion();
+        TelephonyTimeZoneSuggestion emptySlotIndex2Suggestion = createEmptySlotIndex2Suggestion();
+        QualifiedTelephonyTimeZoneSuggestion expectedEmptySlotIndex1ScoredSuggestion =
+                new QualifiedTelephonyTimeZoneSuggestion(emptySlotIndex1Suggestion,
+                        TELEPHONY_SCORE_NONE);
+        QualifiedTelephonyTimeZoneSuggestion expectedEmptySlotIndex2ScoredSuggestion =
+                new QualifiedTelephonyTimeZoneSuggestion(emptySlotIndex2Suggestion,
+                        TELEPHONY_SCORE_NONE);
+
+        Script script = new Script()
+                .initializeAutoTimeZoneDetection(true)
+                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
+                // Initialize the latest suggestions as empty so we don't need to worry about nulls
+                // below for the first loop.
+                .suggestTelephonyTimeZone(emptySlotIndex1Suggestion)
+                .suggestTelephonyTimeZone(emptySlotIndex2Suggestion)
+                .resetState();
+
+        for (SuggestionTestCase testCase : TEST_CASES) {
+            TelephonyTimeZoneSuggestion zoneSlotIndex1Suggestion =
+                    testCase.createSuggestion(SLOT_INDEX1, zoneIds[0]);
+            TelephonyTimeZoneSuggestion zoneSlotIndex2Suggestion =
+                    testCase.createSuggestion(SLOT_INDEX2, zoneIds[1]);
+            QualifiedTelephonyTimeZoneSuggestion expectedZoneSlotIndex1ScoredSuggestion =
+                    new QualifiedTelephonyTimeZoneSuggestion(zoneSlotIndex1Suggestion,
+                            testCase.expectedScore);
+            QualifiedTelephonyTimeZoneSuggestion expectedZoneSlotIndex2ScoredSuggestion =
+                    new QualifiedTelephonyTimeZoneSuggestion(zoneSlotIndex2Suggestion,
+                            testCase.expectedScore);
+
+            // Start the test by making a suggestion for slotIndex1.
+            script.suggestTelephonyTimeZone(zoneSlotIndex1Suggestion);
+            if (testCase.expectedScore >= TELEPHONY_SCORE_USAGE_THRESHOLD) {
+                script.verifyTimeZoneSetAndReset(zoneSlotIndex1Suggestion);
+            } else {
+                script.verifyTimeZoneNotSet();
+            }
+
+            // Assert internal service state.
+            assertEquals(expectedZoneSlotIndex1ScoredSuggestion,
+                    mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
+            assertEquals(expectedEmptySlotIndex2ScoredSuggestion,
+                    mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX2));
+            assertEquals(expectedZoneSlotIndex1ScoredSuggestion,
+                    mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
+
+            // SlotIndex2 then makes an alternative suggestion with an identical score. SlotIndex1's
+            // suggestion should still "win" if it is above the required threshold.
+            script.suggestTelephonyTimeZone(zoneSlotIndex2Suggestion);
+            script.verifyTimeZoneNotSet();
+
+            // Assert internal service state.
+            assertEquals(expectedZoneSlotIndex1ScoredSuggestion,
+                    mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
+            assertEquals(expectedZoneSlotIndex2ScoredSuggestion,
+                    mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX2));
+            // SlotIndex1 should always beat slotIndex2, all other things being equal.
+            assertEquals(expectedZoneSlotIndex1ScoredSuggestion,
+                    mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
+
+            // Withdrawing slotIndex1's suggestion should leave slotIndex2 as the new winner. Since
+            // the zoneId is different, the time zone setting should be updated if the score is high
+            // enough.
+            script.suggestTelephonyTimeZone(emptySlotIndex1Suggestion);
+            if (testCase.expectedScore >= TELEPHONY_SCORE_USAGE_THRESHOLD) {
+                script.verifyTimeZoneSetAndReset(zoneSlotIndex2Suggestion);
+            } else {
+                script.verifyTimeZoneNotSet();
+            }
+
+            // Assert internal service state.
+            assertEquals(expectedEmptySlotIndex1ScoredSuggestion,
+                    mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
+            assertEquals(expectedZoneSlotIndex2ScoredSuggestion,
+                    mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX2));
+            assertEquals(expectedZoneSlotIndex2ScoredSuggestion,
+                    mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
+
+            // Reset the state for the next loop.
+            script.suggestTelephonyTimeZone(emptySlotIndex2Suggestion)
+                    .verifyTimeZoneNotSet();
+            assertEquals(expectedEmptySlotIndex1ScoredSuggestion,
+                    mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
+            assertEquals(expectedEmptySlotIndex2ScoredSuggestion,
+                    mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX2));
+        }
+    }
+
+    /**
+     * The {@link TimeZoneDetectorStrategyImpl.Callback} is left to detect whether changing the time
+     * zone is actually necessary. This test proves that the service doesn't assume it knows the
+     * current setting.
+     */
+    @Test
+    public void testTimeZoneDetectorStrategyDoesNotAssumeCurrentSetting() {
+        Script script = new Script()
+                .initializeAutoTimeZoneDetection(true);
+
+        SuggestionTestCase testCase =
+                newTestCase(MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE,
+                        TELEPHONY_SCORE_HIGH);
+        TelephonyTimeZoneSuggestion losAngelesSuggestion =
+                testCase.createSuggestion(SLOT_INDEX1, "America/Los_Angeles");
+        TelephonyTimeZoneSuggestion newYorkSuggestion =
+                testCase.createSuggestion(SLOT_INDEX1, "America/New_York");
+
+        // Initialization.
+        script.suggestTelephonyTimeZone(losAngelesSuggestion)
+                .verifyTimeZoneSetAndReset(losAngelesSuggestion);
+        // Suggest it again - it should not be set because it is already set.
+        script.suggestTelephonyTimeZone(losAngelesSuggestion)
+                .verifyTimeZoneNotSet();
+
+        // Toggling time zone detection should set the device time zone only if the current setting
+        // value is different from the most recent telephony suggestion.
+        script.autoTimeZoneDetectionEnabled(false)
+                .verifyTimeZoneNotSet()
+                .autoTimeZoneDetectionEnabled(true)
+                .verifyTimeZoneNotSet();
+
+        // Simulate a user turning auto detection off, a new suggestion being made while auto
+        // detection is off, and the user turning it on again.
+        script.autoTimeZoneDetectionEnabled(false)
+                .suggestTelephonyTimeZone(newYorkSuggestion)
+                .verifyTimeZoneNotSet();
+        // Latest suggestion should be used.
+        script.autoTimeZoneDetectionEnabled(true)
+                .verifyTimeZoneSetAndReset(newYorkSuggestion);
+    }
+
+    @Test
+    public void testManualSuggestion_autoTimeZoneDetectionEnabled() {
+        Script script = new Script()
+                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
+                .initializeAutoTimeZoneDetection(true);
+
+        // Auto time zone detection is enabled so the manual suggestion should be ignored.
+        script.suggestManualTimeZone(createManualSuggestion("Europe/Paris"))
+            .verifyTimeZoneNotSet();
+    }
+
+
+    @Test
+    public void testManualSuggestion_autoTimeZoneDetectionDisabled() {
+        Script script = new Script()
+                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
+                .initializeAutoTimeZoneDetection(false);
+
+        // Auto time zone detection is disabled so the manual suggestion should be used.
+        ManualTimeZoneSuggestion manualSuggestion = createManualSuggestion("Europe/Paris");
+        script.suggestManualTimeZone(manualSuggestion)
+            .verifyTimeZoneSetAndReset(manualSuggestion);
+    }
+
+    private ManualTimeZoneSuggestion createManualSuggestion(String zoneId) {
+        return new ManualTimeZoneSuggestion(zoneId);
+    }
+
+    private static TelephonyTimeZoneSuggestion createEmptySlotIndex1Suggestion() {
+        return new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX1).build();
+    }
+
+    private static TelephonyTimeZoneSuggestion createEmptySlotIndex2Suggestion() {
+        return new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX2).build();
+    }
+
+    static class FakeTimeZoneDetectorStrategyCallback
+            implements TimeZoneDetectorStrategyImpl.Callback {
+
+        private boolean mAutoTimeZoneDetectionEnabled;
+        private TestState<String> mTimeZoneId = new TestState<>();
+
+        @Override
+        public boolean isAutoTimeZoneDetectionEnabled() {
+            return mAutoTimeZoneDetectionEnabled;
+        }
+
+        @Override
+        public boolean isDeviceTimeZoneInitialized() {
+            return mTimeZoneId.getLatest() != null;
+        }
+
+        @Override
+        public String getDeviceTimeZone() {
+            return mTimeZoneId.getLatest();
+        }
+
+        @Override
+        public void setDeviceTimeZone(String zoneId) {
+            mTimeZoneId.set(zoneId);
+        }
+
+        void initializeAutoTimeZoneDetection(boolean enabled) {
+            mAutoTimeZoneDetectionEnabled = enabled;
+        }
+
+        void initializeTimeZone(String zoneId) {
+            mTimeZoneId.init(zoneId);
+        }
+
+        void setAutoTimeZoneDetectionEnabled(boolean enabled) {
+            mAutoTimeZoneDetectionEnabled = enabled;
+        }
+
+        void assertTimeZoneNotSet() {
+            mTimeZoneId.assertHasNotBeenSet();
+        }
+
+        void assertTimeZoneSet(String timeZoneId) {
+            mTimeZoneId.assertHasBeenSet();
+            mTimeZoneId.assertChangeCount(1);
+            mTimeZoneId.assertLatestEquals(timeZoneId);
+        }
+
+        void commitAllChanges() {
+            mTimeZoneId.commitLatest();
+        }
+    }
+
+    /** Some piece of state that tests want to track. */
+    private static class TestState<T> {
+        private T mInitialValue;
+        private LinkedList<T> mValues = new LinkedList<>();
+
+        void init(T value) {
+            mValues.clear();
+            mInitialValue = value;
+        }
+
+        void set(T value) {
+            mValues.addFirst(value);
+        }
+
+        boolean hasBeenSet() {
+            return mValues.size() > 0;
+        }
+
+        void assertHasNotBeenSet() {
+            assertFalse(hasBeenSet());
+        }
+
+        void assertHasBeenSet() {
+            assertTrue(hasBeenSet());
+        }
+
+        void commitLatest() {
+            if (hasBeenSet()) {
+                mInitialValue = mValues.getLast();
+                mValues.clear();
+            }
+        }
+
+        void assertLatestEquals(T expected) {
+            assertEquals(expected, getLatest());
+        }
+
+        void assertChangeCount(int expectedCount) {
+            assertEquals(expectedCount, mValues.size());
+        }
+
+        public T getLatest() {
+            if (hasBeenSet()) {
+                return mValues.getFirst();
+            }
+            return mInitialValue;
+        }
+    }
+
+    /**
+     * A "fluent" class allows reuse of code in tests: initialization, simulation and verification
+     * logic.
+     */
+    private class Script {
+
+        Script initializeAutoTimeZoneDetection(boolean enabled) {
+            mFakeTimeZoneDetectorStrategyCallback.initializeAutoTimeZoneDetection(enabled);
+            return this;
+        }
+
+        Script initializeTimeZoneSetting(String zoneId) {
+            mFakeTimeZoneDetectorStrategyCallback.initializeTimeZone(zoneId);
+            return this;
+        }
+
+        Script autoTimeZoneDetectionEnabled(boolean enabled) {
+            mFakeTimeZoneDetectorStrategyCallback.setAutoTimeZoneDetectionEnabled(enabled);
+            mTimeZoneDetectorStrategy.handleAutoTimeZoneDetectionChanged();
+            return this;
+        }
+
+        /**
+         * Simulates the time zone detection strategy receiving a telephony-originated suggestion.
+         */
+        Script suggestTelephonyTimeZone(TelephonyTimeZoneSuggestion timeZoneSuggestion) {
+            mTimeZoneDetectorStrategy.suggestTelephonyTimeZone(timeZoneSuggestion);
+            return this;
+        }
+
+        /** Simulates the time zone detection strategy receiving a user-originated suggestion. */
+        Script suggestManualTimeZone(ManualTimeZoneSuggestion manualTimeZoneSuggestion) {
+            mTimeZoneDetectorStrategy.suggestManualTimeZone(manualTimeZoneSuggestion);
+            return this;
+        }
+
+        Script verifyTimeZoneNotSet() {
+            mFakeTimeZoneDetectorStrategyCallback.assertTimeZoneNotSet();
+            return this;
+        }
+
+        Script verifyTimeZoneSetAndReset(TelephonyTimeZoneSuggestion suggestion) {
+            mFakeTimeZoneDetectorStrategyCallback.assertTimeZoneSet(suggestion.getZoneId());
+            mFakeTimeZoneDetectorStrategyCallback.commitAllChanges();
+            return this;
+        }
+
+        Script verifyTimeZoneSetAndReset(ManualTimeZoneSuggestion suggestion) {
+            mFakeTimeZoneDetectorStrategyCallback.assertTimeZoneSet(suggestion.getZoneId());
+            mFakeTimeZoneDetectorStrategyCallback.commitAllChanges();
+            return this;
+        }
+
+        Script resetState() {
+            mFakeTimeZoneDetectorStrategyCallback.commitAllChanges();
+            return this;
+        }
+    }
+
+    private static class SuggestionTestCase {
+        public final int matchType;
+        public final int quality;
+        public final int expectedScore;
+
+        SuggestionTestCase(int matchType, int quality, int expectedScore) {
+            this.matchType = matchType;
+            this.quality = quality;
+            this.expectedScore = expectedScore;
+        }
+
+        private TelephonyTimeZoneSuggestion createSuggestion(int slotIndex, String zoneId) {
+            return new TelephonyTimeZoneSuggestion.Builder(slotIndex)
+                    .setZoneId(zoneId)
+                    .setMatchType(matchType)
+                    .setQuality(quality)
+                    .build();
+        }
+    }
+
+    private static SuggestionTestCase newTestCase(
+            @MatchType int matchType, @Quality int quality, int expectedScore) {
+        return new SuggestionTestCase(matchType, quality, expectedScore);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyTest.java
deleted file mode 100644
index 2429cfc..0000000
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyTest.java
+++ /dev/null
@@ -1,626 +0,0 @@
-/*
- * Copyright 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.server.timezonedetector;
-
-import static android.app.timezonedetector.PhoneTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID;
-import static android.app.timezonedetector.PhoneTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET;
-import static android.app.timezonedetector.PhoneTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY;
-import static android.app.timezonedetector.PhoneTimeZoneSuggestion.MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY;
-import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS;
-import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET;
-import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_SINGLE_ZONE;
-
-import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.PHONE_SCORE_HIGH;
-import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.PHONE_SCORE_HIGHEST;
-import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.PHONE_SCORE_LOW;
-import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.PHONE_SCORE_MEDIUM;
-import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.PHONE_SCORE_NONE;
-import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.PHONE_SCORE_USAGE_THRESHOLD;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import android.app.timezonedetector.ManualTimeZoneSuggestion;
-import android.app.timezonedetector.PhoneTimeZoneSuggestion;
-import android.app.timezonedetector.PhoneTimeZoneSuggestion.MatchType;
-import android.app.timezonedetector.PhoneTimeZoneSuggestion.Quality;
-
-import com.android.server.timezonedetector.TimeZoneDetectorStrategy.QualifiedPhoneTimeZoneSuggestion;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.LinkedList;
-
-/**
- * White-box unit tests for {@link TimeZoneDetectorStrategy}.
- */
-public class TimeZoneDetectorStrategyTest {
-
-    /** A time zone used for initialization that does not occur elsewhere in tests. */
-    private static final String ARBITRARY_TIME_ZONE_ID = "Etc/UTC";
-    private static final int PHONE1_ID = 10000;
-    private static final int PHONE2_ID = 20000;
-
-    // Suggestion test cases are ordered so that each successive one is of the same or higher score
-    // than the previous.
-    private static final SuggestionTestCase[] TEST_CASES = new SuggestionTestCase[] {
-            newTestCase(MATCH_TYPE_NETWORK_COUNTRY_ONLY,
-                    QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS, PHONE_SCORE_LOW),
-            newTestCase(MATCH_TYPE_NETWORK_COUNTRY_ONLY, QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET,
-                    PHONE_SCORE_MEDIUM),
-            newTestCase(MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET,
-                    QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET, PHONE_SCORE_MEDIUM),
-            newTestCase(MATCH_TYPE_NETWORK_COUNTRY_ONLY, QUALITY_SINGLE_ZONE, PHONE_SCORE_HIGH),
-            newTestCase(MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE,
-                    PHONE_SCORE_HIGH),
-            newTestCase(MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY,
-                    QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET, PHONE_SCORE_HIGHEST),
-            newTestCase(MATCH_TYPE_EMULATOR_ZONE_ID, QUALITY_SINGLE_ZONE, PHONE_SCORE_HIGHEST),
-    };
-
-    private TimeZoneDetectorStrategy mTimeZoneDetectorStrategy;
-    private FakeTimeZoneDetectorStrategyCallback mFakeTimeZoneDetectorStrategyCallback;
-
-    @Before
-    public void setUp() {
-        mFakeTimeZoneDetectorStrategyCallback = new FakeTimeZoneDetectorStrategyCallback();
-        mTimeZoneDetectorStrategy =
-                new TimeZoneDetectorStrategy(mFakeTimeZoneDetectorStrategyCallback);
-    }
-
-    @Test
-    public void testEmptyPhoneSuggestions() {
-        PhoneTimeZoneSuggestion phone1TimeZoneSuggestion = createEmptyPhone1Suggestion();
-        PhoneTimeZoneSuggestion phone2TimeZoneSuggestion = createEmptyPhone2Suggestion();
-        Script script = new Script()
-                .initializeAutoTimeZoneDetection(true)
-                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
-
-        script.suggestPhoneTimeZone(phone1TimeZoneSuggestion)
-                .verifyTimeZoneNotSet();
-
-        // Assert internal service state.
-        QualifiedPhoneTimeZoneSuggestion expectedPhone1ScoredSuggestion =
-                new QualifiedPhoneTimeZoneSuggestion(phone1TimeZoneSuggestion, PHONE_SCORE_NONE);
-        assertEquals(expectedPhone1ScoredSuggestion,
-                mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID));
-        assertNull(mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE2_ID));
-        assertEquals(expectedPhone1ScoredSuggestion,
-                mTimeZoneDetectorStrategy.findBestPhoneSuggestionForTests());
-
-        script.suggestPhoneTimeZone(phone2TimeZoneSuggestion)
-                .verifyTimeZoneNotSet();
-
-        // Assert internal service state.
-        QualifiedPhoneTimeZoneSuggestion expectedPhone2ScoredSuggestion =
-                new QualifiedPhoneTimeZoneSuggestion(phone2TimeZoneSuggestion, PHONE_SCORE_NONE);
-        assertEquals(expectedPhone1ScoredSuggestion,
-                mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID));
-        assertEquals(expectedPhone2ScoredSuggestion,
-                mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE2_ID));
-        // Phone 1 should always beat phone 2, all other things being equal.
-        assertEquals(expectedPhone1ScoredSuggestion,
-                mTimeZoneDetectorStrategy.findBestPhoneSuggestionForTests());
-    }
-
-    @Test
-    public void testFirstPlausiblePhoneSuggestionAcceptedWhenTimeZoneUninitialized() {
-        SuggestionTestCase testCase = newTestCase(MATCH_TYPE_NETWORK_COUNTRY_ONLY,
-                QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS, PHONE_SCORE_LOW);
-        PhoneTimeZoneSuggestion lowQualitySuggestion =
-                testCase.createSuggestion(PHONE1_ID, "America/New_York");
-
-        // The device time zone setting is left uninitialized.
-        Script script = new Script()
-                .initializeAutoTimeZoneDetection(true);
-
-        // The very first suggestion will be taken.
-        script.suggestPhoneTimeZone(lowQualitySuggestion)
-                .verifyTimeZoneSetAndReset(lowQualitySuggestion);
-
-        // Assert internal service state.
-        QualifiedPhoneTimeZoneSuggestion expectedScoredSuggestion =
-                new QualifiedPhoneTimeZoneSuggestion(lowQualitySuggestion, testCase.expectedScore);
-        assertEquals(expectedScoredSuggestion,
-                mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID));
-        assertEquals(expectedScoredSuggestion,
-                mTimeZoneDetectorStrategy.findBestPhoneSuggestionForTests());
-
-        // Another low quality suggestion will be ignored now that the setting is initialized.
-        PhoneTimeZoneSuggestion lowQualitySuggestion2 =
-                testCase.createSuggestion(PHONE1_ID, "America/Los_Angeles");
-        script.suggestPhoneTimeZone(lowQualitySuggestion2)
-                .verifyTimeZoneNotSet();
-
-        // Assert internal service state.
-        QualifiedPhoneTimeZoneSuggestion expectedScoredSuggestion2 =
-                new QualifiedPhoneTimeZoneSuggestion(lowQualitySuggestion2, testCase.expectedScore);
-        assertEquals(expectedScoredSuggestion2,
-                mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID));
-        assertEquals(expectedScoredSuggestion2,
-                mTimeZoneDetectorStrategy.findBestPhoneSuggestionForTests());
-    }
-
-    /**
-     * Confirms that toggling the auto time zone detection setting has the expected behavior when
-     * the strategy is "opinionated".
-     */
-    @Test
-    public void testTogglingAutoTimeZoneDetection() {
-        Script script = new Script();
-
-        for (SuggestionTestCase testCase : TEST_CASES) {
-            // Start with the device in a known state.
-            script.initializeAutoTimeZoneDetection(false)
-                    .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
-
-            PhoneTimeZoneSuggestion suggestion =
-                    testCase.createSuggestion(PHONE1_ID, "Europe/London");
-            script.suggestPhoneTimeZone(suggestion);
-
-            // When time zone detection is not enabled, the time zone suggestion will not be set
-            // regardless of the score.
-            script.verifyTimeZoneNotSet();
-
-            // Assert internal service state.
-            QualifiedPhoneTimeZoneSuggestion expectedScoredSuggestion =
-                    new QualifiedPhoneTimeZoneSuggestion(suggestion, testCase.expectedScore);
-            assertEquals(expectedScoredSuggestion,
-                    mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID));
-            assertEquals(expectedScoredSuggestion,
-                    mTimeZoneDetectorStrategy.findBestPhoneSuggestionForTests());
-
-            // Toggling the time zone setting on should cause the device setting to be set.
-            script.autoTimeZoneDetectionEnabled(true);
-
-            // When time zone detection is already enabled the suggestion (if it scores highly
-            // enough) should be set immediately.
-            if (testCase.expectedScore >= PHONE_SCORE_USAGE_THRESHOLD) {
-                script.verifyTimeZoneSetAndReset(suggestion);
-            } else {
-                script.verifyTimeZoneNotSet();
-            }
-
-            // Assert internal service state.
-            assertEquals(expectedScoredSuggestion,
-                    mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID));
-            assertEquals(expectedScoredSuggestion,
-                    mTimeZoneDetectorStrategy.findBestPhoneSuggestionForTests());
-
-            // Toggling the time zone setting should off should do nothing.
-            script.autoTimeZoneDetectionEnabled(false)
-                    .verifyTimeZoneNotSet();
-
-            // Assert internal service state.
-            assertEquals(expectedScoredSuggestion,
-                    mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID));
-            assertEquals(expectedScoredSuggestion,
-                    mTimeZoneDetectorStrategy.findBestPhoneSuggestionForTests());
-        }
-    }
-
-    @Test
-    public void testPhoneSuggestionsSinglePhone() {
-        Script script = new Script()
-                .initializeAutoTimeZoneDetection(true)
-                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
-
-        for (SuggestionTestCase testCase : TEST_CASES) {
-            makePhone1SuggestionAndCheckState(script, testCase);
-        }
-
-        /*
-         * This is the same test as above but the test cases are in
-         * reverse order of their expected score. New suggestions always replace previous ones:
-         * there's effectively no history and so ordering shouldn't make any difference.
-         */
-
-        // Each test case will have the same or lower score than the last.
-        ArrayList<SuggestionTestCase> descendingCasesByScore =
-                new ArrayList<>(Arrays.asList(TEST_CASES));
-        Collections.reverse(descendingCasesByScore);
-
-        for (SuggestionTestCase testCase : descendingCasesByScore) {
-            makePhone1SuggestionAndCheckState(script, testCase);
-        }
-    }
-
-    private void makePhone1SuggestionAndCheckState(Script script, SuggestionTestCase testCase) {
-        // Give the next suggestion a different zone from the currently set device time zone;
-        String currentZoneId = mFakeTimeZoneDetectorStrategyCallback.getDeviceTimeZone();
-        String suggestionZoneId =
-                "Europe/London".equals(currentZoneId) ? "Europe/Paris" : "Europe/London";
-        PhoneTimeZoneSuggestion zonePhone1Suggestion =
-                testCase.createSuggestion(PHONE1_ID, suggestionZoneId);
-        QualifiedPhoneTimeZoneSuggestion expectedZonePhone1ScoredSuggestion =
-                new QualifiedPhoneTimeZoneSuggestion(zonePhone1Suggestion, testCase.expectedScore);
-
-        script.suggestPhoneTimeZone(zonePhone1Suggestion);
-        if (testCase.expectedScore >= PHONE_SCORE_USAGE_THRESHOLD) {
-            script.verifyTimeZoneSetAndReset(zonePhone1Suggestion);
-        } else {
-            script.verifyTimeZoneNotSet();
-        }
-
-        // Assert internal service state.
-        assertEquals(expectedZonePhone1ScoredSuggestion,
-                mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID));
-        assertEquals(expectedZonePhone1ScoredSuggestion,
-                mTimeZoneDetectorStrategy.findBestPhoneSuggestionForTests());
-    }
-
-    /**
-     * Tries a set of test cases to see if the phone with the lowest ID is given preference. This
-     * test also confirms that the time zone setting would only be set if a suggestion is of
-     * sufficient quality.
-     */
-    @Test
-    public void testMultiplePhoneSuggestionScoringAndPhoneIdBias() {
-        String[] zoneIds = { "Europe/London", "Europe/Paris" };
-        PhoneTimeZoneSuggestion emptyPhone1Suggestion = createEmptyPhone1Suggestion();
-        PhoneTimeZoneSuggestion emptyPhone2Suggestion = createEmptyPhone2Suggestion();
-        QualifiedPhoneTimeZoneSuggestion expectedEmptyPhone1ScoredSuggestion =
-                new QualifiedPhoneTimeZoneSuggestion(emptyPhone1Suggestion, PHONE_SCORE_NONE);
-        QualifiedPhoneTimeZoneSuggestion expectedEmptyPhone2ScoredSuggestion =
-                new QualifiedPhoneTimeZoneSuggestion(emptyPhone2Suggestion, PHONE_SCORE_NONE);
-
-        Script script = new Script()
-                .initializeAutoTimeZoneDetection(true)
-                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
-                // Initialize the latest suggestions as empty so we don't need to worry about nulls
-                // below for the first loop.
-                .suggestPhoneTimeZone(emptyPhone1Suggestion)
-                .suggestPhoneTimeZone(emptyPhone2Suggestion)
-                .resetState();
-
-        for (SuggestionTestCase testCase : TEST_CASES) {
-            PhoneTimeZoneSuggestion zonePhone1Suggestion =
-                    testCase.createSuggestion(PHONE1_ID, zoneIds[0]);
-            PhoneTimeZoneSuggestion zonePhone2Suggestion =
-                    testCase.createSuggestion(PHONE2_ID, zoneIds[1]);
-            QualifiedPhoneTimeZoneSuggestion expectedZonePhone1ScoredSuggestion =
-                    new QualifiedPhoneTimeZoneSuggestion(zonePhone1Suggestion,
-                            testCase.expectedScore);
-            QualifiedPhoneTimeZoneSuggestion expectedZonePhone2ScoredSuggestion =
-                    new QualifiedPhoneTimeZoneSuggestion(zonePhone2Suggestion,
-                            testCase.expectedScore);
-
-            // Start the test by making a suggestion for phone 1.
-            script.suggestPhoneTimeZone(zonePhone1Suggestion);
-            if (testCase.expectedScore >= PHONE_SCORE_USAGE_THRESHOLD) {
-                script.verifyTimeZoneSetAndReset(zonePhone1Suggestion);
-            } else {
-                script.verifyTimeZoneNotSet();
-            }
-
-            // Assert internal service state.
-            assertEquals(expectedZonePhone1ScoredSuggestion,
-                    mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID));
-            assertEquals(expectedEmptyPhone2ScoredSuggestion,
-                    mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE2_ID));
-            assertEquals(expectedZonePhone1ScoredSuggestion,
-                    mTimeZoneDetectorStrategy.findBestPhoneSuggestionForTests());
-
-            // Phone 2 then makes an alternative suggestion with an identical score. Phone 1's
-            // suggestion should still "win" if it is above the required threshold.
-            script.suggestPhoneTimeZone(zonePhone2Suggestion);
-            script.verifyTimeZoneNotSet();
-
-            // Assert internal service state.
-            assertEquals(expectedZonePhone1ScoredSuggestion,
-                    mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID));
-            assertEquals(expectedZonePhone2ScoredSuggestion,
-                    mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE2_ID));
-            // Phone 1 should always beat phone 2, all other things being equal.
-            assertEquals(expectedZonePhone1ScoredSuggestion,
-                    mTimeZoneDetectorStrategy.findBestPhoneSuggestionForTests());
-
-            // Withdrawing phone 1's suggestion should leave phone 2 as the new winner. Since the
-            // zoneId is different, the time zone setting should be updated if the score is high
-            // enough.
-            script.suggestPhoneTimeZone(emptyPhone1Suggestion);
-            if (testCase.expectedScore >= PHONE_SCORE_USAGE_THRESHOLD) {
-                script.verifyTimeZoneSetAndReset(zonePhone2Suggestion);
-            } else {
-                script.verifyTimeZoneNotSet();
-            }
-
-            // Assert internal service state.
-            assertEquals(expectedEmptyPhone1ScoredSuggestion,
-                    mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID));
-            assertEquals(expectedZonePhone2ScoredSuggestion,
-                    mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE2_ID));
-            assertEquals(expectedZonePhone2ScoredSuggestion,
-                    mTimeZoneDetectorStrategy.findBestPhoneSuggestionForTests());
-
-            // Reset the state for the next loop.
-            script.suggestPhoneTimeZone(emptyPhone2Suggestion)
-                    .verifyTimeZoneNotSet();
-            assertEquals(expectedEmptyPhone1ScoredSuggestion,
-                    mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID));
-            assertEquals(expectedEmptyPhone2ScoredSuggestion,
-                    mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE2_ID));
-        }
-    }
-
-    /**
-     * The {@link TimeZoneDetectorStrategy.Callback} is left to detect whether changing the time
-     * zone is actually necessary. This test proves that the service doesn't assume it knows the
-     * current setting.
-     */
-    @Test
-    public void testTimeZoneDetectorStrategyDoesNotAssumeCurrentSetting() {
-        Script script = new Script()
-                .initializeAutoTimeZoneDetection(true);
-
-        SuggestionTestCase testCase =
-                newTestCase(MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE,
-                        PHONE_SCORE_HIGH);
-        PhoneTimeZoneSuggestion losAngelesSuggestion =
-                testCase.createSuggestion(PHONE1_ID, "America/Los_Angeles");
-        PhoneTimeZoneSuggestion newYorkSuggestion =
-                testCase.createSuggestion(PHONE1_ID, "America/New_York");
-
-        // Initialization.
-        script.suggestPhoneTimeZone(losAngelesSuggestion)
-                .verifyTimeZoneSetAndReset(losAngelesSuggestion);
-        // Suggest it again - it should not be set because it is already set.
-        script.suggestPhoneTimeZone(losAngelesSuggestion)
-                .verifyTimeZoneNotSet();
-
-        // Toggling time zone detection should set the device time zone only if the current setting
-        // value is different from the most recent phone suggestion.
-        script.autoTimeZoneDetectionEnabled(false)
-                .verifyTimeZoneNotSet()
-                .autoTimeZoneDetectionEnabled(true)
-                .verifyTimeZoneNotSet();
-
-        // Simulate a user turning auto detection off, a new suggestion being made while auto
-        // detection is off, and the user turning it on again.
-        script.autoTimeZoneDetectionEnabled(false)
-                .suggestPhoneTimeZone(newYorkSuggestion)
-                .verifyTimeZoneNotSet();
-        // Latest suggestion should be used.
-        script.autoTimeZoneDetectionEnabled(true)
-                .verifyTimeZoneSetAndReset(newYorkSuggestion);
-    }
-
-    @Test
-    public void testManualSuggestion_autoTimeZoneDetectionEnabled() {
-        Script script = new Script()
-                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
-                .initializeAutoTimeZoneDetection(true);
-
-        // Auto time zone detection is enabled so the manual suggestion should be ignored.
-        script.suggestManualTimeZone(createManualSuggestion("Europe/Paris"))
-            .verifyTimeZoneNotSet();
-    }
-
-
-    @Test
-    public void testManualSuggestion_autoTimeZoneDetectionDisabled() {
-        Script script = new Script()
-                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
-                .initializeAutoTimeZoneDetection(false);
-
-        // Auto time zone detection is disabled so the manual suggestion should be used.
-        ManualTimeZoneSuggestion manualSuggestion = createManualSuggestion("Europe/Paris");
-        script.suggestManualTimeZone(manualSuggestion)
-            .verifyTimeZoneSetAndReset(manualSuggestion);
-    }
-
-    private ManualTimeZoneSuggestion createManualSuggestion(String zoneId) {
-        return new ManualTimeZoneSuggestion(zoneId);
-    }
-
-    private static PhoneTimeZoneSuggestion createEmptyPhone1Suggestion() {
-        return new PhoneTimeZoneSuggestion.Builder(PHONE1_ID).build();
-    }
-
-    private static PhoneTimeZoneSuggestion createEmptyPhone2Suggestion() {
-        return new PhoneTimeZoneSuggestion.Builder(PHONE2_ID).build();
-    }
-
-    static class FakeTimeZoneDetectorStrategyCallback implements TimeZoneDetectorStrategy.Callback {
-
-        private boolean mAutoTimeZoneDetectionEnabled;
-        private TestState<String> mTimeZoneId = new TestState<>();
-
-        @Override
-        public boolean isAutoTimeZoneDetectionEnabled() {
-            return mAutoTimeZoneDetectionEnabled;
-        }
-
-        @Override
-        public boolean isDeviceTimeZoneInitialized() {
-            return mTimeZoneId.getLatest() != null;
-        }
-
-        @Override
-        public String getDeviceTimeZone() {
-            return mTimeZoneId.getLatest();
-        }
-
-        @Override
-        public void setDeviceTimeZone(String zoneId) {
-            mTimeZoneId.set(zoneId);
-        }
-
-        void initializeAutoTimeZoneDetection(boolean enabled) {
-            mAutoTimeZoneDetectionEnabled = enabled;
-        }
-
-        void initializeTimeZone(String zoneId) {
-            mTimeZoneId.init(zoneId);
-        }
-
-        void setAutoTimeZoneDetectionEnabled(boolean enabled) {
-            mAutoTimeZoneDetectionEnabled = enabled;
-        }
-
-        void assertTimeZoneNotSet() {
-            mTimeZoneId.assertHasNotBeenSet();
-        }
-
-        void assertTimeZoneSet(String timeZoneId) {
-            mTimeZoneId.assertHasBeenSet();
-            mTimeZoneId.assertChangeCount(1);
-            mTimeZoneId.assertLatestEquals(timeZoneId);
-        }
-
-        void commitAllChanges() {
-            mTimeZoneId.commitLatest();
-        }
-    }
-
-    /** Some piece of state that tests want to track. */
-    private static class TestState<T> {
-        private T mInitialValue;
-        private LinkedList<T> mValues = new LinkedList<>();
-
-        void init(T value) {
-            mValues.clear();
-            mInitialValue = value;
-        }
-
-        void set(T value) {
-            mValues.addFirst(value);
-        }
-
-        boolean hasBeenSet() {
-            return mValues.size() > 0;
-        }
-
-        void assertHasNotBeenSet() {
-            assertFalse(hasBeenSet());
-        }
-
-        void assertHasBeenSet() {
-            assertTrue(hasBeenSet());
-        }
-
-        void commitLatest() {
-            if (hasBeenSet()) {
-                mInitialValue = mValues.getLast();
-                mValues.clear();
-            }
-        }
-
-        void assertLatestEquals(T expected) {
-            assertEquals(expected, getLatest());
-        }
-
-        void assertChangeCount(int expectedCount) {
-            assertEquals(expectedCount, mValues.size());
-        }
-
-        public T getLatest() {
-            if (hasBeenSet()) {
-                return mValues.getFirst();
-            }
-            return mInitialValue;
-        }
-    }
-
-    /**
-     * A "fluent" class allows reuse of code in tests: initialization, simulation and verification
-     * logic.
-     */
-    private class Script {
-
-        Script initializeAutoTimeZoneDetection(boolean enabled) {
-            mFakeTimeZoneDetectorStrategyCallback.initializeAutoTimeZoneDetection(enabled);
-            return this;
-        }
-
-        Script initializeTimeZoneSetting(String zoneId) {
-            mFakeTimeZoneDetectorStrategyCallback.initializeTimeZone(zoneId);
-            return this;
-        }
-
-        Script autoTimeZoneDetectionEnabled(boolean enabled) {
-            mFakeTimeZoneDetectorStrategyCallback.setAutoTimeZoneDetectionEnabled(enabled);
-            mTimeZoneDetectorStrategy.handleAutoTimeZoneDetectionChange();
-            return this;
-        }
-
-        /** Simulates the time zone detection strategy receiving a phone-originated suggestion. */
-        Script suggestPhoneTimeZone(PhoneTimeZoneSuggestion phoneTimeZoneSuggestion) {
-            mTimeZoneDetectorStrategy.suggestPhoneTimeZone(phoneTimeZoneSuggestion);
-            return this;
-        }
-
-        /** Simulates the time zone detection strategy receiving a user-originated suggestion. */
-        Script suggestManualTimeZone(ManualTimeZoneSuggestion manualTimeZoneSuggestion) {
-            mTimeZoneDetectorStrategy.suggestManualTimeZone(manualTimeZoneSuggestion);
-            return this;
-        }
-
-        Script verifyTimeZoneNotSet() {
-            mFakeTimeZoneDetectorStrategyCallback.assertTimeZoneNotSet();
-            return this;
-        }
-
-        Script verifyTimeZoneSetAndReset(PhoneTimeZoneSuggestion suggestion) {
-            mFakeTimeZoneDetectorStrategyCallback.assertTimeZoneSet(suggestion.getZoneId());
-            mFakeTimeZoneDetectorStrategyCallback.commitAllChanges();
-            return this;
-        }
-
-        Script verifyTimeZoneSetAndReset(ManualTimeZoneSuggestion suggestion) {
-            mFakeTimeZoneDetectorStrategyCallback.assertTimeZoneSet(suggestion.getZoneId());
-            mFakeTimeZoneDetectorStrategyCallback.commitAllChanges();
-            return this;
-        }
-
-        Script resetState() {
-            mFakeTimeZoneDetectorStrategyCallback.commitAllChanges();
-            return this;
-        }
-    }
-
-    private static class SuggestionTestCase {
-        public final int matchType;
-        public final int quality;
-        public final int expectedScore;
-
-        SuggestionTestCase(int matchType, int quality, int expectedScore) {
-            this.matchType = matchType;
-            this.quality = quality;
-            this.expectedScore = expectedScore;
-        }
-
-        private PhoneTimeZoneSuggestion createSuggestion(int phoneId, String zoneId) {
-            return new PhoneTimeZoneSuggestion.Builder(phoneId)
-                    .setZoneId(zoneId)
-                    .setMatchType(matchType)
-                    .setQuality(quality)
-                    .build();
-        }
-    }
-
-    private static SuggestionTestCase newTestCase(
-            @MatchType int matchType, @Quality int quality, int expectedScore) {
-        return new SuggestionTestCase(matchType, quality, expectedScore);
-    }
-}
diff --git a/services/tests/uiservicestests/AndroidManifest.xml b/services/tests/uiservicestests/AndroidManifest.xml
index 180deb5..dab0a5f 100644
--- a/services/tests/uiservicestests/AndroidManifest.xml
+++ b/services/tests/uiservicestests/AndroidManifest.xml
@@ -28,6 +28,8 @@
     <uses-permission android:name="android.permission.ACCESS_VOICE_INTERACTION_SERVICE" />
     <uses-permission android:name="android.permission.DEVICE_POWER" />
     <uses-permission android:name="android.permission.ACCESS_CONTENT_PROVIDERS_EXTERNALLY" />
+    <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE"/>
+    <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG"/>
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.OBSERVE_ROLE_HOLDERS" />
     <uses-permission android:name="android.permission.GET_INTENT_SENDER_INTENT"/>
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
index d16c232a..47ad831 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
@@ -97,7 +97,7 @@
         NotificationChannel updatedChannel =
                 new NotificationChannel("a", "", IMPORTANCE_HIGH);
         when(mConfig.getConversationNotificationChannel(
-                any(), anyInt(), eq("a"), eq(r.sbn.getShortcutId(mContext)), eq(true), eq(false)))
+                any(), anyInt(), eq("a"), eq(r.getSbn().getShortcutId(mContext)), eq(true), eq(false)))
                 .thenReturn(updatedChannel);
 
         assertNull(extractor.process(r));
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 34872db..e0ee3ce 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -30,6 +30,9 @@
 import static android.app.NotificationManager.IMPORTANCE_MAX;
 import static android.app.NotificationManager.IMPORTANCE_NONE;
 import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
+import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CALLS;
+import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CONVERSATIONS;
+import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
@@ -67,6 +70,7 @@
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
@@ -684,8 +688,9 @@
         NotificationRecord nrBubble = generateMessageBubbleNotifRecord(true /* addMetadata */,
                 mTestNotificationChannel, 1 /* id */, "tag", groupKey, false /* isSummary */);
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, nrBubble.sbn.getTag(),
-                nrBubble.sbn.getId(), nrBubble.sbn.getNotification(), nrBubble.sbn.getUserId());
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nrBubble.getSbn().getTag(),
+                nrBubble.getSbn().getId(), nrBubble.getSbn().getNotification(),
+                nrBubble.getSbn().getUserId());
         waitForIdle();
 
         // Make sure we are a bubble
@@ -697,8 +702,9 @@
         NotificationRecord nrPlain = generateMessageBubbleNotifRecord(false /* addMetadata */,
                 mTestNotificationChannel, 2 /* id */, "tag", groupKey, false /* isSummary */);
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, nrPlain.sbn.getTag(),
-                nrPlain.sbn.getId(), nrPlain.sbn.getNotification(), nrPlain.sbn.getUserId());
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nrPlain.getSbn().getTag(),
+                nrPlain.getSbn().getId(), nrPlain.getSbn().getNotification(),
+                nrPlain.getSbn().getUserId());
         waitForIdle();
 
         notifsAfter = mBinderService.getActiveNotifications(PKG);
@@ -711,8 +717,9 @@
         if (summaryAutoCancel) {
             nrSummary.getNotification().flags |= FLAG_AUTO_CANCEL;
         }
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, nrSummary.sbn.getTag(),
-                nrSummary.sbn.getId(), nrSummary.sbn.getNotification(), nrSummary.sbn.getUserId());
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nrSummary.getSbn().getTag(),
+                nrSummary.getSbn().getId(), nrSummary.getSbn().getNotification(),
+                nrSummary.getSbn().getUserId());
         waitForIdle();
 
         notifsAfter = mBinderService.getActiveNotifications(PKG);
@@ -891,7 +898,7 @@
 
         mBinderService.createNotificationChannels(
                 PKG, new ParceledListSlice(Arrays.asList(channel)));
-        final StatusBarNotification sbn = generateNotificationRecord(channel).sbn;
+        final StatusBarNotification sbn = generateNotificationRecord(channel).getSbn();
         mBinderService.enqueueNotificationWithTag(PKG, PKG,
                 "testBlockedNotifications_blockedChannel",
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
@@ -909,7 +916,7 @@
         mBinderService.createNotificationChannels(
                 PKG, new ParceledListSlice(Arrays.asList(channel)));
 
-        final StatusBarNotification sbn = generateNotificationRecord(channel).sbn;
+        final StatusBarNotification sbn = generateNotificationRecord(channel).getSbn();
         sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
         mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
@@ -938,7 +945,7 @@
         assertEquals(IMPORTANCE_NONE, mBinderService.getNotificationChannel(
                 PKG, mContext.getUserId(), PKG, channel.getId()).getImportance());
 
-        StatusBarNotification sbn = generateNotificationRecord(channel).sbn;
+        StatusBarNotification sbn = generateNotificationRecord(channel).getSbn();
         sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
         mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
@@ -960,7 +967,7 @@
         assertEquals(IMPORTANCE_NONE, mBinderService.getNotificationChannel(
                 PKG, mContext.getUserId(), PKG, channel.getId()).getImportance());
 
-        sbn = generateNotificationRecord(channel).sbn;
+        sbn = generateNotificationRecord(channel).getSbn();
         sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
         mBinderService.enqueueNotificationWithTag(PKG, PKG,
                 "testEnqueuedBlockedNotifications_userBlockedChannelForegroundService",
@@ -994,7 +1001,7 @@
 
         mBinderService.setNotificationsEnabledForPackage(PKG, mUid, false);
 
-        final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
+        final StatusBarNotification sbn = generateNotificationRecord(null).getSbn();
         mBinderService.enqueueNotificationWithTag(PKG, PKG,
                 "testEnqueuedBlockedNotifications_blockedApp",
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
@@ -1008,7 +1015,7 @@
 
         mBinderService.setNotificationsEnabledForPackage(PKG, mUid, false);
 
-        final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
+        final StatusBarNotification sbn = generateNotificationRecord(null).getSbn();
         sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
         mBinderService.enqueueNotificationWithTag(PKG, PKG,
                 "testEnqueuedBlockedNotifications_blockedAppForegroundService",
@@ -1031,7 +1038,7 @@
         int id = 0;
         for (String category: categories) {
             final StatusBarNotification sbn =
-                    generateNotificationRecord(mTestNotificationChannel, ++id, "", false).sbn;
+                    generateNotificationRecord(mTestNotificationChannel, ++id, "", false).getSbn();
             sbn.getNotification().category = category;
             mBinderService.enqueueNotificationWithTag(PKG, PKG,
                     "testEnqueuedRestrictedNotifications_asSystem",
@@ -1056,7 +1063,7 @@
         int id = 0;
         for (String category: categories) {
             final StatusBarNotification sbn =
-                    generateNotificationRecord(mTestNotificationChannel, ++id, "", false).sbn;
+                    generateNotificationRecord(mTestNotificationChannel, ++id, "", false).getSbn();
             sbn.getNotification().category = category;
             mBinderService.enqueueNotificationWithTag(PKG, PKG,
                     "testEnqueuedRestrictedNotifications_notAutomotive",
@@ -1079,7 +1086,7 @@
                 Notification.CATEGORY_CAR_WARNING,
                 Notification.CATEGORY_CAR_INFORMATION);
         for (String category: categories) {
-            final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
+            final StatusBarNotification sbn = generateNotificationRecord(null).getSbn();
             sbn.getNotification().category = category;
             try {
                 mBinderService.enqueueNotificationWithTag(PKG, PKG,
@@ -1107,7 +1114,7 @@
         Bundle bundle = new Bundle();
         bundle.putInt(KEY_IMPORTANCE, IMPORTANCE_NONE);
         Adjustment adjustment = new Adjustment(
-                r.sbn.getPackageName(), r.getKey(), bundle, "", r.getUser().getIdentifier());
+                r.getSbn().getPackageName(), r.getKey(), bundle, "", r.getUser().getIdentifier());
         mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment);
 
         NotificationManagerService.PostNotificationRunnable runnable =
@@ -1142,11 +1149,11 @@
         assertNull(call.old);
         assertEquals(0, call.position);
         assertEquals(0, call.buzzBeepBlink);
-        assertEquals(PKG, call.r.sbn.getPackageName());
-        assertEquals(0, call.r.sbn.getId());
-        assertEquals(tag, call.r.sbn.getTag());
-        assertNotNull(call.r.sbn.getInstanceId());
-        assertEquals(0, call.r.sbn.getInstanceId().getId());
+        assertEquals(PKG, call.r.getSbn().getPackageName());
+        assertEquals(0, call.r.getSbn().getId());
+        assertEquals(tag, call.r.getSbn().getTag());
+        assertNotNull(call.r.getSbn().getInstanceId());
+        assertEquals(0, call.r.getSbn().getInstanceId().getId());
     }
 
     @Test
@@ -1168,14 +1175,14 @@
         assertEquals(
                 NotificationRecordLogger.NotificationReportedEvents.NOTIFICATION_POSTED,
                 mNotificationRecordLogger.get(0).getUiEvent());
-        assertEquals(0, mNotificationRecordLogger.get(0).r.sbn.getInstanceId().getId());
+        assertEquals(0, mNotificationRecordLogger.get(0).r.getSbn().getInstanceId().getId());
 
         assertTrue(mNotificationRecordLogger.get(1).shouldLog());
         assertEquals(
                 NotificationRecordLogger.NotificationReportedEvents.NOTIFICATION_UPDATED,
                 mNotificationRecordLogger.get(1).getUiEvent());
         // Instance ID doesn't change on update of an active notification
-        assertEquals(0, mNotificationRecordLogger.get(1).r.sbn.getInstanceId().getId());
+        assertEquals(0, mNotificationRecordLogger.get(1).r.getSbn().getInstanceId().getId());
     }
 
     @Test
@@ -1209,14 +1216,14 @@
         assertEquals(
                 NotificationRecordLogger.NotificationReportedEvents.NOTIFICATION_POSTED,
                 mNotificationRecordLogger.get(0).getUiEvent());
-        assertEquals(0, mNotificationRecordLogger.get(0).r.sbn.getInstanceId().getId());
+        assertEquals(0, mNotificationRecordLogger.get(0).r.getSbn().getInstanceId().getId());
 
         assertTrue(mNotificationRecordLogger.get(1).shouldLog());
         assertEquals(
                 NotificationRecordLogger.NotificationReportedEvents.NOTIFICATION_POSTED,
                 mNotificationRecordLogger.get(1).getUiEvent());
         // New instance ID because notification was canceled before re-post
-        assertEquals(1, mNotificationRecordLogger.get(1).r.sbn.getInstanceId().getId());
+        assertEquals(1, mNotificationRecordLogger.get(1).r.getSbn().getInstanceId().getId());
     }
 
     @Test
@@ -1257,7 +1264,7 @@
     @Test
     public void testCancelNotificationsFromListenerImmediatelyAfterEnqueue() throws Exception {
         NotificationRecord r = generateNotificationRecord(null);
-        final StatusBarNotification sbn = r.sbn;
+        final StatusBarNotification sbn = r.getSbn();
         mBinderService.enqueueNotificationWithTag(PKG, PKG,
                 "testCancelNotificationsFromListenerImmediatelyAfterEnqueue",
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
@@ -1271,7 +1278,7 @@
 
     @Test
     public void testCancelAllNotificationsImmediatelyAfterEnqueue() throws Exception {
-        final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
+        final StatusBarNotification sbn = generateNotificationRecord(null).getSbn();
         mBinderService.enqueueNotificationWithTag(PKG, PKG,
                 "testCancelAllNotificationsImmediatelyAfterEnqueue",
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
@@ -1286,7 +1293,7 @@
     @Test
     public void testCancelImmediatelyAfterEnqueueNotifiesListeners_ForegroundServiceFlag()
             throws Exception {
-        final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
+        final StatusBarNotification sbn = generateNotificationRecord(null).getSbn();
         sbn.getNotification().flags =
                 Notification.FLAG_ONGOING_EVENT | FLAG_FOREGROUND_SERVICE;
         mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
@@ -1304,14 +1311,14 @@
 
         mBinderService.enqueueNotificationWithTag(PKG, PKG,
                 "testUserInitiatedClearAll_noLeak",
-                n.sbn.getId(), n.sbn.getNotification(), n.sbn.getUserId());
+                n.getSbn().getId(), n.getSbn().getNotification(), n.getSbn().getUserId());
         waitForIdle();
 
         mService.mNotificationDelegate.onClearAll(mUid, Binder.getCallingPid(),
                 n.getUserId());
         waitForIdle();
         StatusBarNotification[] notifs =
-                mBinderService.getActiveNotifications(n.sbn.getPackageName());
+                mBinderService.getActiveNotifications(n.getSbn().getPackageName());
         assertEquals(0, notifs.length);
         assertEquals(0, mService.getNotificationRecordCount());
         ArgumentCaptor<NotificationStats> captor = ArgumentCaptor.forClass(NotificationStats.class);
@@ -1328,20 +1335,22 @@
 
         mBinderService.enqueueNotificationWithTag(PKG, PKG,
                 "testCancelAllNotificationsCancelsChildren",
-                parent.sbn.getId(), parent.sbn.getNotification(), parent.sbn.getUserId());
+                parent.getSbn().getId(), parent.getSbn().getNotification(),
+                parent.getSbn().getUserId());
         mBinderService.enqueueNotificationWithTag(PKG, PKG,
                 "testCancelAllNotificationsCancelsChildren",
-                child.sbn.getId(), child.sbn.getNotification(), child.sbn.getUserId());
+                child.getSbn().getId(), child.getSbn().getNotification(),
+                child.getSbn().getUserId());
         waitForIdle();
 
-        mBinderService.cancelAllNotifications(PKG, parent.sbn.getUserId());
+        mBinderService.cancelAllNotifications(PKG, parent.getSbn().getUserId());
         waitForIdle();
         assertEquals(0, mService.getNotificationRecordCount());
     }
 
     @Test
     public void testCancelAllNotificationsMultipleEnqueuedDoesNotCrash() throws Exception {
-        final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
+        final StatusBarNotification sbn = generateNotificationRecord(null).getSbn();
         for (int i = 0; i < 10; i++) {
             mBinderService.enqueueNotificationWithTag(PKG, PKG,
                     "testCancelAllNotificationsMultipleEnqueuedDoesNotCrash",
@@ -1365,20 +1374,22 @@
         // fully post parent notification
         mBinderService.enqueueNotificationWithTag(PKG, PKG,
                 "testCancelGroupSummaryMultipleEnqueuedChildrenDoesNotCrash",
-                parent.sbn.getId(), parent.sbn.getNotification(), parent.sbn.getUserId());
+                parent.getSbn().getId(), parent.getSbn().getNotification(),
+                parent.getSbn().getUserId());
         waitForIdle();
 
         // enqueue the child several times
         for (int i = 0; i < 10; i++) {
             mBinderService.enqueueNotificationWithTag(PKG, PKG,
                     "testCancelGroupSummaryMultipleEnqueuedChildrenDoesNotCrash",
-                    child.sbn.getId(), child.sbn.getNotification(), child.sbn.getUserId());
+                    child.getSbn().getId(), child.getSbn().getNotification(),
+                    child.getSbn().getUserId());
         }
         // make the parent a child, which will cancel the child notification
         mBinderService.enqueueNotificationWithTag(PKG, PKG,
                 "testCancelGroupSummaryMultipleEnqueuedChildrenDoesNotCrash",
-                parentAsChild.sbn.getId(), parentAsChild.sbn.getNotification(),
-                parentAsChild.sbn.getUserId());
+                parentAsChild.getSbn().getId(), parentAsChild.getSbn().getNotification(),
+                parentAsChild.getSbn().getUserId());
         waitForIdle();
 
         assertEquals(0, mService.getNotificationRecordCount());
@@ -1395,7 +1406,7 @@
         mService.mAutobundledSummaries.get(0).put("pkg", summary.getKey());
         mService.updateAutobundledSummaryFlags(0, "pkg", true, false);
 
-        assertTrue(summary.sbn.isOngoing());
+        assertTrue(summary.getSbn().isOngoing());
     }
 
     @Test
@@ -1411,12 +1422,12 @@
 
         mService.updateAutobundledSummaryFlags(0, "pkg", false, false);
 
-        assertFalse(summary.sbn.isOngoing());
+        assertFalse(summary.getSbn().isOngoing());
     }
 
     @Test
     public void testCancelAllNotifications_IgnoreForegroundService() throws Exception {
-        final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
+        final StatusBarNotification sbn = generateNotificationRecord(null).getSbn();
         sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
         mBinderService.enqueueNotificationWithTag(PKG, PKG,
                 "testCancelAllNotifications_IgnoreForegroundService",
@@ -1431,7 +1442,7 @@
 
     @Test
     public void testCancelAllNotifications_IgnoreOtherPackages() throws Exception {
-        final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
+        final StatusBarNotification sbn = generateNotificationRecord(null).getSbn();
         sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
         mBinderService.enqueueNotificationWithTag(PKG, PKG,
                 "testCancelAllNotifications_IgnoreOtherPackages",
@@ -1446,7 +1457,7 @@
 
     @Test
     public void testCancelAllNotifications_NullPkgRemovesAll() throws Exception {
-        final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
+        final StatusBarNotification sbn = generateNotificationRecord(null).getSbn();
         mBinderService.enqueueNotificationWithTag(PKG, PKG,
                 "testCancelAllNotifications_NullPkgRemovesAll",
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
@@ -1460,7 +1471,7 @@
 
     @Test
     public void testCancelAllNotifications_NullPkgIgnoresUserAllNotifications() throws Exception {
-        final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
+        final StatusBarNotification sbn = generateNotificationRecord(null).getSbn();
         mBinderService.enqueueNotificationWithTag(PKG, PKG,
                 "testCancelAllNotifications_NullPkgIgnoresUserAllNotifications",
                 sbn.getId(), sbn.getNotification(), UserHandle.USER_ALL);
@@ -1475,7 +1486,7 @@
 
     @Test
     public void testAppInitiatedCancelAllNotifications_CancelsNoClearFlag() throws Exception {
-        final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
+        final StatusBarNotification sbn = generateNotificationRecord(null).getSbn();
         sbn.getNotification().flags |= Notification.FLAG_NO_CLEAR;
         mBinderService.enqueueNotificationWithTag(PKG, PKG,
                 "testAppInitiatedCancelAllNotifications_CancelsNoClearFlag",
@@ -1497,7 +1508,7 @@
                 notif.getUserId(), 0, null);
         waitForIdle();
         StatusBarNotification[] notifs =
-                mBinderService.getActiveNotifications(notif.sbn.getPackageName());
+                mBinderService.getActiveNotifications(notif.getSbn().getPackageName());
         assertEquals(0, notifs.length);
     }
 
@@ -1512,7 +1523,7 @@
                 notif.getUserId());
         waitForIdle();
         StatusBarNotification[] notifs =
-                mBinderService.getActiveNotifications(notif.sbn.getPackageName());
+                mBinderService.getActiveNotifications(notif.getSbn().getPackageName());
         assertEquals(1, notifs.length);
     }
 
@@ -1534,7 +1545,7 @@
         mService.getBinderService().cancelNotificationsFromListener(null, null);
         waitForIdle();
         StatusBarNotification[] notifs =
-                mBinderService.getActiveNotifications(parent.sbn.getPackageName());
+                mBinderService.getActiveNotifications(parent.getSbn().getPackageName());
         assertEquals(1, notifs.length);
     }
 
@@ -1557,7 +1568,7 @@
                 parent.getUserId());
         waitForIdle();
         StatusBarNotification[] notifs =
-                mBinderService.getActiveNotifications(parent.sbn.getPackageName());
+                mBinderService.getActiveNotifications(parent.getSbn().getPackageName());
         assertEquals(1, notifs.length);
     }
 
@@ -1582,7 +1593,7 @@
 
     @Test
     public void testCancelAfterSecondEnqueueDoesNotSpecifyForegroundFlag() throws Exception {
-        final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
+        final StatusBarNotification sbn = generateNotificationRecord(null).getSbn();
         sbn.getNotification().flags =
                 Notification.FLAG_ONGOING_EVENT | FLAG_FOREGROUND_SERVICE;
         mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
@@ -1616,7 +1627,7 @@
         mService.getBinderService().cancelNotificationsFromListener(null, null);
         waitForIdle();
         StatusBarNotification[] notifs =
-                mBinderService.getActiveNotifications(parent.sbn.getPackageName());
+                mBinderService.getActiveNotifications(parent.getSbn().getPackageName());
         assertEquals(0, notifs.length);
     }
 
@@ -1636,12 +1647,12 @@
         mService.addNotification(child);
         mService.addNotification(child2);
         mService.addNotification(newGroup);
-        String[] keys = {parent.sbn.getKey(), child.sbn.getKey(),
-                child2.sbn.getKey(), newGroup.sbn.getKey()};
+        String[] keys = {parent.getSbn().getKey(), child.getSbn().getKey(),
+                child2.getSbn().getKey(), newGroup.getSbn().getKey()};
         mService.getBinderService().cancelNotificationsFromListener(null, keys);
         waitForIdle();
         StatusBarNotification[] notifs =
-                mBinderService.getActiveNotifications(parent.sbn.getPackageName());
+                mBinderService.getActiveNotifications(parent.getSbn().getPackageName());
         assertEquals(1, notifs.length);
     }
 
@@ -1664,7 +1675,7 @@
                 parent.getUserId());
         waitForIdle();
         StatusBarNotification[] notifs =
-                mBinderService.getActiveNotifications(parent.sbn.getPackageName());
+                mBinderService.getActiveNotifications(parent.getSbn().getPackageName());
         assertEquals(0, notifs.length);
     }
 
@@ -1680,31 +1691,33 @@
         final NotificationRecord group2 = generateNotificationRecord(
                 mTestNotificationChannel, 2, "group2", true);
         mBinderService.enqueueNotificationWithTag(PKG, PKG, "testFindGroupNotificationsLocked",
-                group2.sbn.getId(), group2.sbn.getNotification(), group2.sbn.getUserId());
+                group2.getSbn().getId(), group2.getSbn().getNotification(),
+                group2.getSbn().getUserId());
         waitForIdle();
 
         // should not be returned
         final NotificationRecord nonGroup = generateNotificationRecord(
                 mTestNotificationChannel, 3, null, false);
         mBinderService.enqueueNotificationWithTag(PKG, PKG, "testFindGroupNotificationsLocked",
-                nonGroup.sbn.getId(), nonGroup.sbn.getNotification(), nonGroup.sbn.getUserId());
+                nonGroup.getSbn().getId(), nonGroup.getSbn().getNotification(),
+                nonGroup.getSbn().getUserId());
         waitForIdle();
 
         // same group, child, should be returned
         final NotificationRecord group1Child = generateNotificationRecord(
                 mTestNotificationChannel, 4, "group1", false);
         mBinderService.enqueueNotificationWithTag(PKG, PKG, "testFindGroupNotificationsLocked",
-                group1Child.sbn.getId(),
-                group1Child.sbn.getNotification(), group1Child.sbn.getUserId());
+                group1Child.getSbn().getId(),
+                group1Child.getSbn().getNotification(), group1Child.getSbn().getUserId());
         waitForIdle();
 
         List<NotificationRecord> inGroup1 =
                 mService.findGroupNotificationsLocked(PKG, group1.getGroupKey(),
-                        group1.sbn.getUserId());
+                        group1.getSbn().getUserId());
         assertEquals(3, inGroup1.size());
         for (NotificationRecord record : inGroup1) {
             assertTrue(record.getGroupKey().equals(group1.getGroupKey()));
-            assertTrue(record.sbn.getId() == 1 || record.sbn.getId() == 4);
+            assertTrue(record.getSbn().getId() == 1 || record.getSbn().getId() == 4);
         }
     }
 
@@ -1718,7 +1731,7 @@
                 Notification.FLAG_ONGOING_EVENT, true, notif.getUserId(), 0, null);
         waitForIdle();
         StatusBarNotification[] notifs =
-                mBinderService.getActiveNotifications(notif.sbn.getPackageName());
+                mBinderService.getActiveNotifications(notif.getSbn().getPackageName());
         assertEquals(0, notifs.length);
     }
 
@@ -1738,18 +1751,18 @@
         mService.addNotification(child);
         mService.addNotification(child2);
         mService.addNotification(newGroup);
-        String[] keys = {parent.sbn.getKey(), child.sbn.getKey(),
-                child2.sbn.getKey(), newGroup.sbn.getKey()};
+        String[] keys = {parent.getSbn().getKey(), child.getSbn().getKey(),
+                child2.getSbn().getKey(), newGroup.getSbn().getKey()};
         mService.getBinderService().cancelNotificationsFromListener(null, keys);
         waitForIdle();
         StatusBarNotification[] notifs =
-                mBinderService.getActiveNotifications(parent.sbn.getPackageName());
+                mBinderService.getActiveNotifications(parent.getSbn().getPackageName());
         assertEquals(0, notifs.length);
     }
 
     @Test
     public void testAppInitiatedCancelAllNotifications_CancelsOnGoingFlag() throws Exception {
-        final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
+        final StatusBarNotification sbn = generateNotificationRecord(null).getSbn();
         sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
         mBinderService.enqueueNotificationWithTag(PKG, PKG,
                 "testAppInitiatedCancelAllNotifications_CancelsOnGoingFlag",
@@ -1771,7 +1784,7 @@
                 notif.getUserId(), 0, null);
         waitForIdle();
         StatusBarNotification[] notifs =
-                mBinderService.getActiveNotifications(notif.sbn.getPackageName());
+                mBinderService.getActiveNotifications(notif.getSbn().getPackageName());
         assertEquals(0, notifs.length);
     }
 
@@ -1786,7 +1799,7 @@
                 notif.getUserId());
         waitForIdle();
         StatusBarNotification[] notifs =
-                mBinderService.getActiveNotifications(notif.sbn.getPackageName());
+                mBinderService.getActiveNotifications(notif.getSbn().getPackageName());
         assertEquals(1, notifs.length);
     }
 
@@ -1808,7 +1821,7 @@
         mService.getBinderService().cancelNotificationsFromListener(null, null);
         waitForIdle();
         StatusBarNotification[] notifs =
-                mBinderService.getActiveNotifications(parent.sbn.getPackageName());
+                mBinderService.getActiveNotifications(parent.getSbn().getPackageName());
         assertEquals(1, notifs.length);
     }
 
@@ -1828,12 +1841,12 @@
         mService.addNotification(child);
         mService.addNotification(child2);
         mService.addNotification(newGroup);
-        String[] keys = {parent.sbn.getKey(), child.sbn.getKey(),
-                child2.sbn.getKey(), newGroup.sbn.getKey()};
+        String[] keys = {parent.getSbn().getKey(), child.getSbn().getKey(),
+                child2.getSbn().getKey(), newGroup.getSbn().getKey()};
         mService.getBinderService().cancelNotificationsFromListener(null, keys);
         waitForIdle();
         StatusBarNotification[] notifs =
-                mBinderService.getActiveNotifications(parent.sbn.getPackageName());
+                mBinderService.getActiveNotifications(parent.getSbn().getPackageName());
         assertEquals(0, notifs.length);
     }
 
@@ -1856,7 +1869,7 @@
                 parent.getUserId());
         waitForIdle();
         StatusBarNotification[] notifs =
-                mBinderService.getActiveNotifications(parent.sbn.getPackageName());
+                mBinderService.getActiveNotifications(parent.getSbn().getPackageName());
         assertEquals(1, notifs.length);
     }
 
@@ -1990,7 +2003,8 @@
     public void testUpdateGroupNotifyCreatorBlock() throws Exception {
         NotificationChannelGroup existing = new NotificationChannelGroup("id", "name");
         mService.setPreferencesHelper(mPreferencesHelper);
-        when(mPreferencesHelper.getNotificationChannelGroup(eq(existing.getId()), eq(PKG), anyInt()))
+        when(mPreferencesHelper.getNotificationChannelGroup(eq(existing.getId()),
+                eq(PKG), anyInt()))
                 .thenReturn(existing);
 
         NotificationChannelGroup updated = new NotificationChannelGroup("id", "name");
@@ -2013,7 +2027,8 @@
         NotificationChannelGroup existing = new NotificationChannelGroup("id", "name");
         existing.setBlocked(true);
         mService.setPreferencesHelper(mPreferencesHelper);
-        when(mPreferencesHelper.getNotificationChannelGroup(eq(existing.getId()), eq(PKG), anyInt()))
+        when(mPreferencesHelper.getNotificationChannelGroup(eq(existing.getId()),
+                eq(PKG), anyInt()))
                 .thenReturn(existing);
 
         mBinderService.updateNotificationChannelGroupForPackage(
@@ -2033,7 +2048,8 @@
     public void testUpdateGroupNoNotifyCreatorOtherChanges() throws Exception {
         NotificationChannelGroup existing = new NotificationChannelGroup("id", "name");
         mService.setPreferencesHelper(mPreferencesHelper);
-        when(mPreferencesHelper.getNotificationChannelGroup(eq(existing.getId()), eq(PKG), anyInt()))
+        when(mPreferencesHelper.getNotificationChannelGroup(
+                eq(existing.getId()), eq(PKG), anyInt()))
                 .thenReturn(existing);
 
         mBinderService.updateNotificationChannelGroupForPackage(
@@ -2492,7 +2508,7 @@
     public void testSnoozeRunnable_snoozeGroupChild_onlyChildOfSummary() throws Exception {
         final NotificationRecord parent = generateNotificationRecord(
                 mTestNotificationChannel, 1, "group", true);
-        assertTrue(parent.sbn.getNotification().isGroupSummary());
+        assertTrue(parent.getSbn().getNotification().isGroupSummary());
         final NotificationRecord child = generateNotificationRecord(
                 mTestNotificationChannel, 2, "group", false);
         mService.addNotification(parent);
@@ -2528,7 +2544,8 @@
                 mTestNotificationChannel, 2, "group", false);
 
         mBinderService.enqueueNotificationWithTag(PKG, PKG, "testPostNonGroup_noUnsnoozing",
-                child.sbn.getId(), child.sbn.getNotification(), child.sbn.getUserId());
+                child.getSbn().getId(), child.getSbn().getNotification(),
+                child.getSbn().getUserId());
         waitForIdle();
 
         verify(mSnoozeHelper, times(1)).repostGroupSummary(
@@ -2541,7 +2558,8 @@
                 mTestNotificationChannel, 2, null, false);
 
         mBinderService.enqueueNotificationWithTag(PKG, PKG, "testPostNonGroup_noUnsnoozing",
-                record.sbn.getId(), record.sbn.getNotification(), record.sbn.getUserId());
+                record.getSbn().getId(), record.getSbn().getNotification(),
+                record.getSbn().getUserId());
         waitForIdle();
 
         verify(mSnoozeHelper, never()).repostGroupSummary(anyString(), anyInt(), anyString());
@@ -2553,7 +2571,8 @@
                 mTestNotificationChannel, 2, "group", true);
 
         mBinderService.enqueueNotificationWithTag(PKG, PKG, "testPostGroupSummary_noUnsnoozing",
-                parent.sbn.getId(), parent.sbn.getNotification(), parent.sbn.getUserId());
+                parent.getSbn().getId(), parent.getSbn().getNotification(),
+                parent.getSbn().getUserId());
         waitForIdle();
 
         verify(mSnoozeHelper, never()).repostGroupSummary(anyString(), anyInt(), anyString());
@@ -2972,11 +2991,11 @@
         NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
         mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
-                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+                nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
         waitForIdle();
 
         NotificationRecord posted = mService.findNotificationLocked(
-                PKG, nr.sbn.getTag(), nr.sbn.getId(), nr.sbn.getUserId());
+                PKG, nr.getSbn().getTag(), nr.getSbn().getId(), nr.getSbn().getUserId());
 
         assertFalse(posted.getNotification().isColorized());
     }
@@ -2995,7 +3014,7 @@
             NotificationRecord r =
                     generateNotificationRecord(mTestNotificationChannel, i, null, false);
             mService.addNotification(r);
-            sampleTagToExclude = r.sbn.getTag();
+            sampleTagToExclude = r.getSbn().getTag();
             sampleIdToExclude = i;
         }
 
@@ -3231,7 +3250,8 @@
 
         StatusBarNotification sbn = new StatusBarNotification(preOPkg, preOPkg, 9,
                 "testBumpFGImportance_noChannelChangePreOApp",
-                Binder.getCallingUid(), 0, nb.build(), new UserHandle(Binder.getCallingUid()), null, 0);
+                Binder.getCallingUid(), 0, nb.build(), new UserHandle(Binder.getCallingUid()), null,
+                0);
 
         mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), sbn.getOpPkg(),
                 sbn.getTag(), sbn.getId(), sbn.getNotification(), sbn.getUserId());
@@ -3269,7 +3289,7 @@
 
         mService.mNotificationDelegate.onNotificationDirectReplied(r.getKey());
         assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasDirectReplied());
-        verify(mAssistants).notifyAssistantNotificationDirectReplyLocked(eq(r.sbn));
+        verify(mAssistants).notifyAssistantNotificationDirectReplyLocked(eq(r.getSbn()));
     }
 
     @Test
@@ -3279,12 +3299,14 @@
 
         mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), true, true,
                 NOTIFICATION_LOCATION_UNKNOWN);
-        verify(mAssistants).notifyAssistantExpansionChangedLocked(eq(r.sbn), eq(true), eq((true)));
+        verify(mAssistants).notifyAssistantExpansionChangedLocked(eq(r.getSbn()), eq(true),
+                eq((true)));
         assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasExpanded());
 
         mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), true, false,
                 NOTIFICATION_LOCATION_UNKNOWN);
-        verify(mAssistants).notifyAssistantExpansionChangedLocked(eq(r.sbn), eq(true), eq((false)));
+        verify(mAssistants).notifyAssistantExpansionChangedLocked(eq(r.getSbn()), eq(true),
+                eq((false)));
         assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasExpanded());
     }
 
@@ -3296,13 +3318,14 @@
         mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, true,
                 NOTIFICATION_LOCATION_UNKNOWN);
         assertFalse(mService.getNotificationRecord(r.getKey()).getStats().hasExpanded());
-        verify(mAssistants).notifyAssistantExpansionChangedLocked(eq(r.sbn), eq(false), eq((true)));
+        verify(mAssistants).notifyAssistantExpansionChangedLocked(eq(r.getSbn()), eq(false),
+                eq((true)));
 
         mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, false,
                 NOTIFICATION_LOCATION_UNKNOWN);
         assertFalse(mService.getNotificationRecord(r.getKey()).getStats().hasExpanded());
         verify(mAssistants).notifyAssistantExpansionChangedLocked(
-                eq(r.sbn), eq(false), eq((false)));
+                eq(r.getSbn()), eq(false), eq((false)));
     }
 
     @Test
@@ -3322,11 +3345,11 @@
         final NotificationVisibility nv = NotificationVisibility.obtain(r.getKey(), 1, 2, true);
         mService.mNotificationDelegate.onNotificationVisibilityChanged(
                 new NotificationVisibility[] {nv}, new NotificationVisibility[]{});
-        verify(mAssistants).notifyAssistantVisibilityChangedLocked(eq(r.sbn), eq(true));
+        verify(mAssistants).notifyAssistantVisibilityChangedLocked(eq(r.getSbn()), eq(true));
         assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasSeen());
         mService.mNotificationDelegate.onNotificationVisibilityChanged(
                 new NotificationVisibility[] {}, new NotificationVisibility[]{nv});
-        verify(mAssistants).notifyAssistantVisibilityChangedLocked(eq(r.sbn), eq(false));
+        verify(mAssistants).notifyAssistantVisibilityChangedLocked(eq(r.getSbn()), eq(false));
         assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasSeen());
     }
 
@@ -3336,8 +3359,8 @@
         mService.addNotification(r);
 
         final NotificationVisibility nv = NotificationVisibility.obtain(r.getKey(), 0, 1, true);
-        mService.mNotificationDelegate.onNotificationClear(mUid, 0, PKG, r.sbn.getTag(),
-                r.sbn.getId(), r.getUserId(), r.getKey(), NotificationStats.DISMISSAL_AOD,
+        mService.mNotificationDelegate.onNotificationClear(mUid, 0, PKG, r.getSbn().getTag(),
+                r.getSbn().getId(), r.getUserId(), r.getKey(), NotificationStats.DISMISSAL_AOD,
                 NotificationStats.DISMISS_SENTIMENT_POSITIVE, nv);
         waitForIdle();
 
@@ -3350,8 +3373,8 @@
         mService.addNotification(r);
 
         final NotificationVisibility nv = NotificationVisibility.obtain(r.getKey(), 0, 1, true);
-        mService.mNotificationDelegate.onNotificationClear(mUid, 0, PKG, r.sbn.getTag(),
-                r.sbn.getId(), r.getUserId(), r.getKey(), NotificationStats.DISMISSAL_AOD,
+        mService.mNotificationDelegate.onNotificationClear(mUid, 0, PKG, r.getSbn().getTag(),
+                r.getSbn().getId(), r.getUserId(), r.getKey(), NotificationStats.DISMISSAL_AOD,
                 NotificationStats.DISMISS_SENTIMENT_NEGATIVE, nv);
         waitForIdle();
 
@@ -3373,7 +3396,7 @@
         signals.putInt(Adjustment.KEY_USER_SENTIMENT,
                 USER_SENTIMENT_NEGATIVE);
         Adjustment adjustment = new Adjustment(
-                r.sbn.getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier());
+                r.getSbn().getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier());
         mBinderService.applyAdjustmentFromAssistant(null, adjustment);
 
         waitForIdle();
@@ -3392,7 +3415,7 @@
         Bundle signals = new Bundle();
         signals.putInt(KEY_IMPORTANCE, IMPORTANCE_NONE);
         Adjustment adjustment = new Adjustment(
-                r.sbn.getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier());
+                r.getSbn().getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier());
         when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
         mBinderService.applyAdjustmentFromAssistant(null, adjustment);
 
@@ -3415,7 +3438,7 @@
         signals.putInt(Adjustment.KEY_USER_SENTIMENT,
                 USER_SENTIMENT_NEGATIVE);
         Adjustment adjustment = new Adjustment(
-                r.sbn.getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier());
+                r.getSbn().getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier());
         mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment);
 
         assertEquals(USER_SENTIMENT_NEGATIVE, r.getUserSentiment());
@@ -3433,7 +3456,7 @@
         Bundle signals = new Bundle();
         signals.putInt(KEY_IMPORTANCE, IMPORTANCE_LOW);
         Adjustment adjustment = new Adjustment(
-                r.sbn.getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier());
+                r.getSbn().getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier());
         mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment);
 
         assertEquals(IMPORTANCE_LOW, r.getImportance());
@@ -3452,7 +3475,7 @@
         signals.putInt(Adjustment.KEY_USER_SENTIMENT,
                 USER_SENTIMENT_NEGATIVE);
         Adjustment adjustment = new Adjustment(
-                r.sbn.getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier());
+                r.getSbn().getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier());
         mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment);
 
         assertEquals(USER_SENTIMENT_NEUTRAL, r.getUserSentiment());
@@ -3472,7 +3495,7 @@
         signals.putInt(Adjustment.KEY_USER_SENTIMENT,
                 USER_SENTIMENT_NEGATIVE);
         Adjustment adjustment = new Adjustment(
-                r.sbn.getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier());
+                r.getSbn().getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier());
         mBinderService.applyAdjustmentFromAssistant(null, adjustment);
 
         waitForIdle();
@@ -3490,7 +3513,7 @@
         signals.putInt(Adjustment.KEY_USER_SENTIMENT,
                 USER_SENTIMENT_NEGATIVE);
         Adjustment adjustment = new Adjustment(
-                r.sbn.getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier());
+                r.getSbn().getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier());
         mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment);
 
         waitForIdle();
@@ -3508,7 +3531,7 @@
         signals.putInt(Adjustment.KEY_USER_SENTIMENT,
                 USER_SENTIMENT_NEGATIVE);
         Adjustment adjustment = new Adjustment(
-                r.sbn.getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier());
+                r.getSbn().getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier());
         mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment);
 
         assertEquals(USER_SENTIMENT_NEGATIVE, r.getUserSentiment());
@@ -3622,6 +3645,33 @@
     }
 
     @Test
+    public void updateUriPermissions_posterDoesNotOwnUri() throws Exception {
+        NotificationChannel c = new NotificationChannel(
+                TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT);
+        c.setSound(null, Notification.AUDIO_ATTRIBUTES_DEFAULT);
+        Message message1 = new Message("", 0, "");
+        message1.setData("",
+                ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 1));
+
+        Notification.Builder nbA = new Notification.Builder(mContext, c.getId())
+                .setContentTitle("foo")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setStyle(new Notification.MessagingStyle("")
+                        .addMessage(message1));
+        NotificationRecord recordA = new NotificationRecord(mContext, new StatusBarNotification(
+                PKG, PKG, 0, "tag", mUid, 0, nbA.build(), new UserHandle(mUid), null, 0), c);
+
+        doThrow(new SecurityException("no access")).when(mUgm)
+                .grantUriPermissionFromOwner(
+                        any(), anyInt(), any(), any(), anyInt(), anyInt(), anyInt());
+
+        when(mUgmInternal.newUriPermissionOwner(any())).thenReturn(new Binder());
+        mService.updateUriPermissions(recordA, null, mContext.getPackageName(),  USER_SYSTEM);
+
+        // yay, no crash
+    }
+
+    @Test
     public void testVisitUris() throws Exception {
         final Uri audioContents = Uri.parse("content://com.example/audio");
         final Uri backgroundImage = Uri.parse("content://com.example/background");
@@ -4078,7 +4128,7 @@
         ArgumentCaptor<List<NotificationRecord>> captorHide = ArgumentCaptor.forClass(List.class);
         verify(mListeners, times(1)).notifyHiddenLocked(captorHide.capture());
         assertEquals(1, captorHide.getValue().size());
-        assertEquals("a", captorHide.getValue().get(0).sbn.getPackageName());
+        assertEquals("a", captorHide.getValue().get(0).getSbn().getPackageName());
 
         // on broadcast, unhide the package
         mService.simulatePackageDistractionBroadcast(
@@ -4086,7 +4136,7 @@
         ArgumentCaptor<List<NotificationRecord>> captorUnhide = ArgumentCaptor.forClass(List.class);
         verify(mListeners, times(1)).notifyUnhiddenLocked(captorUnhide.capture());
         assertEquals(1, captorUnhide.getValue().size());
-        assertEquals("a", captorUnhide.getValue().get(0).sbn.getPackageName());
+        assertEquals("a", captorUnhide.getValue().get(0).getSbn().getPackageName());
     }
 
     @Test
@@ -4107,8 +4157,8 @@
         // should be called only once.
         verify(mListeners, times(1)).notifyHiddenLocked(captorHide.capture());
         assertEquals(2, captorHide.getValue().size());
-        assertEquals("a", captorHide.getValue().get(0).sbn.getPackageName());
-        assertEquals("b", captorHide.getValue().get(1).sbn.getPackageName());
+        assertEquals("a", captorHide.getValue().get(0).getSbn().getPackageName());
+        assertEquals("b", captorHide.getValue().get(1).getSbn().getPackageName());
 
         // on broadcast, unhide the package
         mService.simulatePackageDistractionBroadcast(
@@ -4118,8 +4168,8 @@
         // should be called only once.
         verify(mListeners, times(1)).notifyUnhiddenLocked(captorUnhide.capture());
         assertEquals(2, captorUnhide.getValue().size());
-        assertEquals("a", captorUnhide.getValue().get(0).sbn.getPackageName());
-        assertEquals("b", captorUnhide.getValue().get(1).sbn.getPackageName());
+        assertEquals("a", captorUnhide.getValue().get(0).getSbn().getPackageName());
+        assertEquals("b", captorUnhide.getValue().get(1).getSbn().getPackageName());
     }
 
     @Test
@@ -4405,7 +4455,7 @@
         ai.uid = -1;
         when(mPackageManager.getApplicationInfo(anyString(), anyInt(), anyInt())).thenReturn(ai);
 
-        final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
+        final StatusBarNotification sbn = generateNotificationRecord(null).getSbn();
         try {
             mInternalService.enqueueNotification(notReal, "android", 0, 0,
                     "testPostFromAndroidForNonExistentPackage",
@@ -4426,7 +4476,7 @@
         when(mPackageManager.getApplicationInfo(anyString(), anyInt(), anyInt())).thenReturn(ai);
 
         // unlike the post case, ignore instead of throwing
-        final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
+        final StatusBarNotification sbn = generateNotificationRecord(null).getSbn();
 
         mInternalService.cancelNotification(notReal, "android", 0, 0, "tag",
                 sbn.getId(), sbn.getUserId());
@@ -4457,7 +4507,7 @@
         mService.addEnqueuedNotification(r);
 
         mInternalService.removeForegroundServiceFlagFromNotification(
-                PKG, r.sbn.getId(), r.sbn.getUserId());
+                PKG, r.getSbn().getId(), r.getSbn().getUserId());
 
         waitForIdle();
 
@@ -4476,7 +4526,7 @@
         mService.addNotification(r);
 
         mInternalService.removeForegroundServiceFlagFromNotification(
-                PKG, r.sbn.getId(), r.sbn.getUserId());
+                PKG, r.getSbn().getId(), r.getSbn().getUserId());
 
         waitForIdle();
 
@@ -4674,7 +4724,7 @@
                 r.getKey(), replyIndex, reply, NOTIFICATION_LOCATION_UNKNOWN,
                 modifiedBeforeSending);
         verify(mAssistants).notifyAssistantSuggestedReplySent(
-                eq(r.sbn), eq(reply), eq(generatedByAssistant));
+                eq(r.getSbn()), eq(reply), eq(generatedByAssistant));
     }
 
     @Test
@@ -4693,7 +4743,7 @@
                 10, 10, r.getKey(), actionIndex, action, notificationVisibility,
                 generatedByAssistant);
         verify(mAssistants).notifyAssistantActionClicked(
-                eq(r.sbn), eq(actionIndex), eq(action), eq(generatedByAssistant));
+                eq(r.getSbn()), eq(actionIndex), eq(action), eq(generatedByAssistant));
     }
 
     @Test
@@ -4766,13 +4816,13 @@
         NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
         mService.addNotification(r);
 
-        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, r.sbn.getId(),
-                r.sbn.getTag(), mUid, 0,
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, r.getSbn().getId(),
+                r.getSbn().getTag(), mUid, 0,
                 new Notification.Builder(mContext, mTestNotificationChannel.getId()).build(),
                 new UserHandle(mUid), null, 0);
         NotificationRecord update = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
         mService.addEnqueuedNotification(update);
-        assertNull(update.sbn.getNotification().getSmallIcon());
+        assertNull(update.getSbn().getNotification().getSmallIcon());
 
         NotificationManagerService.PostNotificationRunnable runnable =
                 mService.new PostNotificationRunnable(update.getKey());
@@ -4944,15 +4994,15 @@
         NotificationRecord nr =
                 generateMessageBubbleNotifRecord(mTestNotificationChannel, "testFlagBubble");
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
-                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+                nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
         waitForIdle();
 
         StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG);
         assertEquals(1, notifs.length);
         assertTrue((notifs[0].getNotification().flags & FLAG_BUBBLE) != 0);
         assertTrue(mService.getNotificationRecord(
-                nr.sbn.getKey()).getNotification().isBubbleNotification());
+                nr.getSbn().getKey()).getNotification().isBubbleNotification());
     }
 
     @Test
@@ -4963,15 +5013,15 @@
         NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
                         "testFlagBubble_noFlag_appNotAllowed");
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
-                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+                nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
         waitForIdle();
 
         StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG);
         assertEquals(1, notifs.length);
         assertEquals((notifs[0].getNotification().flags & FLAG_BUBBLE), 0);
         assertFalse(mService.getNotificationRecord(
-                nr.sbn.getKey()).getNotification().isBubbleNotification());
+                nr.getSbn().getKey()).getNotification().isBubbleNotification());
     }
 
     @Test
@@ -4990,16 +5040,16 @@
         NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
         // Say we're foreground
-        when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
+        when(mActivityManager.getPackageImportance(nr.getSbn().getPackageName())).thenReturn(
                 IMPORTANCE_FOREGROUND);
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
-                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+                nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
         waitForIdle();
 
         // if notif isn't configured properly it doesn't get to bubble just because app is
         // foreground.
         assertFalse(mService.getNotificationRecord(
-                nr.sbn.getKey()).getNotification().isBubbleNotification());
+                nr.getSbn().getKey()).getNotification().isBubbleNotification());
     }
 
     @Test
@@ -5010,13 +5060,13 @@
         NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
                 "testFlagBubbleNotifs_flag_messaging");
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
-                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+                nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
         waitForIdle();
 
         // yes allowed, yes messaging, yes bubble
         assertTrue(mService.getNotificationRecord(
-                nr.sbn.getKey()).getNotification().isBubbleNotification());
+                nr.getSbn().getKey()).getNotification().isBubbleNotification());
     }
 
     @Test
@@ -5046,8 +5096,8 @@
         sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
         NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
-                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+                nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
         waitForIdle();
 
         // yes phone call, yes person, yes foreground service, yes bubble
@@ -5079,8 +5129,8 @@
                 nb.build(), new UserHandle(mUid), null, 0);
         NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
-                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+                nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
         waitForIdle();
 
         // yes phone call, yes person, NO foreground service, no bubble
@@ -5110,8 +5160,8 @@
         sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
         NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
-                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+                nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
         waitForIdle();
 
         // yes phone call, yes foreground service, BUT NO person, no bubble
@@ -5145,8 +5195,8 @@
         sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
         NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
-                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+                nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
         waitForIdle();
 
         // yes person, yes foreground service, BUT NO call, no bubble
@@ -5163,13 +5213,13 @@
                 "testFlagBubbleNotifs_noFlag_messaging_appNotAllowed");
 
         // Post the notification
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
-                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+                nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
         waitForIdle();
 
         // not allowed, no bubble
         assertFalse(mService.getNotificationRecord(
-                nr.sbn.getKey()).getNotification().isBubbleNotification());
+                nr.getSbn().getKey()).getNotification().isBubbleNotification());
     }
 
     @Test
@@ -5187,13 +5237,13 @@
         NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
         // Post the notification
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
-                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+                nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
         waitForIdle();
 
         // no bubble metadata, no bubble
         assertFalse(mService.getNotificationRecord(
-                nr.sbn.getKey()).getNotification().isBubbleNotification());
+                nr.getSbn().getKey()).getNotification().isBubbleNotification());
     }
 
     @Test
@@ -5205,13 +5255,13 @@
                 "testFlagBubbleNotifs_noFlag_messaging_channelNotAllowed");
 
         // Post the notification
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
-                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+                nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
         waitForIdle();
 
         // channel not allowed, no bubble
         assertFalse(mService.getNotificationRecord(
-                nr.sbn.getKey()).getNotification().isBubbleNotification());
+                nr.getSbn().getKey()).getNotification().isBubbleNotification());
     }
 
     @Test
@@ -5277,7 +5327,7 @@
         NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
         mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
-                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+                nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
         waitForIdle();
 
         // yes phone call, yes person, yes foreground service, but channel not allowed, no bubble
@@ -5288,10 +5338,10 @@
     @Test
     public void testCancelAllNotifications_cancelsBubble() throws Exception {
         final NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel);
-        nr.sbn.getNotification().flags |= FLAG_BUBBLE;
+        nr.getSbn().getNotification().flags |= FLAG_BUBBLE;
         mService.addNotification(nr);
 
-        mBinderService.cancelAllNotifications(PKG, nr.sbn.getUserId());
+        mBinderService.cancelAllNotifications(PKG, nr.getSbn().getUserId());
         waitForIdle();
 
         StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG);
@@ -5302,12 +5352,13 @@
     @Test
     public void testAppCancelNotifications_cancelsBubbles() throws Exception {
         final NotificationRecord nrBubble = generateNotificationRecord(mTestNotificationChannel);
-        nrBubble.sbn.getNotification().flags |= FLAG_BUBBLE;
+        nrBubble.getSbn().getNotification().flags |= FLAG_BUBBLE;
 
         // Post the notification
         mBinderService.enqueueNotificationWithTag(PKG, PKG,
                 "testAppCancelNotifications_cancelsBubbles",
-                nrBubble.sbn.getId(), nrBubble.sbn.getNotification(), nrBubble.sbn.getUserId());
+                nrBubble.getSbn().getId(), nrBubble.getSbn().getNotification(),
+                nrBubble.getSbn().getUserId());
         waitForIdle();
 
         StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG);
@@ -5315,8 +5366,8 @@
         assertEquals(1, mService.getNotificationRecordCount());
 
         mBinderService.cancelNotificationWithTag(PKG, PKG,
-                "testAppCancelNotifications_cancelsBubbles", nrBubble.sbn.getId(),
-                nrBubble.sbn.getUserId());
+                "testAppCancelNotifications_cancelsBubbles", nrBubble.getSbn().getId(),
+                nrBubble.getSbn().getUserId());
         waitForIdle();
 
         StatusBarNotification[] notifs2 = mBinderService.getActiveNotifications(PKG);
@@ -5328,7 +5379,7 @@
     public void testCancelAllNotificationsFromListener_ignoresBubbles() throws Exception {
         final NotificationRecord nrNormal = generateNotificationRecord(mTestNotificationChannel);
         final NotificationRecord nrBubble = generateNotificationRecord(mTestNotificationChannel);
-        nrBubble.sbn.getNotification().flags |= FLAG_BUBBLE;
+        nrBubble.getSbn().getNotification().flags |= FLAG_BUBBLE;
 
         mService.addNotification(nrNormal);
         mService.addNotification(nrBubble);
@@ -5345,12 +5396,12 @@
     public void testCancelNotificationsFromListener_ignoresBubbles() throws Exception {
         final NotificationRecord nrNormal = generateNotificationRecord(mTestNotificationChannel);
         final NotificationRecord nrBubble = generateNotificationRecord(mTestNotificationChannel);
-        nrBubble.sbn.getNotification().flags |= FLAG_BUBBLE;
+        nrBubble.getSbn().getNotification().flags |= FLAG_BUBBLE;
 
         mService.addNotification(nrNormal);
         mService.addNotification(nrBubble);
 
-        String[] keys = {nrNormal.sbn.getKey(), nrBubble.sbn.getKey()};
+        String[] keys = {nrNormal.getSbn().getKey(), nrBubble.getSbn().getKey()};
         mService.getBinderService().cancelNotificationsFromListener(null, keys);
         waitForIdle();
 
@@ -5386,7 +5437,7 @@
         Bundle signals = new Bundle();
         signals.putInt(KEY_IMPORTANCE, IMPORTANCE_LOW);
         signals.putInt(KEY_USER_SENTIMENT, USER_SENTIMENT_NEGATIVE);
-        Adjustment adjustment = new Adjustment(r.sbn.getPackageName(), r.getKey(), signals,
+        Adjustment adjustment = new Adjustment(r.getSbn().getPackageName(), r.getKey(), signals,
                "", r.getUser().getIdentifier());
 
         mBinderService.applyAdjustmentFromAssistant(null, adjustment);
@@ -5469,8 +5520,8 @@
         NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
                 "testNotificationBubbleChanged_false");
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
-                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+                nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
         waitForIdle();
 
         // Reset as this is called when the notif is first sent
@@ -5499,8 +5550,8 @@
         // Notif that is not a bubble
         NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
                 1, null, false);
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
-                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+                nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
         waitForIdle();
 
         // Would be a normal notification because wouldn't have met requirements to bubble
@@ -5510,9 +5561,9 @@
 
         // Update the notification to be message style / meet bubble requirements
         NotificationRecord nr2 = generateMessageBubbleNotifRecord(mTestNotificationChannel,
-                nr.sbn.getTag());
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr2.sbn.getTag(),
-                nr2.sbn.getId(), nr2.sbn.getNotification(), nr2.sbn.getUserId());
+                nr.getSbn().getTag());
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr2.getSbn().getTag(),
+                nr2.getSbn().getId(), nr2.getSbn().getNotification(), nr2.getSbn().getUserId());
         waitForIdle();
 
         // Reset as this is called when the notif is first sent
@@ -5535,8 +5586,8 @@
 
         // Notif that is not a bubble
         NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel);
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
-                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+                nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
         waitForIdle();
 
         // Reset as this is called when the notif is first sent
@@ -5563,11 +5614,11 @@
         NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel, "tag");
 
         // Bubbles are allowed!
-        setUpPrefsForBubbles(PKG, nr.sbn.getUserId(), true /* global */,
+        setUpPrefsForBubbles(PKG, nr.getSbn().getUserId(), true /* global */,
                 true /* app */, true /* channel */);
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
-                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+                nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
         waitForIdle();
 
         // NOT suppressed
@@ -5601,7 +5652,7 @@
     public void testGrantInlineReplyUriPermission_recordExists() throws Exception {
         NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel, 0);
         mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
-                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+                nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
         waitForIdle();
 
         // A notification exists for the given record
@@ -5613,12 +5664,13 @@
         Uri uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 1);
 
         mService.mNotificationDelegate.grantInlineReplyUriPermission(
-                nr.getKey(), uri, nr.sbn.getUser(), nr.sbn.getPackageName(), nr.sbn.getUid());
+                nr.getKey(), uri, nr.getSbn().getUser(), nr.getSbn().getPackageName(),
+                nr.getSbn().getUid());
 
         // Grant permission called for the UID of SystemUI under the target user ID
         verify(mUgm, times(1)).grantUriPermissionFromOwner(any(),
-                eq(nr.sbn.getUid()), eq(nr.sbn.getPackageName()), eq(uri), anyInt(), anyInt(),
-                eq(nr.sbn.getUserId()));
+                eq(nr.getSbn().getUid()), eq(nr.getSbn().getPackageName()), eq(uri), anyInt(),
+                anyInt(), eq(nr.getSbn().getUserId()));
     }
 
     @Test
@@ -5634,12 +5686,13 @@
         int uid = 0; // sysui on primary user
 
         mService.mNotificationDelegate.grantInlineReplyUriPermission(
-                nr.getKey(), uri, nr.sbn.getUser(), nr.sbn.getPackageName(), nr.sbn.getUid());
+                nr.getKey(), uri, nr.getSbn().getUser(), nr.getSbn().getPackageName(),
+                nr.getSbn().getUid());
 
         // Grant permission still called if no NotificationRecord exists for the given key
         verify(mUgm, times(1)).grantUriPermissionFromOwner(any(),
-                eq(nr.sbn.getUid()), eq(nr.sbn.getPackageName()), eq(uri), anyInt(), anyInt(),
-                eq(nr.sbn.getUserId()));
+                eq(nr.getSbn().getUid()), eq(nr.getSbn().getPackageName()), eq(uri), anyInt(),
+                anyInt(), eq(nr.getSbn().getUserId()));
     }
 
     @Test
@@ -5648,7 +5701,7 @@
         NotificationRecord nr =
                 generateNotificationRecord(mTestNotificationChannel, UserHandle.USER_ALL);
         mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
-                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+                nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
         waitForIdle();
 
         // A notification exists for the given record
@@ -5660,12 +5713,13 @@
         Uri uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 1);
 
         mService.mNotificationDelegate.grantInlineReplyUriPermission(
-                nr.getKey(), uri, nr.sbn.getUser(), nr.sbn.getPackageName(), nr.sbn.getUid());
+                nr.getKey(), uri, nr.getSbn().getUser(), nr.getSbn().getPackageName(),
+                nr.getSbn().getUid());
 
         // Target user for the grant is USER_ALL instead of USER_SYSTEM
         verify(mUgm, times(1)).grantUriPermissionFromOwner(any(),
-                eq(nr.sbn.getUid()), eq(nr.sbn.getPackageName()), eq(uri), anyInt(), anyInt(),
-                eq(UserHandle.USER_SYSTEM));
+                eq(nr.getSbn().getUid()), eq(nr.getSbn().getPackageName()), eq(uri), anyInt(),
+                anyInt(), eq(UserHandle.USER_SYSTEM));
     }
 
     @Test
@@ -5675,7 +5729,7 @@
         NotificationRecord nr =
                 generateNotificationRecord(mTestNotificationChannel, otherUserId);
         mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
-                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+                nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
         waitForIdle();
 
         // A notification exists for the given record
@@ -5698,11 +5752,11 @@
                 .thenReturn(otherUserUid);
 
         mService.mNotificationDelegate.grantInlineReplyUriPermission(
-                nr.getKey(), uri, nr.sbn.getUser(), nr.sbn.getPackageName(), uid);
+                nr.getKey(), uri, nr.getSbn().getUser(), nr.getSbn().getPackageName(), uid);
 
         // Target user for the grant is USER_ALL instead of USER_SYSTEM
         verify(mUgm, times(1)).grantUriPermissionFromOwner(any(),
-                eq(otherUserUid), eq(nr.sbn.getPackageName()), eq(uri), anyInt(), anyInt(),
+                eq(otherUserUid), eq(nr.getSbn().getPackageName()), eq(uri), anyInt(), anyInt(),
                 eq(otherUserId));
     }
 
@@ -5716,15 +5770,18 @@
 
         // create an inline record with two uris in it
         mService.mNotificationDelegate.grantInlineReplyUriPermission(
-                nr.getKey(), uri1, nr.sbn.getUser(), nr.sbn.getPackageName(), nr.sbn.getUid());
+                nr.getKey(), uri1, nr.getSbn().getUser(), nr.getSbn().getPackageName(),
+                nr.getSbn().getUid());
         mService.mNotificationDelegate.grantInlineReplyUriPermission(
-                nr.getKey(), uri2, nr.sbn.getUser(), nr.sbn.getPackageName(), nr.sbn.getUid());
+                nr.getKey(), uri2, nr.getSbn().getUser(), nr.getSbn().getPackageName(),
+                nr.getSbn().getUid());
 
         InlineReplyUriRecord record = mService.mInlineReplyRecordsByKey.get(nr.getKey());
         assertNotNull(record); // record exists
         assertEquals(record.getUris().size(), 2); // record has two uris in it
 
-        mService.mNotificationDelegate.clearInlineReplyUriPermissions(nr.getKey(), nr.sbn.getUid());
+        mService.mNotificationDelegate.clearInlineReplyUriPermissions(nr.getKey(),
+                nr.getSbn().getUid());
 
         // permissionOwner destroyed
         verify(mUgmInternal, times(1)).revokeUriPermissionFromOwner(
@@ -5737,7 +5794,8 @@
         NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel, 0);
         reset(mPackageManager);
 
-        mService.mNotificationDelegate.clearInlineReplyUriPermissions(nr.getKey(), nr.sbn.getUid());
+        mService.mNotificationDelegate.clearInlineReplyUriPermissions(nr.getKey(),
+                nr.getSbn().getUid());
 
         // no permissionOwner destroyed
         verify(mUgmInternal, times(0)).revokeUriPermissionFromOwner(
@@ -5755,12 +5813,14 @@
 
         // create an inline record a uri in it
         mService.mNotificationDelegate.grantInlineReplyUriPermission(
-                nr.getKey(), uri1, nr.sbn.getUser(), nr.sbn.getPackageName(), nr.sbn.getUid());
+                nr.getKey(), uri1, nr.getSbn().getUser(), nr.getSbn().getPackageName(),
+                nr.getSbn().getUid());
 
         InlineReplyUriRecord record = mService.mInlineReplyRecordsByKey.get(nr.getKey());
         assertNotNull(record); // record exists
 
-        mService.mNotificationDelegate.clearInlineReplyUriPermissions(nr.getKey(), nr.sbn.getUid());
+        mService.mNotificationDelegate.clearInlineReplyUriPermissions(
+                nr.getKey(), nr.getSbn().getUid());
 
         // permissionOwner destroyed for USER_SYSTEM, not USER_ALL
         verify(mUgmInternal, times(1)).revokeUriPermissionFromOwner(
@@ -5778,8 +5838,8 @@
         // Notification that would typically bubble
         NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
                 "testNotificationBubbles_disabled_lowRamDevice");
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
-                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+                nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
         waitForIdle();
 
         // But we wouldn't be a bubble because the device is low ram & all bubbles are disabled.
@@ -5859,20 +5919,20 @@
         NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
                 "testNotificationBubbles_flagAutoExpandForeground_fails_notForeground");
         // Modify metadata flags
-        nr.sbn.getNotification().getBubbleMetadata().setFlags(
+        nr.getSbn().getNotification().getBubbleMetadata().setFlags(
                 Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE
                         | Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION);
 
         // Ensure we're not foreground
-        when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
+        when(mActivityManager.getPackageImportance(nr.getSbn().getPackageName())).thenReturn(
                 IMPORTANCE_VISIBLE);
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
-                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+                nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
         waitForIdle();
 
         // yes allowed, yes messaging, yes bubble
-        Notification notif = mService.getNotificationRecord(nr.sbn.getKey()).getNotification();
+        Notification notif = mService.getNotificationRecord(nr.getSbn().getKey()).getNotification();
         assertTrue(notif.isBubbleNotification());
 
         // Our flags should have failed since we're not foreground
@@ -5889,20 +5949,20 @@
         NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
                 "testNotificationBubbles_flagAutoExpandForeground_succeeds_foreground");
         // Modify metadata flags
-        nr.sbn.getNotification().getBubbleMetadata().setFlags(
+        nr.getSbn().getNotification().getBubbleMetadata().setFlags(
                 Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE
                         | Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION);
 
         // Ensure we are in the foreground
-        when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
+        when(mActivityManager.getPackageImportance(nr.getSbn().getPackageName())).thenReturn(
                 IMPORTANCE_FOREGROUND);
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
-                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+                nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
         waitForIdle();
 
         // yes allowed, yes messaging, yes bubble
-        Notification notif = mService.getNotificationRecord(nr.sbn.getKey()).getNotification();
+        Notification notif = mService.getNotificationRecord(nr.getSbn().getKey()).getNotification();
         assertTrue(notif.isBubbleNotification());
 
         // Our flags should have passed since we are foreground
@@ -5935,8 +5995,8 @@
         when(mLauncherApps.getShortcuts(any(), any())).thenReturn(shortcutInfos);
 
         // Test: Send the bubble notification
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
-                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+                nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
         waitForIdle();
 
         // Verify:
@@ -5945,7 +6005,7 @@
         verify(mLauncherApps, times(1)).registerCallback(launcherAppsCallback.capture(), any());
 
         // yes allowed, yes messaging w/shortcut, yes bubble
-        Notification notif = mService.getNotificationRecord(nr.sbn.getKey()).getNotification();
+        Notification notif = mService.getNotificationRecord(nr.getSbn().getKey()).getNotification();
         assertTrue(notif.isBubbleNotification());
 
         // Test: Remove the shortcut
@@ -5958,7 +6018,7 @@
         verify(mLauncherApps, times(1)).unregisterCallback(launcherAppsCallback.getValue());
 
         // We're no longer a bubble
-        Notification notif2 = mService.getNotificationRecord(nr.sbn.getKey()).getNotification();
+        Notification notif2 = mService.getNotificationRecord(nr.getSbn().getKey()).getNotification();
         assertFalse(notif2.isBubbleNotification());
     }
 
@@ -5974,8 +6034,9 @@
         // Dismiss summary
         final NotificationVisibility nv = NotificationVisibility.obtain(nrSummary.getKey(), 1, 2,
                 true);
-        mService.mNotificationDelegate.onNotificationClear(mUid, 0, PKG, nrSummary.sbn.getTag(),
-                nrSummary.sbn.getId(), nrSummary.getUserId(), nrSummary.getKey(),
+        mService.mNotificationDelegate.onNotificationClear(mUid, 0, PKG,
+                nrSummary.getSbn().getTag(),
+                nrSummary.getSbn().getId(), nrSummary.getUserId(), nrSummary.getKey(),
                 NotificationStats.DISMISSAL_SHADE,
                 NotificationStats.DISMISS_SENTIMENT_NEUTRAL, nv);
         waitForIdle();
@@ -6085,8 +6146,8 @@
     public void testNotificationHistory_addNoisyNotification() throws Exception {
         NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
                 null /* tvExtender */);
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
-                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+                nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
         waitForIdle();
 
         verify(mHistoryManager, times(1)).addNotification(any());
@@ -6168,4 +6229,48 @@
         assertNull(mBinderService.getConversationNotificationChannel(
                 PKG, 0, PKG, callsParent.getId(), false, conversationId));
     }
+
+    @Test
+    public void testCorrectCategory_systemOn_appCannotTurnOff() {
+        int requested = 0;
+        int system = PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_CONVERSATIONS;
+
+        int actual = mService.correctCategory(requested, PRIORITY_CATEGORY_CONVERSATIONS,
+                system);
+
+        assertEquals(PRIORITY_CATEGORY_CONVERSATIONS, actual);
+    }
+
+    @Test
+    public void testCorrectCategory_systemOff_appTurnOff_noChanges() {
+        int requested = PRIORITY_CATEGORY_CALLS;
+        int system = PRIORITY_CATEGORY_CALLS;
+
+        int actual = mService.correctCategory(requested, PRIORITY_CATEGORY_CONVERSATIONS,
+                system);
+
+        assertEquals(PRIORITY_CATEGORY_CALLS, actual);
+    }
+
+    @Test
+    public void testCorrectCategory_systemOn_appTurnOn_noChanges() {
+        int requested = PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_CONVERSATIONS;
+        int system = PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_CONVERSATIONS;
+
+        int actual = mService.correctCategory(requested, PRIORITY_CATEGORY_CONVERSATIONS,
+                system);
+
+        assertEquals(PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_CONVERSATIONS, actual);
+    }
+
+    @Test
+    public void testCorrectCategory_systemOff_appCannotTurnOn() {
+        int requested = PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_CONVERSATIONS;
+        int system = PRIORITY_CATEGORY_CALLS;
+
+        int actual = mService.correctCategory(requested, PRIORITY_CATEGORY_CONVERSATIONS,
+                system);
+
+        assertEquals(PRIORITY_CATEGORY_CALLS, actual);
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index c1c74da..bb84b04 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -171,7 +171,7 @@
                 .thenReturn(SOUND_URI);
 
         mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
-                NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND);
+                NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0);
         when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper);
         resetZenModeHelper();
@@ -1430,7 +1430,7 @@
         // start notification policy off with mAreChannelsBypassingDnd = true, but
         // RankingHelper should change to false
         mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
-                NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND);
+                NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0);
         when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper);
         assertFalse(mHelper.areChannelsBypassingDnd());
@@ -1441,7 +1441,7 @@
     @Test
     public void testSetupNewZenModeHelper_cannotBypass() {
         // start notification policy off with mAreChannelsBypassingDnd = false
-        mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, 0);
+        mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, 0, 0);
         when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper);
         assertFalse(mHelper.areChannelsBypassingDnd());
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
index 8774b63..5018166 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
@@ -140,7 +140,7 @@
                 .thenReturn(SOUND_URI);
 
         mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
-                NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND);
+                NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0);
         when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
         mHelper = new RankingHelper(getContext(), mHandler, mConfig, mMockZenModeHelper,
                 mUsageStats, new String[] {ImportanceExtractor.class.getName()});
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
index 5841e59..3186d53 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
@@ -169,11 +169,11 @@
     public void testCleanupContextShouldRemovePersistedRecord() {
         NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
         mSnoozeHelper.snooze(r, "context");
-        mSnoozeHelper.cleanupPersistedContext(r.sbn.getKey());
+        mSnoozeHelper.cleanupPersistedContext(r.getSbn().getKey());
         assertNull(mSnoozeHelper.getSnoozeContextForUnpostedNotification(
                 r.getUser().getIdentifier(),
-                r.sbn.getPackageName(),
-                r.sbn.getKey()
+                r.getSbn().getPackageName(),
+                r.getSbn().getKey()
         ));
     }
 
@@ -201,7 +201,7 @@
         long actualSnoozedUntilDuration = captor.getValue() - SystemClock.elapsedRealtime();
         assertTrue(Math.abs(actualSnoozedUntilDuration - 1000) < 250);
         assertTrue(mSnoozeHelper.isSnoozed(
-                UserHandle.USER_SYSTEM, r.sbn.getPackageName(), r.getKey()));
+                UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey()));
     }
 
     @Test
@@ -211,7 +211,7 @@
         verify(mAm, never()).setExactAndAllowWhileIdle(
                 anyInt(), anyLong(), any(PendingIntent.class));
         assertTrue(mSnoozeHelper.isSnoozed(
-                UserHandle.USER_SYSTEM, r.sbn.getPackageName(), r.getKey()));
+                UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey()));
     }
 
     @Test
@@ -221,17 +221,17 @@
         mSnoozeHelper.snooze(r, 1000);
         mSnoozeHelper.snooze(r2 , 1000);
         assertTrue(mSnoozeHelper.isSnoozed(
-                UserHandle.USER_SYSTEM, r.sbn.getPackageName(), r.getKey()));
+                UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey()));
         assertTrue(mSnoozeHelper.isSnoozed(
-                UserHandle.USER_SYSTEM, r2.sbn.getPackageName(), r2.getKey()));
+                UserHandle.USER_SYSTEM, r2.getSbn().getPackageName(), r2.getKey()));
 
-        mSnoozeHelper.cancel(UserHandle.USER_SYSTEM, r.sbn.getPackageName(), "one", 1);
+        mSnoozeHelper.cancel(UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), "one", 1);
         // 2 = one for each snooze, above, zero for the cancel.
         verify(mAm, times(2)).cancel(any(PendingIntent.class));
         assertTrue(mSnoozeHelper.isSnoozed(
-                UserHandle.USER_SYSTEM, r.sbn.getPackageName(), r.getKey()));
+                UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey()));
         assertTrue(mSnoozeHelper.isSnoozed(
-                UserHandle.USER_SYSTEM, r2.sbn.getPackageName(), r2.getKey()));
+                UserHandle.USER_SYSTEM, r2.getSbn().getPackageName(), r2.getKey()));
     }
 
     @Test
@@ -243,21 +243,21 @@
         mSnoozeHelper.snooze(r2, 1000);
         mSnoozeHelper.snooze(r3, 1000);
         assertTrue(mSnoozeHelper.isSnoozed(
-                UserHandle.USER_SYSTEM, r.sbn.getPackageName(), r.getKey()));
+                UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey()));
         assertTrue(mSnoozeHelper.isSnoozed(
-                UserHandle.USER_SYSTEM, r2.sbn.getPackageName(), r2.getKey()));
+                UserHandle.USER_SYSTEM, r2.getSbn().getPackageName(), r2.getKey()));
         assertTrue(mSnoozeHelper.isSnoozed(
-                UserHandle.USER_ALL, r3.sbn.getPackageName(), r3.getKey()));
+                UserHandle.USER_ALL, r3.getSbn().getPackageName(), r3.getKey()));
 
         mSnoozeHelper.cancel(UserHandle.USER_SYSTEM, false);
         // 3 = once for each snooze above (3), only.
         verify(mAm, times(3)).cancel(any(PendingIntent.class));
         assertTrue(mSnoozeHelper.isSnoozed(
-                UserHandle.USER_SYSTEM, r.sbn.getPackageName(), r.getKey()));
+                UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey()));
         assertTrue(mSnoozeHelper.isSnoozed(
-                UserHandle.USER_SYSTEM, r2.sbn.getPackageName(), r2.getKey()));
+                UserHandle.USER_SYSTEM, r2.getSbn().getPackageName(), r2.getKey()));
         assertTrue(mSnoozeHelper.isSnoozed(
-                UserHandle.USER_ALL, r3.sbn.getPackageName(), r3.getKey()));
+                UserHandle.USER_ALL, r3.getSbn().getPackageName(), r3.getKey()));
     }
 
     @Test
@@ -269,21 +269,21 @@
         mSnoozeHelper.snooze(r2, 1000);
         mSnoozeHelper.snooze(r3, 1000);
         assertTrue(mSnoozeHelper.isSnoozed(
-                UserHandle.USER_SYSTEM, r.sbn.getPackageName(), r.getKey()));
+                UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey()));
         assertTrue(mSnoozeHelper.isSnoozed(
-                UserHandle.USER_SYSTEM, r2.sbn.getPackageName(), r2.getKey()));
+                UserHandle.USER_SYSTEM, r2.getSbn().getPackageName(), r2.getKey()));
         assertTrue(mSnoozeHelper.isSnoozed(
-                UserHandle.USER_SYSTEM, r3.sbn.getPackageName(), r3.getKey()));
+                UserHandle.USER_SYSTEM, r3.getSbn().getPackageName(), r3.getKey()));
 
         mSnoozeHelper.cancel(UserHandle.USER_SYSTEM, "pkg2");
         // 3 = once for each snooze above (3), only.
         verify(mAm, times(3)).cancel(any(PendingIntent.class));
         assertTrue(mSnoozeHelper.isSnoozed(
-                UserHandle.USER_SYSTEM, r.sbn.getPackageName(), r.getKey()));
+                UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey()));
         assertTrue(mSnoozeHelper.isSnoozed(
-                UserHandle.USER_SYSTEM, r2.sbn.getPackageName(), r2.getKey()));
+                UserHandle.USER_SYSTEM, r2.getSbn().getPackageName(), r2.getKey()));
         assertTrue(mSnoozeHelper.isSnoozed(
-                UserHandle.USER_SYSTEM, r3.sbn.getPackageName(), r3.getKey()));
+                UserHandle.USER_SYSTEM, r3.getSbn().getPackageName(), r3.getKey()));
     }
 
     @Test
@@ -291,12 +291,12 @@
         NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
         mSnoozeHelper.snooze(r, 1000);
         assertTrue(mSnoozeHelper.isSnoozed(
-                UserHandle.USER_SYSTEM, r.sbn.getPackageName(), r.getKey()));
+                UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey()));
 
-        mSnoozeHelper.cancel(UserHandle.USER_SYSTEM, r.sbn.getPackageName(), "one", 1);
+        mSnoozeHelper.cancel(UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), "one", 1);
 
         assertTrue(mSnoozeHelper.isSnoozed(
-                UserHandle.USER_SYSTEM, r.sbn.getPackageName(), r.getKey()));
+                UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey()));
     }
 
     @Test
@@ -306,11 +306,11 @@
         mSnoozeHelper.snooze(r, 1000);
         mSnoozeHelper.snooze(r2 , 1000);
         assertTrue(mSnoozeHelper.isSnoozed(
-                UserHandle.USER_SYSTEM, r.sbn.getPackageName(), r.getKey()));
+                UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey()));
         assertTrue(mSnoozeHelper.isSnoozed(
-                UserHandle.USER_SYSTEM, r2.sbn.getPackageName(), r2.getKey()));
+                UserHandle.USER_SYSTEM, r2.getSbn().getPackageName(), r2.getKey()));
 
-        mSnoozeHelper.cancel(UserHandle.USER_SYSTEM, r.sbn.getPackageName(), "one", 1);
+        mSnoozeHelper.cancel(UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), "one", 1);
 
         mSnoozeHelper.repost(r.getKey(), UserHandle.USER_SYSTEM);
         verify(mCallback, never()).repost(UserHandle.USER_SYSTEM, r);
@@ -507,18 +507,18 @@
         mSnoozeHelper.snooze(r, 1000);
         mSnoozeHelper.snooze(r2, 1000);
         assertTrue(mSnoozeHelper.isSnoozed(
-                UserHandle.USER_SYSTEM, r.sbn.getPackageName(), r.getKey()));
+                UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey()));
         assertTrue(mSnoozeHelper.isSnoozed(
-                UserHandle.USER_SYSTEM, r2.sbn.getPackageName(), r2.getKey()));
+                UserHandle.USER_SYSTEM, r2.getSbn().getPackageName(), r2.getKey()));
 
         // clear data
         mSnoozeHelper.clearData(UserHandle.USER_SYSTEM, "pkg");
 
         // nothing snoozed; alarms canceled
         assertFalse(mSnoozeHelper.isSnoozed(
-                UserHandle.USER_SYSTEM, r.sbn.getPackageName(), r.getKey()));
+                UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey()));
         assertFalse(mSnoozeHelper.isSnoozed(
-                UserHandle.USER_SYSTEM, r2.sbn.getPackageName(), r2.getKey()));
+                UserHandle.USER_SYSTEM, r2.getSbn().getPackageName(), r2.getKey()));
         // twice for initial snooze, twice for canceling the snooze
         verify(mAm, times(4)).cancel(any(PendingIntent.class));
     }
@@ -533,21 +533,21 @@
         mSnoozeHelper.snooze(r2, 1000);
         mSnoozeHelper.snooze(r3, 1000);
         assertTrue(mSnoozeHelper.isSnoozed(
-                UserHandle.USER_SYSTEM, r.sbn.getPackageName(), r.getKey()));
+                UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey()));
         assertTrue(mSnoozeHelper.isSnoozed(
-                UserHandle.USER_ALL, r2.sbn.getPackageName(), r2.getKey()));
+                UserHandle.USER_ALL, r2.getSbn().getPackageName(), r2.getKey()));
         assertTrue(mSnoozeHelper.isSnoozed(
-                UserHandle.USER_SYSTEM, r3.sbn.getPackageName(), r3.getKey()));
+                UserHandle.USER_SYSTEM, r3.getSbn().getPackageName(), r3.getKey()));
 
         // clear data
         mSnoozeHelper.clearData(UserHandle.USER_SYSTEM, "pkg");
 
         assertFalse(mSnoozeHelper.isSnoozed(
-                UserHandle.USER_SYSTEM, r.sbn.getPackageName(), r.getKey()));
+                UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey()));
         assertTrue(mSnoozeHelper.isSnoozed(
-                UserHandle.USER_ALL, r2.sbn.getPackageName(), r2.getKey()));
+                UserHandle.USER_ALL, r2.getSbn().getPackageName(), r2.getKey()));
         assertTrue(mSnoozeHelper.isSnoozed(
-                UserHandle.USER_SYSTEM, r3.sbn.getPackageName(), r3.getKey()));
+                UserHandle.USER_SYSTEM, r3.getSbn().getPackageName(), r3.getKey()));
         // once for each initial snooze, once for canceling one snooze
         verify(mAm, times(4)).cancel(any(PendingIntent.class));
     }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
index b654764..f7b435e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
@@ -91,6 +91,7 @@
         int priorityCategories = originalPolicy.priorityCategories;
         int priorityCallSenders = originalPolicy.priorityCallSenders;
         int priorityMessageSenders = originalPolicy.priorityMessageSenders;
+        int priorityConversationsSenders = originalPolicy.priorityConversationSenders;
         int suppressedVisualEffects = originalPolicy.suppressedVisualEffects;
         priorityCategories |= Policy.PRIORITY_CATEGORY_ALARMS;
         priorityCategories |= Policy.PRIORITY_CATEGORY_REMINDERS;
@@ -99,7 +100,7 @@
         suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_AMBIENT;
 
         Policy expectedPolicy = new Policy(priorityCategories, priorityCallSenders,
-                priorityMessageSenders, suppressedVisualEffects, 0);
+                priorityMessageSenders, suppressedVisualEffects, 0, priorityConversationsSenders);
 
         assertEquals(expectedPolicy, config.toNotificationPolicy(zenPolicy));
     }
@@ -235,6 +236,8 @@
         config.areChannelsBypassingDnd = false;
         config.allowCallsFrom = ZenModeConfig.SOURCE_ANYONE;
         config.allowMessagesFrom = ZenModeConfig.SOURCE_ANYONE;
+        config.allowConversations = true;
+        config.allowConversationsFrom = ZenPolicy.CONVERSATION_SENDERS_IMPORTANT;
 
         config.suppressedVisualEffects = 0;
         return config;
@@ -252,6 +255,8 @@
         config.allowReminders = false;
         config.allowEvents = false;
         config.areChannelsBypassingDnd = false;
+        config.allowConversations = false;
+        config.allowConversationsFrom = ZenPolicy.CONVERSATION_SENDERS_NONE;
 
         config.suppressedVisualEffects = 0;
         return config;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
index 32f389a..fb15088 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
@@ -16,6 +16,16 @@
 
 package com.android.server.notification;
 
+import static android.app.Notification.CATEGORY_CALL;
+import static android.app.Notification.CATEGORY_MESSAGE;
+import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_ANYONE;
+import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_IMPORTANT;
+import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_NONE;
+import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CALLS;
+import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CONVERSATIONS;
+import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES;
+import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_ANY;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
 import static android.provider.Settings.Global.ZEN_MODE_ALARMS;
 import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
@@ -31,11 +41,10 @@
 
 import android.app.Notification;
 import android.app.NotificationChannel;
-import android.app.NotificationManager;
 import android.app.NotificationManager.Policy;
 import android.media.AudioAttributes;
+import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
-import android.service.notification.ZenModeConfig;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -76,6 +85,16 @@
         return new NotificationRecord(mContext, sbn, c);
     }
 
+    private NotificationRecord getConversationRecord(NotificationChannel c,
+            StatusBarNotification sbn) {
+        NotificationRecord r = mock(NotificationRecord.class);
+        when(r.getCriticality()).thenReturn(CriticalNotificationExtractor.NORMAL);
+        when(r.getSbn()).thenReturn(sbn);
+        when(r.getChannel()).thenReturn(c);
+        when(r.isConversation()).thenReturn(true);
+        return r;
+    }
+
     @Test
     public void testIsMessage() {
         NotificationRecord r = getNotificationRecord();
@@ -97,14 +116,14 @@
         assertTrue(mZenModeFiltering.isAlarm(r));
 
         r = getNotificationRecord();
-        r.sbn.getNotification().category = Notification.CATEGORY_ALARM;
+        r.getSbn().getNotification().category = Notification.CATEGORY_ALARM;
         assertTrue(mZenModeFiltering.isAlarm(r));
     }
 
     @Test
     public void testIsAlarm_wrongCategory() {
         NotificationRecord r = getNotificationRecord();
-        r.sbn.getNotification().category = Notification.CATEGORY_CALL;
+        r.getSbn().getNotification().category = CATEGORY_CALL;
         assertFalse(mZenModeFiltering.isAlarm(r));
     }
 
@@ -121,10 +140,10 @@
     @Test
     public void testSuppressDNDInfo_yes_VisEffectsAllowed() {
         NotificationRecord r = getNotificationRecord();
-        when(r.sbn.getPackageName()).thenReturn("android");
-        when(r.sbn.getId()).thenReturn(SystemMessage.NOTE_ZEN_UPGRADE);
+        when(r.getSbn().getPackageName()).thenReturn("android");
+        when(r.getSbn().getId()).thenReturn(SystemMessage.NOTE_ZEN_UPGRADE);
         Policy policy = new Policy(0, 0, 0, Policy.getAllSuppressedVisualEffects()
-                - SUPPRESSED_EFFECT_STATUS_BAR);
+                - SUPPRESSED_EFFECT_STATUS_BAR, 0);
 
         assertTrue(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r));
     }
@@ -132,9 +151,9 @@
     @Test
     public void testSuppressDNDInfo_yes_WrongId() {
         NotificationRecord r = getNotificationRecord();
-        when(r.sbn.getPackageName()).thenReturn("android");
-        when(r.sbn.getId()).thenReturn(SystemMessage.NOTE_ACCOUNT_CREDENTIAL_PERMISSION);
-        Policy policy = new Policy(0, 0, 0, Policy.getAllSuppressedVisualEffects());
+        when(r.getSbn().getPackageName()).thenReturn("android");
+        when(r.getSbn().getId()).thenReturn(SystemMessage.NOTE_ACCOUNT_CREDENTIAL_PERMISSION);
+        Policy policy = new Policy(0, 0, 0, Policy.getAllSuppressedVisualEffects(), 0);
 
         assertTrue(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r));
     }
@@ -142,9 +161,9 @@
     @Test
     public void testSuppressDNDInfo_yes_WrongPackage() {
         NotificationRecord r = getNotificationRecord();
-        when(r.sbn.getPackageName()).thenReturn("android2");
-        when(r.sbn.getId()).thenReturn(SystemMessage.NOTE_ZEN_UPGRADE);
-        Policy policy = new Policy(0, 0, 0, Policy.getAllSuppressedVisualEffects());
+        when(r.getSbn().getPackageName()).thenReturn("android2");
+        when(r.getSbn().getId()).thenReturn(SystemMessage.NOTE_ZEN_UPGRADE);
+        Policy policy = new Policy(0, 0, 0, Policy.getAllSuppressedVisualEffects(), 0);
 
         assertTrue(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r));
     }
@@ -152,9 +171,9 @@
     @Test
     public void testSuppressDNDInfo_no() {
         NotificationRecord r = getNotificationRecord();
-        when(r.sbn.getPackageName()).thenReturn("android");
-        when(r.sbn.getId()).thenReturn(SystemMessage.NOTE_ZEN_UPGRADE);
-        Policy policy = new Policy(0, 0, 0, Policy.getAllSuppressedVisualEffects());
+        when(r.getSbn().getPackageName()).thenReturn("android");
+        when(r.getSbn().getId()).thenReturn(SystemMessage.NOTE_ZEN_UPGRADE);
+        Policy policy = new Policy(0, 0, 0, Policy.getAllSuppressedVisualEffects(), 0);
 
         assertFalse(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r));
         assertFalse(mZenModeFiltering.shouldIntercept(ZEN_MODE_ALARMS, policy, r));
@@ -164,7 +183,7 @@
     @Test
     public void testSuppressAnything_yes_ZenModeOff() {
         NotificationRecord r = getNotificationRecord();
-        when(r.sbn.getPackageName()).thenReturn("bananas");
+        when(r.getSbn().getPackageName()).thenReturn("bananas");
         Policy policy = new Policy(0, 0, 0, Policy.getAllSuppressedVisualEffects());
 
         assertFalse(mZenModeFiltering.shouldIntercept(ZEN_MODE_OFF, policy, r));
@@ -174,12 +193,120 @@
     public void testSuppressAnything_bypass_ZenModeOn() {
         NotificationRecord r = getNotificationRecord();
         r.setCriticality(CriticalNotificationExtractor.CRITICAL);
-        when(r.sbn.getPackageName()).thenReturn("bananas");
-        Policy policy = new Policy(0, 0, 0, Policy.getAllSuppressedVisualEffects());
+        when(r.getSbn().getPackageName()).thenReturn("bananas");
+        Policy policy = new Policy(0, 0, 0, Policy.getAllSuppressedVisualEffects(), 0);
 
         assertFalse(mZenModeFiltering.shouldIntercept(ZEN_MODE_NO_INTERRUPTIONS, policy, r));
 
         r.setCriticality(CriticalNotificationExtractor.CRITICAL_LOW);
         assertFalse(mZenModeFiltering.shouldIntercept(ZEN_MODE_NO_INTERRUPTIONS, policy, r));
     }
+
+    @Test
+    public void testConversation_allAllowed() {
+        Notification n = new Notification.Builder(mContext, "a").build();
+        StatusBarNotification sbn = new StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0, n,
+                UserHandle.SYSTEM, null, 0);
+
+        NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_DEFAULT);
+        channel.setConversationId("parent", "me, work");
+
+        NotificationRecord r = getConversationRecord(channel, sbn);
+        when(r.isConversation()).thenReturn(true);
+
+        Policy policy = new Policy(
+                PRIORITY_CATEGORY_CONVERSATIONS, 0, 0, 0, CONVERSATION_SENDERS_ANYONE);
+
+        assertFalse(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r));
+    }
+
+    @Test
+    public void testConversation_importantAllowed_isImportant() {
+        Notification n = new Notification.Builder(mContext, "a").build();
+        StatusBarNotification sbn = new StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0, n,
+                UserHandle.SYSTEM, null, 0);
+
+        NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_DEFAULT);
+        channel.setConversationId("parent", "me, work");
+        channel.setImportantConversation(true);
+
+        NotificationRecord r = getConversationRecord(channel, sbn);
+
+        Policy policy = new Policy(
+                PRIORITY_CATEGORY_CONVERSATIONS, 0, 0, 0, CONVERSATION_SENDERS_IMPORTANT);
+
+        assertFalse(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r));
+    }
+
+    @Test
+    public void testConversation_importantAllowed_isNotImportant() {
+        Notification n = new Notification.Builder(mContext, "a").build();
+        StatusBarNotification sbn = new StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0, n,
+                UserHandle.SYSTEM, null, 0);
+
+        NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_DEFAULT);
+        channel.setConversationId("parent", "me, work");
+
+        NotificationRecord r = getConversationRecord(channel, sbn);
+
+        Policy policy = new Policy(
+                PRIORITY_CATEGORY_CONVERSATIONS, 0, 0, 0, CONVERSATION_SENDERS_IMPORTANT);
+
+        assertTrue(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r));
+    }
+
+    @Test
+    public void testConversation_noneAllowed_notCallOrMsg() {
+        Notification n = new Notification.Builder(mContext, "a").build();
+        StatusBarNotification sbn = new StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0, n,
+                UserHandle.SYSTEM, null, 0);
+
+        NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_DEFAULT);
+        channel.setConversationId("parent", "me, work");
+
+        NotificationRecord r = getConversationRecord(channel, sbn);
+
+        Policy policy =
+                new Policy(PRIORITY_CATEGORY_CONVERSATIONS, 0, 0, 0, CONVERSATION_SENDERS_NONE);
+
+        assertTrue(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r));
+    }
+
+    @Test
+    public void testConversation_noneAllowed_callAllowed() {
+        Notification n = new Notification.Builder(mContext, "a").build();
+        StatusBarNotification sbn = new StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0, n,
+                UserHandle.SYSTEM, null, 0);
+
+        NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_DEFAULT);
+        channel.setConversationId("parent", "me, work");
+
+        NotificationRecord r = getConversationRecord(channel, sbn);
+        when(r.isCategory(CATEGORY_CALL)).thenReturn(true);
+
+        Policy policy =
+                new Policy(PRIORITY_CATEGORY_CALLS,
+                        PRIORITY_SENDERS_ANY, 0, 0, CONVERSATION_SENDERS_NONE);
+
+        assertFalse(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r));
+    }
+
+    @Test
+    public void testConversation_noneAllowed_msgAllowed() {
+        when(mMessagingUtil.isMessaging(any())).thenReturn(true);
+        Notification n = new Notification.Builder(mContext, "a").build();
+        StatusBarNotification sbn = new StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0, n,
+                UserHandle.SYSTEM, null, 0);
+
+        NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_DEFAULT);
+        channel.setConversationId("parent", "me, work");
+
+        NotificationRecord r = getConversationRecord(channel, sbn);
+
+        Policy policy =
+                new Policy(PRIORITY_CATEGORY_MESSAGES,
+                        0, PRIORITY_SENDERS_ANY, 0, CONVERSATION_SENDERS_NONE);
+
+        assertFalse(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r));
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 99771b9..bc2766c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -142,7 +142,8 @@
                 + "<allow calls=\"false\" repeatCallers=\"false\" messages=\"true\" "
                 + "reminders=\"false\" events=\"false\" callsFrom=\"1\" messagesFrom=\"2\" "
                 + "visualScreenOff=\"true\" alarms=\"true\" "
-                + "media=\"true\" system=\"false\" />\n"
+                + "media=\"true\" system=\"false\" conversations=\"true\""
+                + " conversationsFrom=\"2\"/>\n"
                 + "<automatic ruleId=\"" + EVENTS_DEFAULT_RULE_ID
                 + "\" enabled=\"false\" snoozing=\"false\""
                 + " name=\"Event\" zen=\"1\""
@@ -218,7 +219,7 @@
     public void testZenOff_NoMuteApplied() {
         mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_OFF;
         mZenModeHelperSpy.mConsolidatedPolicy = new Policy(Policy.PRIORITY_CATEGORY_ALARMS |
-                Policy.PRIORITY_CATEGORY_MEDIA, 0, 0, 0, 0);
+                Policy.PRIORITY_CATEGORY_MEDIA, 0, 0, 0, 0, 0);
         mZenModeHelperSpy.applyRestrictions();
 
         doNothing().when(mZenModeHelperSpy).applyRestrictions(eq(false), anyBoolean(), anyInt());
@@ -232,7 +233,7 @@
     public void testZenOn_AllowAlarmsMedia_NoAlarmMediaMuteApplied() {
         mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         mZenModeHelperSpy.mConsolidatedPolicy = new Policy(Policy.PRIORITY_CATEGORY_ALARMS |
-                Policy.PRIORITY_CATEGORY_MEDIA, 0, 0, 0, 0);
+                Policy.PRIORITY_CATEGORY_MEDIA, 0, 0, 0, 0, 0);
 
         mZenModeHelperSpy.applyRestrictions();
         verify(mZenModeHelperSpy, atLeastOnce()).applyRestrictions(true, false,
@@ -244,7 +245,7 @@
     @Test
     public void testZenOn_DisallowAlarmsMedia_AlarmMediaMuteApplied() {
         mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
-        mZenModeHelperSpy.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0);
+        mZenModeHelperSpy.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0);
         mZenModeHelperSpy.applyRestrictions();
         verify(mZenModeHelperSpy, atLeastOnce()).applyRestrictions(true, true,
                 AudioAttributes.USAGE_ALARM);
@@ -260,7 +261,7 @@
     public void testTotalSilence() {
         mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_NO_INTERRUPTIONS;
         mZenModeHelperSpy.mConsolidatedPolicy = new Policy(Policy.PRIORITY_CATEGORY_ALARMS |
-                Policy.PRIORITY_CATEGORY_MEDIA, 0, 0, 0, 0);
+                Policy.PRIORITY_CATEGORY_MEDIA, 0, 0, 0, 0, 0);
         mZenModeHelperSpy.applyRestrictions();
 
         // Total silence will silence alarms, media and system noises (but not vibrations)
@@ -281,7 +282,7 @@
     @Test
     public void testAlarmsOnly_alarmMediaMuteNotApplied() {
         mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_ALARMS;
-        mZenModeHelperSpy.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0);
+        mZenModeHelperSpy.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0);
         mZenModeHelperSpy.applyRestrictions();
 
         // Alarms only mode will not silence alarms
@@ -304,7 +305,7 @@
     @Test
     public void testAlarmsOnly_callsMuteApplied() {
         mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_ALARMS;
-        mZenModeHelperSpy.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0);
+        mZenModeHelperSpy.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0);
         mZenModeHelperSpy.applyRestrictions();
 
         // Alarms only mode will silence calls despite priority-mode config
@@ -318,7 +319,7 @@
     public void testAlarmsOnly_allZenConfigToggledCannotBypass_alarmMuteNotApplied() {
         // Only audio attributes with SUPPRESIBLE_NEVER can bypass
         mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_ALARMS;
-        mZenModeHelperSpy.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0);
+        mZenModeHelperSpy.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0);
         mZenModeHelperSpy.applyRestrictions();
 
         verify(mZenModeHelperSpy, atLeastOnce()).applyRestrictions(false, false,
@@ -330,7 +331,7 @@
         // Only audio attributes with SUPPRESIBLE_NEVER can bypass
         // with special case USAGE_ASSISTANCE_SONIFICATION
         mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
-        mZenModeHelperSpy.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0);
+        mZenModeHelperSpy.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0);
         mZenModeHelperSpy.applyRestrictions();
 
         for (int usage : AudioAttributes.SDK_USAGES) {
@@ -352,7 +353,7 @@
     public void testApplyRestrictions_whitelist_priorityOnlyMode() {
         mZenModeHelperSpy.setPriorityOnlyDndExemptPackages(new String[] {PKG_O});
         mZenModeHelperSpy.mZenMode = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
-        mZenModeHelperSpy.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0);
+        mZenModeHelperSpy.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0);
         mZenModeHelperSpy.applyRestrictions();
 
         for (int usage : AudioAttributes.SDK_USAGES) {
@@ -367,7 +368,7 @@
     public void testApplyRestrictions_whitelist_alarmsOnlyMode() {
         mZenModeHelperSpy.setPriorityOnlyDndExemptPackages(new String[] {PKG_O});
         mZenModeHelperSpy.mZenMode = Global.ZEN_MODE_ALARMS;
-        mZenModeHelperSpy.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0);
+        mZenModeHelperSpy.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0);
         mZenModeHelperSpy.applyRestrictions();
 
         for (int usage : AudioAttributes.SDK_USAGES) {
@@ -382,7 +383,7 @@
     public void testApplyRestrictions_whitelist_totalSilenceMode() {
         mZenModeHelperSpy.setPriorityOnlyDndExemptPackages(new String[] {PKG_O});
         mZenModeHelperSpy.mZenMode = Global.ZEN_MODE_NO_INTERRUPTIONS;
-        mZenModeHelperSpy.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0);
+        mZenModeHelperSpy.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0);
         mZenModeHelperSpy.applyRestrictions();
 
         for (int usage : AudioAttributes.SDK_USAGES) {
@@ -401,7 +402,7 @@
         Settings.Secure.putInt(mContentResolver, Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 1);
         Settings.Secure.putInt(mContentResolver, Settings.Secure.ZEN_SETTINGS_UPDATED, 0);
         mZenModeHelperSpy.mIsBootComplete = true;
-        mZenModeHelperSpy.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0);
+        mZenModeHelperSpy.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0);
         mZenModeHelperSpy.setZenModeSetting(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS);
 
         verify(mZenModeHelperSpy, times(1)).createZenUpgradeNotification();
@@ -452,7 +453,8 @@
         mZenModeHelperSpy.mConfig.allowCalls = false;
         mZenModeHelperSpy.mConfig.allowMessages = false;
         mZenModeHelperSpy.mConfig.allowEvents = false;
-        mZenModeHelperSpy.mConfig.allowRepeatCallers= false;
+        mZenModeHelperSpy.mConfig.allowRepeatCallers = false;
+        mZenModeHelperSpy.mConfig.allowConversations = false;
 
         // 2. apply priority only zen - verify ringer is unchanged
         mZenModeHelperSpy.applyZenToRingerMode();
@@ -509,7 +511,8 @@
         mZenModeHelperSpy.mConfig.allowCalls = false;
         mZenModeHelperSpy.mConfig.allowMessages = false;
         mZenModeHelperSpy.mConfig.allowEvents = false;
-        mZenModeHelperSpy.mConfig.allowRepeatCallers= false;
+        mZenModeHelperSpy.mConfig.allowRepeatCallers = false;
+        mZenModeHelperSpy.mConfig.allowConversations = false;
         ZenModeHelper.RingerModeDelegate ringerModeDelegateRingerNotMuted =
                 mZenModeHelperSpy.new RingerModeDelegate();
 
@@ -694,7 +697,9 @@
         mZenModeHelperSpy.mConfig.allowCalls = true;
         mZenModeHelperSpy.mConfig.allowMessages = true;
         mZenModeHelperSpy.mConfig.allowEvents = true;
-        mZenModeHelperSpy.mConfig.allowRepeatCallers= true;
+        mZenModeHelperSpy.mConfig.allowRepeatCallers = true;
+        mZenModeHelperSpy.mConfig.allowConversations = true;
+        mZenModeHelperSpy.mConfig.allowConversationsFrom = ZenPolicy.CONVERSATION_SENDERS_ANYONE;
         mZenModeHelperSpy.mConfig.suppressedVisualEffects = SUPPRESSED_EFFECT_BADGE;
         mZenModeHelperSpy.mConfig.manualRule = new ZenModeConfig.ZenRule();
         mZenModeHelperSpy.mConfig.manualRule.component = new ComponentName("a", "a");
@@ -716,7 +721,9 @@
         mZenModeHelperSpy.mConfig.allowCalls = true;
         mZenModeHelperSpy.mConfig.allowMessages = true;
         mZenModeHelperSpy.mConfig.allowEvents = true;
-        mZenModeHelperSpy.mConfig.allowRepeatCallers= true;
+        mZenModeHelperSpy.mConfig.allowRepeatCallers = true;
+        mZenModeHelperSpy.mConfig.allowConversations = true;
+        mZenModeHelperSpy.mConfig.allowConversationsFrom = ZenPolicy.CONVERSATION_SENDERS_ANYONE;
         mZenModeHelperSpy.mConfig.suppressedVisualEffects = SUPPRESSED_EFFECT_BADGE;
         mZenModeHelperSpy.mConfig.manualRule = new ZenModeConfig.ZenRule();
         mZenModeHelperSpy.mConfig.manualRule.zenMode =
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index 8c454424..123bb07 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -37,6 +37,7 @@
     <uses-permission android:name="android.permission.WAKE_LOCK" />
     <uses-permission android:name="android.permission.REORDER_TASKS" />
     <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
+    <uses-permission android:name="android.permission.STATUS_BAR" />
 
     <!-- TODO: Remove largeHeap hack when memory leak is fixed (b/123984854) -->
     <application android:debuggable="true"
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
index 399cf49..135d005 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
 import static android.content.pm.ApplicationInfo.FLAG_SUSPENDED;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
@@ -44,6 +45,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.app.BlockedAppActivity;
 import com.android.internal.app.HarmfulAppWarningActivity;
 import com.android.internal.app.SuspendedAppActivity;
 import com.android.internal.app.UnlaunchableAppActivity;
@@ -105,6 +107,8 @@
     private PackageManagerService mPackageManager;
     @Mock
     private ActivityManagerInternal mAmInternal;
+    @Mock
+    private LockTaskController mLockTaskController;
 
     private ActivityStartInterceptor mInterceptor;
     private ActivityInfo mAInfo = new ActivityInfo();
@@ -145,6 +149,13 @@
         when(mPackageManager.getHarmfulAppWarning(TEST_PACKAGE_NAME, TEST_USER_ID))
                 .thenReturn(null);
 
+        // Mock LockTaskController
+        mAInfo.lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_DEFAULT;
+        when(mService.getLockTaskController()).thenReturn(mLockTaskController);
+        when(mLockTaskController.isActivityAllowed(
+                TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT))
+                .thenReturn(true);
+
         // Initialise activity info
         mAInfo.applicationInfo = new ApplicationInfo();
         mAInfo.packageName = mAInfo.applicationInfo.packageName = TEST_PACKAGE_NAME;
@@ -196,6 +207,18 @@
     }
 
     @Test
+    public void testInterceptLockTaskModeViolationPackage() {
+        when(mLockTaskController.isActivityAllowed(
+                TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT))
+                .thenReturn(false);
+
+        assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null));
+
+        assertTrue(BlockedAppActivity.createIntent(TEST_USER_ID, TEST_PACKAGE_NAME)
+                .filterEquals(mInterceptor.mIntent));
+    }
+
+    @Test
     public void testInterceptQuietProfile() {
         // GIVEN that the user the activity is starting as is currently in quiet mode
         when(mUserManager.isQuietModeEnabled(eq(UserHandle.of(TEST_USER_ID)))).thenReturn(true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index 5acc0f2..956c200 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -126,7 +126,8 @@
         mTarget = new TestDragDropController(mWm, mWm.mH.getLooper());
         mWindow = createDropTargetWindow("Drag test window", 0);
         doReturn(mWindow).when(mDisplayContent).getTouchableWinAtPointLocked(0, 0);
-        when(mWm.mInputManager.transferTouchFocus(any(), any())).thenReturn(true);
+        when(mWm.mInputManager.transferTouchFocus(any(InputChannel.class),
+                any(InputChannel.class))).thenReturn(true);
 
         mWm.mWindowMap.put(mWindow.mClient.asBinder(), mWindow);
     }
@@ -176,7 +177,8 @@
                     .setFormat(PixelFormat.TRANSLUCENT)
                     .build();
 
-            assertTrue(mWm.mInputManager.transferTouchFocus(null, null));
+            assertTrue(mWm.mInputManager.transferTouchFocus(new InputChannel(),
+                    new InputChannel()));
             mToken = mTarget.performDrag(
                     new SurfaceSession(), 0, 0, mWindow.mClient, flag, surface, 0, 0, 0, 0, 0,
                     data);
diff --git a/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
index 039ff60..a137cde 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
@@ -25,10 +25,14 @@
 import static android.app.StatusBarManager.DISABLE_HOME;
 import static android.app.StatusBarManager.DISABLE_NOTIFICATION_ALERTS;
 import static android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK;
 import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_HOME;
 import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_KEYGUARD;
 import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NONE;
 import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER;
 import static android.os.Process.SYSTEM_UID;
 import static android.telecom.TelecomManager.EMERGENCY_DIALER_COMPONENT;
 
@@ -693,6 +697,45 @@
         assertTrue((StatusBarManager.DISABLE2_QUICK_SETTINGS & flags.second) != 0);
     }
 
+    @Test
+    public void testIsActivityAllowed() {
+        // WHEN lock task mode is not enabled
+        assertTrue(mLockTaskController.isActivityAllowed(
+                TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT));
+
+        // Start lock task mode
+        Task tr = getTask(Task.LOCK_TASK_AUTH_WHITELISTED);
+        mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
+
+        // WHEN LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK is not enabled
+        assertTrue(mLockTaskController.isActivityAllowed(
+                TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT));
+
+        // Enable LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK feature
+        mLockTaskController.updateLockTaskFeatures(
+                TEST_USER_ID, LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK);
+
+        // package with LOCK_TASK_LAUNCH_MODE_ALWAYS should always be allowed
+        assertTrue(mLockTaskController.isActivityAllowed(
+                TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_ALWAYS));
+
+        // unwhitelisted package should not be allowed
+        assertFalse(mLockTaskController.isActivityAllowed(
+                TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT));
+
+        // update the whitelist
+        String[] whitelist = new String[] { TEST_PACKAGE_NAME };
+        mLockTaskController.updateLockTaskPackages(TEST_USER_ID, whitelist);
+
+        // whitelisted package should be allowed
+        assertTrue(mLockTaskController.isActivityAllowed(
+                TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT));
+
+        // package with LOCK_TASK_LAUNCH_MODE_NEVER should never be allowed
+        assertFalse(mLockTaskController.isActivityAllowed(
+                TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_NEVER));
+    }
+
     private Task getTask(int lockTaskAuth) {
         return getTask(TEST_PACKAGE_NAME, lockTaskAuth);
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
index 078347e..7172a1b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
@@ -26,16 +26,19 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityManager.StackInfo;
@@ -139,6 +142,52 @@
     }
 
     @Test
+    public void testUnregisterOrganizer() throws RemoteException {
+        final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
+        final Task task = createTaskInStack(stack, 0 /* userId */);
+        final ITaskOrganizer organizer = registerMockOrganizer();
+
+        stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        verify(organizer).taskAppeared(any());
+        assertTrue(stack.isControlledByTaskOrganizer());
+
+        mWm.mAtmService.mTaskOrganizerController.unregisterTaskOrganizer(organizer);
+        verify(organizer).taskVanished(any());
+        assertFalse(stack.isControlledByTaskOrganizer());
+    }
+
+    @Test
+    public void testUnregisterOrganizerReturnsRegistrationToPrevious() throws RemoteException {
+        final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
+        final Task task = createTaskInStack(stack, 0 /* userId */);
+        final ActivityStack stack2 = createTaskStackOnDisplay(mDisplayContent);
+        final Task task2 = createTaskInStack(stack2, 0 /* userId */);
+        final ActivityStack stack3 = createTaskStackOnDisplay(mDisplayContent);
+        final Task task3 = createTaskInStack(stack3, 0 /* userId */);
+        final ITaskOrganizer organizer = registerMockOrganizer(WINDOWING_MODE_MULTI_WINDOW);
+
+        // First organizer is registered, verify a task appears when changing windowing mode
+        stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        verify(organizer, times(1)).taskAppeared(any());
+        assertTrue(stack.isControlledByTaskOrganizer());
+
+        // Now we replace the registration and1 verify the new organizer receives tasks
+        // newly entering the windowing mode.
+        final ITaskOrganizer organizer2 = registerMockOrganizer(WINDOWING_MODE_MULTI_WINDOW);
+        stack2.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        verify(organizer2).taskAppeared(any());
+        assertTrue(stack2.isControlledByTaskOrganizer());
+
+        // Now we unregister the second one, the first one should automatically be reregistered
+        // so we verify that it's now seeing changes.
+        mWm.mAtmService.mTaskOrganizerController.unregisterTaskOrganizer(organizer2);
+
+        stack3.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        verify(organizer, times(2)).taskAppeared(any());
+        assertTrue(stack3.isControlledByTaskOrganizer());
+    }
+
+    @Test
     public void testRegisterTaskOrganizerStackWindowingModeChanges() throws RemoteException {
         final ITaskOrganizer organizer = registerMockOrganizer(WINDOWING_MODE_PINNED);
 
@@ -161,7 +210,7 @@
         WindowContainerTransaction t = new WindowContainerTransaction();
         Rect newBounds = new Rect(10, 10, 100, 100);
         t.setBounds(task.mRemoteToken, new Rect(10, 10, 100, 100));
-        mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(t);
+        mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(t, null);
         assertEquals(newBounds, task.getBounds());
     }
 
@@ -176,7 +225,7 @@
         assertEquals(stack.mRemoteToken, info.stackToken);
         Rect newBounds = new Rect(10, 10, 100, 100);
         t.setBounds(info.stackToken, new Rect(10, 10, 100, 100));
-        mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(t);
+        mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(t, null);
         assertEquals(newBounds, stack.getBounds());
     }
 
@@ -189,7 +238,7 @@
         WindowContainerTransaction t = new WindowContainerTransaction();
         assertTrue(task.isFocusable());
         t.setFocusable(stack.mRemoteToken, false);
-        mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(t);
+        mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(t, null);
         assertFalse(task.isFocusable());
     }
 
@@ -318,4 +367,46 @@
         }
         return out;
     }
+
+    @Test
+    public void testTrivialBLASTCallback() throws RemoteException {
+        final ActivityStack stackController1 = createTaskStackOnDisplay(mDisplayContent);
+        final Task task = createTaskInStack(stackController1, 0 /* userId */);
+        final ITaskOrganizer organizer = registerMockOrganizer();
+
+        BLASTSyncEngine bse = new BLASTSyncEngine();
+
+        BLASTSyncEngine.TransactionReadyListener transactionListener =
+            mock(BLASTSyncEngine.TransactionReadyListener.class);
+
+        int id = bse.startSyncSet(transactionListener);
+        bse.addToSyncSet(id, task);
+        bse.setReady(id);
+        // Since this task has no windows the sync is trivial and completes immediately.
+        verify(transactionListener)
+            .transactionReady(anyInt(), any());
+    }
+
+    @Test
+    public void testBLASTCallbackWithWindow() {
+        final ActivityStack stackController1 = createTaskStackOnDisplay(mDisplayContent);
+        final Task task = createTaskInStack(stackController1, 0 /* userId */);
+        final ITaskOrganizer organizer = registerMockOrganizer();
+        final WindowState w = createAppWindow(task, TYPE_APPLICATION, "Enlightened Window");
+
+        BLASTSyncEngine bse = new BLASTSyncEngine();
+
+        BLASTSyncEngine.TransactionReadyListener transactionListener =
+            mock(BLASTSyncEngine.TransactionReadyListener.class);
+
+        int id = bse.startSyncSet(transactionListener);
+        bse.addToSyncSet(id, task);
+        bse.setReady(id);
+        // Since we have a window we have to wait for it to draw to finish sync.
+        verify(transactionListener, never())
+            .transactionReady(anyInt(), any());
+        w.finishDrawing(null);
+        verify(transactionListener)
+            .transactionReady(anyInt(), any());
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
new file mode 100644
index 0000000..35723ab
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -0,0 +1,60 @@
+/*
+ * 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 androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import android.content.pm.PackageManager;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class WindowManagerServiceTests extends WindowTestsBase {
+    @Rule
+    public ExpectedException mExpectedException = ExpectedException.none();
+
+    @Test
+    public void testForceShowSystemBarsThrowsExceptionForNonAutomotive() {
+        if (!isAutomotive()) {
+            mExpectedException.expect(UnsupportedOperationException.class);
+
+            mWm.setForceShowSystemBars(true);
+        }
+    }
+
+    @Test
+    public void testForceShowSystemBarsDoesNotThrowExceptionForAutomotiveWithStatusBarPermission() {
+        if (isAutomotive()) {
+            mExpectedException.none();
+
+            mWm.setForceShowSystemBars(true);
+        }
+    }
+
+    private boolean isAutomotive() {
+        return getInstrumentation().getTargetContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_AUTOMOTIVE);
+    }
+}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 6723522..e520a02 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -7761,9 +7761,8 @@
      *
      * @hide
      */
-    @SystemApi
     public static final int DEFAULT_PREFERRED_NETWORK_MODE =
-            RILConstants.DEFAULT_PREFERRED_NETWORK_MODE;
+            RILConstants.PREFERRED_NETWORK_MODE;
 
     /**
      * Get the preferred network type.
@@ -11290,14 +11289,6 @@
      */
     public static final int INDICATION_UPDATE_MODE_IGNORE_SCREEN_OFF        = 2;
 
-    /** @hide */
-    @IntDef(prefix = { "INDICATION_UPDATE_MODE_" }, value = {
-            INDICATION_UPDATE_MODE_NORMAL,
-            INDICATION_UPDATE_MODE_IGNORE_SCREEN_OFF
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface IndicationUpdateMode{}
-
     /**
      * The indication for signal strength update.
      * @hide
@@ -11328,51 +11319,6 @@
      */
     public static final int INDICATION_FILTER_PHYSICAL_CHANNEL_CONFIG       = 0x10;
 
-    /** @hide */
-    @IntDef(flag = true, prefix = { "INDICATION_FILTER_" }, value = {
-            INDICATION_FILTER_SIGNAL_STRENGTH,
-            INDICATION_FILTER_FULL_NETWORK_STATE,
-            INDICATION_FILTER_DATA_CALL_DORMANCY_CHANGED,
-            INDICATION_FILTER_LINK_CAPACITY_ESTIMATE,
-            INDICATION_FILTER_PHYSICAL_CHANNEL_CONFIG
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface IndicationFilters{}
-
-    /**
-     * Sets radio indication update mode. This can be used to control the behavior of indication
-     * update from modem to Android frameworks. For example, by default several indication updates
-     * are turned off when screen is off, but in some special cases (e.g. carkit is connected but
-     * screen is off) we want to turn on those indications even when the screen is off.
-     *
-     * <p>Requires Permission:
-     *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
-     *
-     * @param filters Indication filters. Should be a bitmask of INDICATION_FILTER_XXX.
-     * @see #INDICATION_FILTER_SIGNAL_STRENGTH
-     * @see #INDICATION_FILTER_FULL_NETWORK_STATE
-     * @see #INDICATION_FILTER_DATA_CALL_DORMANCY_CHANGED
-     * @param updateMode The voice activation state
-     * @see #INDICATION_UPDATE_MODE_NORMAL
-     * @see #INDICATION_UPDATE_MODE_IGNORE_SCREEN_OFF
-     * @hide
-     */
-    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
-    public void setRadioIndicationUpdateMode(@IndicationFilters int filters,
-                                             @IndicationUpdateMode int updateMode) {
-        try {
-            ITelephony telephony = getITelephony();
-            if (telephony != null) {
-                telephony.setRadioIndicationUpdateMode(getSubId(), filters, updateMode);
-            }
-        } catch (RemoteException ex) {
-            // This could happen if binder process crashes.
-            if (!isSystemProcess()) {
-                ex.rethrowAsRuntimeException();
-            }
-        }
-    }
-
     /**
      * A test API to override carrier information including mccmnc, imsi, iccid, gid1, gid2,
      * plmn and spn. This would be handy for, eg, forcing a particular carrier id, carrier's config
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index beb3c8c..168c8b6 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -1827,14 +1827,6 @@
     boolean switchSlots(in int[] physicalSlots);
 
     /**
-     * Sets radio indication update mode. This can be used to control the behavior of indication
-     * update from modem to Android frameworks. For example, by default several indication updates
-     * are turned off when screen is off, but in some special cases (e.g. carkit is connected but
-     * screen is off) we want to turn on those indications even when the screen is off.
-     */
-    void setRadioIndicationUpdateMode(int subId, int filters, int mode);
-
-    /**
      * Returns whether mobile data roaming is enabled on the subscription with id {@code subId}.
      *
      * @param subId the subscription id
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index 9ac8cb1..c40573b 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -233,14 +233,11 @@
     /** NR 5G, LTE, TD-SCDMA, CDMA, EVDO, GSM and WCDMA */
     int NETWORK_MODE_NR_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA = 33;
 
-    /** Default preferred network mode */
-    int DEFAULT_PREFERRED_NETWORK_MODE = NETWORK_MODE_WCDMA_PREF;
-
     @UnsupportedAppUsage
     int PREFERRED_NETWORK_MODE = Optional.of(TelephonyProperties.default_network())
             .filter(list -> !list.isEmpty())
             .map(list -> list.get(0))
-            .orElse(DEFAULT_PREFERRED_NETWORK_MODE);
+            .orElse(NETWORK_MODE_WCDMA_PREF);
 
     int BAND_MODE_UNSPECIFIED = 0;      //"unspecified" (selected by baseband automatically)
     int BAND_MODE_EURO = 1;             //"EURO band" (GSM-900 / DCS-1800 / WCDMA-IMT-2000)
diff --git a/test-mock/src/android/test/mock/MockPackageManager.java b/test-mock/src/android/test/mock/MockPackageManager.java
index 14b847f..5f95bc1 100644
--- a/test-mock/src/android/test/mock/MockPackageManager.java
+++ b/test-mock/src/android/test/mock/MockPackageManager.java
@@ -1249,12 +1249,4 @@
             int uid, byte[] certificate, @PackageManager.CertificateInputType int type) {
         throw new UnsupportedOperationException();
     }
-
-    /**
-     * @hide
-     */
-    @Override
-    public String getSystemTextClassifierPackageName() {
-        throw new UnsupportedOperationException();
-    }
 }
diff --git a/tests/BlobStoreTestUtils/Android.bp b/tests/BlobStoreTestUtils/Android.bp
new file mode 100644
index 0000000..edd2b43
--- /dev/null
+++ b/tests/BlobStoreTestUtils/Android.bp
@@ -0,0 +1,20 @@
+// 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.
+
+java_library {
+  name: "BlobStoreTestUtils",
+  srcs: ["src/**/*.java"],
+  static_libs: ["truth-prebuilt"],
+  platform_apis: true
+}
\ No newline at end of file
diff --git a/tests/BlobStoreTestUtils/src/com/android/utils/blob/DummyBlobData.java b/tests/BlobStoreTestUtils/src/com/android/utils/blob/DummyBlobData.java
new file mode 100644
index 0000000..f96766a
--- /dev/null
+++ b/tests/BlobStoreTestUtils/src/com/android/utils/blob/DummyBlobData.java
@@ -0,0 +1,227 @@
+/*
+ * 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.utils.blob;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.blob.BlobHandle;
+import android.app.blob.BlobStoreManager;
+import android.content.Context;
+import android.os.FileUtils;
+import android.os.ParcelFileDescriptor;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.nio.file.Files;
+import java.security.MessageDigest;
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+
+public class DummyBlobData {
+    private static final long DEFAULT_SIZE_BYTES = 10 * 1024L * 1024L;
+    private static final int BUFFER_SIZE_BYTES = 16 * 1024;
+
+    private final Context mContext;
+    private final Random mRandom;
+    private final File mFile;
+    private final long mFileSize;
+    private final String mLabel;
+
+    byte[] mFileDigest;
+    long mExpiryTimeMs;
+
+    public DummyBlobData(Context context) {
+        this(context, new Random(0), "blob_" + System.nanoTime());
+    }
+
+    public DummyBlobData(Context context, long fileSize) {
+        this(context, fileSize, new Random(0), "blob_" + System.nanoTime(), "Test label");
+    }
+
+    public DummyBlobData(Context context, Random random, String fileName) {
+        this(context, DEFAULT_SIZE_BYTES, random, fileName, "Test label");
+    }
+
+    public DummyBlobData(Context context, Random random, String fileName, String label) {
+        this(context, DEFAULT_SIZE_BYTES, random, fileName, label);
+    }
+
+    public DummyBlobData(Context context, long fileSize, Random random, String fileName,
+            String label) {
+        mContext = context;
+        mRandom = random;
+        mFile = new File(mContext.getFilesDir(), fileName);
+        mFileSize = fileSize;
+        mLabel = label;
+    }
+
+    public void prepare() throws Exception {
+        try (RandomAccessFile file = new RandomAccessFile(mFile, "rw")) {
+            writeRandomData(file, mFileSize);
+        }
+        mFileDigest = FileUtils.digest(mFile, "SHA-256");
+        mExpiryTimeMs = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1);
+    }
+
+    public BlobHandle getBlobHandle() throws Exception {
+        return BlobHandle.createWithSha256(createSha256Digest(mFile), mLabel,
+                mExpiryTimeMs, "test_tag");
+    }
+
+    public long getFileSize() throws Exception {
+        return mFileSize;
+    }
+
+    public long getExpiryTimeMillis() {
+        return mExpiryTimeMs;
+    }
+
+    public void delete() {
+        mFile.delete();
+    }
+
+    public void writeToSession(BlobStoreManager.Session session) throws Exception {
+        writeToSession(session, 0, mFileSize);
+    }
+
+    public void writeToSession(BlobStoreManager.Session session,
+            long offsetBytes, long lengthBytes) throws Exception {
+        try (FileInputStream in = new FileInputStream(mFile)) {
+            in.getChannel().position(offsetBytes);
+            try (FileOutputStream out = new ParcelFileDescriptor.AutoCloseOutputStream(
+                    session.openWrite(offsetBytes, lengthBytes))) {
+                copy(in, out, lengthBytes);
+            }
+        }
+    }
+
+    public void writeToFd(FileDescriptor fd, long offsetBytes, long lengthBytes) throws Exception {
+        try (FileInputStream in = new FileInputStream(mFile)) {
+            in.getChannel().position(offsetBytes);
+            try (FileOutputStream out = new FileOutputStream(fd)) {
+                copy(in, out, lengthBytes);
+            }
+        }
+    }
+
+    private void copy(InputStream in, OutputStream out, long lengthBytes) throws Exception {
+        final byte[] buffer = new byte[BUFFER_SIZE_BYTES];
+        long bytesWrittern = 0;
+        while (bytesWrittern < lengthBytes) {
+            final int toWrite = (bytesWrittern + buffer.length <= lengthBytes)
+                    ? buffer.length : (int) (lengthBytes - bytesWrittern);
+            in.read(buffer, 0, toWrite);
+            out.write(buffer, 0, toWrite);
+            bytesWrittern += toWrite;
+        }
+    }
+
+    public void readFromSessionAndVerifyBytes(BlobStoreManager.Session session,
+            long offsetBytes, int lengthBytes) throws Exception {
+        final byte[] expectedBytes = new byte[lengthBytes];
+        try (FileInputStream in = new FileInputStream(mFile)) {
+            read(in, expectedBytes, offsetBytes, lengthBytes);
+        }
+
+        final byte[] actualBytes = new byte[lengthBytes];
+        try (FileInputStream in = new ParcelFileDescriptor.AutoCloseInputStream(
+                session.openWrite(0L, 0L))) {
+            read(in, actualBytes, offsetBytes, lengthBytes);
+        }
+
+        assertThat(actualBytes).isEqualTo(expectedBytes);
+
+    }
+
+    private void read(FileInputStream in, byte[] buffer,
+            long offsetBytes, int lengthBytes) throws Exception {
+        in.getChannel().position(offsetBytes);
+        in.read(buffer, 0, lengthBytes);
+    }
+
+    public void readFromSessionAndVerifyDigest(BlobStoreManager.Session session)
+            throws Exception {
+        readFromSessionAndVerifyDigest(session, 0, mFile.length());
+    }
+
+    public void readFromSessionAndVerifyDigest(BlobStoreManager.Session session,
+            long offsetBytes, long lengthBytes) throws Exception {
+        final byte[] actualDigest;
+        try (FileInputStream in = new ParcelFileDescriptor.AutoCloseInputStream(
+                session.openWrite(0L, 0L))) {
+            actualDigest = createSha256Digest(in, offsetBytes, lengthBytes);
+        }
+
+        assertThat(actualDigest).isEqualTo(mFileDigest);
+    }
+
+    public void verifyBlob(ParcelFileDescriptor pfd) throws Exception {
+        final byte[] actualDigest;
+        try (FileInputStream in = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) {
+            actualDigest = FileUtils.digest(in, "SHA-256");
+        }
+        assertThat(actualDigest).isEqualTo(mFileDigest);
+    }
+
+    private byte[] createSha256Digest(FileInputStream in, long offsetBytes, long lengthBytes)
+            throws Exception {
+        final MessageDigest digest = MessageDigest.getInstance("SHA-256");
+        in.getChannel().position(offsetBytes);
+        final byte[] buffer = new byte[BUFFER_SIZE_BYTES];
+        long bytesRead = 0;
+        while (bytesRead < lengthBytes) {
+            int toRead = (bytesRead + buffer.length <= lengthBytes)
+                    ? buffer.length : (int) (lengthBytes - bytesRead);
+            toRead = in.read(buffer, 0, toRead);
+            digest.update(buffer, 0, toRead);
+            bytesRead += toRead;
+        }
+        return digest.digest();
+    }
+
+    private byte[] createSha256Digest(File file) throws Exception {
+        final MessageDigest digest = MessageDigest.getInstance("SHA-256");
+        try (BufferedInputStream in = new BufferedInputStream(
+                Files.newInputStream(file.toPath()))) {
+            final byte[] buffer = new byte[BUFFER_SIZE_BYTES];
+            int bytesRead;
+            while ((bytesRead = in.read(buffer)) > 0) {
+                digest.update(buffer, 0, bytesRead);
+            }
+        }
+        return digest.digest();
+    }
+
+    private void writeRandomData(RandomAccessFile file, long fileSize)
+            throws Exception {
+        long bytesWritten = 0;
+        final byte[] buffer = new byte[BUFFER_SIZE_BYTES];
+        while (bytesWritten < fileSize) {
+            mRandom.nextBytes(buffer);
+            final int toWrite = (bytesWritten + buffer.length <= fileSize)
+                    ? buffer.length : (int) (fileSize - bytesWritten);
+            file.seek(bytesWritten);
+            file.write(buffer, 0, toWrite);
+            bytesWritten += toWrite;
+        }
+    }
+}
diff --git a/tests/PlatformCompatGating/Android.bp b/tests/PlatformCompatGating/Android.bp
index 5e9ef8e..74dfde8 100644
--- a/tests/PlatformCompatGating/Android.bp
+++ b/tests/PlatformCompatGating/Android.bp
@@ -18,14 +18,11 @@
     name: "PlatformCompatGating",
     // Only compile source java files in this apk.
     srcs: ["src/**/*.java"],
-    certificate: "platform",
-    libs: [
-        "android.test.runner",
-        "android.test.base",
-    ],
     static_libs: [
         "junit",
-        "android-support-test",
+        "androidx.test.runner",
+        "androidx.test.core",
+        "androidx.test.ext.junit",
         "mockito-target-minus-junit4",
         "truth-prebuilt",
         "platform-compat-test-rules"
diff --git a/tests/PlatformCompatGating/AndroidManifest.xml b/tests/PlatformCompatGating/AndroidManifest.xml
index 7f14b83..c24dc31 100644
--- a/tests/PlatformCompatGating/AndroidManifest.xml
+++ b/tests/PlatformCompatGating/AndroidManifest.xml
@@ -6,6 +6,6 @@
         <uses-library android:name="android.test.runner" />
     </application>
 
-    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
                      android:targetPackage="com.android.tests.gating"/>
 </manifest>
diff --git a/tests/PlatformCompatGating/AndroidTest.xml b/tests/PlatformCompatGating/AndroidTest.xml
index c626848..0c7485b 100644
--- a/tests/PlatformCompatGating/AndroidTest.xml
+++ b/tests/PlatformCompatGating/AndroidTest.xml
@@ -24,7 +24,6 @@
 
     <test class="com.android.tradefed.testtype.AndroidJUnitTest">
         <option name="package" value="com.android.tests.gating"/>
-        <option name="runner" value="android.support.test.runner.AndroidJUnitRunner"/>
         <option name="hidden-api-checks" value="false"/>
     </test>
 </configuration>
diff --git a/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatGatingTest.java b/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatGatingTest.java
index dc317f19..c1ce0e9 100644
--- a/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatGatingTest.java
+++ b/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatGatingTest.java
@@ -18,8 +18,9 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import android.compat.testing.PlatformCompatChangeRule;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.compat.testing.DummyApi;
 
@@ -81,14 +82,14 @@
     @Test
     @EnableCompatChanges({DummyApi.CHANGE_SYSTEM_SERVER})
     public void testDummyGatingPositiveSystemServer() {
-        assertThat(
-                DummyApi.dummySystemServer(InstrumentationRegistry.getTargetContext())).isTrue();
+        assertThat(DummyApi.dummySystemServer(
+                InstrumentationRegistry.getInstrumentation().getTargetContext())).isTrue();
     }
 
     @Test
     @DisableCompatChanges({DummyApi.CHANGE_SYSTEM_SERVER})
     public void testDummyGatingNegativeSystemServer() {
-        assertThat(
-                DummyApi.dummySystemServer(InstrumentationRegistry.getTargetContext())).isFalse();
+        assertThat(DummyApi.dummySystemServer(
+                InstrumentationRegistry.getInstrumentation().getTargetContext())).isFalse();
     }
 }
diff --git a/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatPermissionsTest.java b/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatPermissionsTest.java
new file mode 100644
index 0000000..9b9e581
--- /dev/null
+++ b/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatPermissionsTest.java
@@ -0,0 +1,319 @@
+/*
+ * 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.tests.gating;
+
+import static android.Manifest.permission.LOG_COMPAT_CHANGE;
+import static android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG;
+import static android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG;
+
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.compat.Compatibility.ChangeConfig;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Process;
+import android.os.ServiceManager;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.internal.compat.CompatibilityChangeConfig;
+import com.android.internal.compat.IPlatformCompat;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.HashSet;
+import java.util.Set;
+
+@RunWith(JUnit4.class)
+public final class PlatformCompatPermissionsTest {
+
+    // private Context mContext;
+    private IPlatformCompat mPlatformCompat;
+
+    @Rule
+    public final ExpectedException thrown = ExpectedException.none();
+    private Context mContext;
+    private UiAutomation mUiAutomation;
+    private PackageManager mPackageManager;
+
+    @Before
+    public void setUp() {
+        // mContext;
+        mPlatformCompat = IPlatformCompat.Stub
+            .asInterface(ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        mUiAutomation = instrumentation.getUiAutomation();
+        mContext = instrumentation.getTargetContext();
+
+        mPackageManager = mContext.getPackageManager();
+    }
+
+    @After
+    public void tearDown() {
+
+        mUiAutomation.dropShellPermissionIdentity();
+    }
+
+    @Test
+    public void reportChange_noLogCompatChangePermission_throwsSecurityException()
+            throws Throwable {
+        thrown.expect(SecurityException.class);
+        final String packageName = mContext.getPackageName();
+
+        mPlatformCompat.reportChange(1, mPackageManager.getApplicationInfo(packageName, 0));
+    }
+
+    @Test
+    public void reportChange_logCompatChangePermission_noThrow()
+            throws Throwable {
+        mUiAutomation.adoptShellPermissionIdentity(LOG_COMPAT_CHANGE);
+        final String packageName = mContext.getPackageName();
+
+        mPlatformCompat.reportChange(1, mPackageManager.getApplicationInfo(packageName, 0));
+    }
+
+    @Test
+    public void reportChangeByPackageName_noLogCompatChangePermission_throwsSecurityException()
+            throws Throwable {
+        thrown.expect(SecurityException.class);
+        final String packageName = mContext.getPackageName();
+
+        mPlatformCompat.reportChangeByPackageName(1, packageName, 0);
+    }
+
+    @Test
+    public void reportChangeByPackageName_logCompatChangePermission_noThrow()
+            throws Throwable {
+        mUiAutomation.adoptShellPermissionIdentity(LOG_COMPAT_CHANGE);
+        final String packageName = mContext.getPackageName();
+
+        mPlatformCompat.reportChangeByPackageName(1, packageName, 0);
+    }
+
+    @Test
+    public void reportChangeByUid_noLogCompatChangePermission_throwsSecurityException()
+            throws Throwable {
+        thrown.expect(SecurityException.class);
+
+        mPlatformCompat.reportChangeByUid(1, Process.myUid());
+    }
+
+    @Test
+    public void reportChangeByUid_logCompatChangePermission_noThrow()
+            throws Throwable {
+        mUiAutomation.adoptShellPermissionIdentity(LOG_COMPAT_CHANGE);
+
+        mPlatformCompat.reportChangeByUid(1, Process.myUid());
+    }
+
+    @Test
+    public void isChangeEnabled_noReadCompatConfigPermission_throwsSecurityException()
+            throws Throwable {
+        thrown.expect(SecurityException.class);
+        final String packageName = mContext.getPackageName();
+
+        mPlatformCompat.isChangeEnabled(1, mPackageManager.getApplicationInfo(packageName, 0));
+    }
+
+    @Test
+    public void isChangeEnabled_noLogCompatChangeConfigPermission_throwsSecurityException()
+            throws Throwable {
+        thrown.expect(SecurityException.class);
+        mUiAutomation.adoptShellPermissionIdentity(READ_COMPAT_CHANGE_CONFIG);
+        final String packageName = mContext.getPackageName();
+
+        mPlatformCompat.isChangeEnabled(1, mPackageManager.getApplicationInfo(packageName, 0));
+    }
+
+    @Test
+    public void isChangeEnabled_readAndLogCompatChangeConfigPermission_noThrow()
+            throws Throwable {
+        mUiAutomation.adoptShellPermissionIdentity(READ_COMPAT_CHANGE_CONFIG, LOG_COMPAT_CHANGE);
+        final String packageName = mContext.getPackageName();
+
+        mPlatformCompat.isChangeEnabled(1, mPackageManager.getApplicationInfo(packageName, 0));
+    }
+
+    @Test
+    public void isChangeEnabledByPackageName_noReadCompatConfigPermission_throwsSecurityException()
+            throws Throwable {
+        thrown.expect(SecurityException.class);
+        final String packageName = mContext.getPackageName();
+
+        mPlatformCompat.isChangeEnabledByPackageName(1, packageName, 0);
+    }
+
+    @Test
+    public void isChangeEnabledByPackageName_noLogompatConfigPermission_throwsSecurityException()
+            throws Throwable {
+        thrown.expect(SecurityException.class);
+        mUiAutomation.adoptShellPermissionIdentity(READ_COMPAT_CHANGE_CONFIG);
+        final String packageName = mContext.getPackageName();
+
+        mPlatformCompat.isChangeEnabledByPackageName(1, packageName, 0);
+    }
+
+    @Test
+    public void isChangeEnabledByPackageName_readAndLogCompatChangeConfigPermission_noThrow()
+            throws Throwable {
+        mUiAutomation.adoptShellPermissionIdentity(READ_COMPAT_CHANGE_CONFIG, LOG_COMPAT_CHANGE);
+        final String packageName = mContext.getPackageName();
+
+        mPlatformCompat.isChangeEnabledByPackageName(1, packageName, 0);
+    }
+
+    @Test
+    public void isChangeEnabledByUid_noReadCompatConfigPermission_throwsSecurityException()
+            throws Throwable {
+        thrown.expect(SecurityException.class);
+
+        mPlatformCompat.isChangeEnabledByUid(1, Process.myUid());
+    }
+
+    @Test
+    public void isChangeEnabledByUid_noLogCompatChangePermission_throwsSecurityException()
+            throws Throwable {
+        thrown.expect(SecurityException.class);
+        mUiAutomation.adoptShellPermissionIdentity(READ_COMPAT_CHANGE_CONFIG);
+
+        mPlatformCompat.isChangeEnabledByUid(1, Process.myUid());
+    }
+
+    @Test
+    public void isChangeEnabledByUid_readAndLogCompatChangeConfigPermission_noThrow()
+            throws Throwable {
+        mUiAutomation.adoptShellPermissionIdentity(READ_COMPAT_CHANGE_CONFIG, LOG_COMPAT_CHANGE);
+
+        mPlatformCompat.isChangeEnabledByUid(1, Process.myUid());
+    }
+
+    @Test
+    public void setOverrides_noOverridesPermission_throwsSecurityException()
+            throws Throwable {
+        thrown.expect(SecurityException.class);
+        Set<Long> enabled = new HashSet<>();
+        Set<Long> disabled = new HashSet<>();
+        ChangeConfig changeConfig = new ChangeConfig(enabled, disabled);
+        CompatibilityChangeConfig compatibilityChangeConfig =
+                new CompatibilityChangeConfig(changeConfig);
+
+        mPlatformCompat.setOverrides(compatibilityChangeConfig, "foo.bar");
+    }
+    @Test
+    public void setOverrides_overridesPermission_noThrow()
+            throws Throwable {
+        mUiAutomation.adoptShellPermissionIdentity(OVERRIDE_COMPAT_CHANGE_CONFIG);
+        Set<Long> enabled = new HashSet<>();
+        Set<Long> disabled = new HashSet<>();
+        ChangeConfig changeConfig = new ChangeConfig(enabled, disabled);
+        CompatibilityChangeConfig compatibilityChangeConfig =
+                new CompatibilityChangeConfig(changeConfig);
+
+        mPlatformCompat.setOverrides(compatibilityChangeConfig, "foo.bar");
+    }
+
+    @Test
+    public void setOverridesForTest_noOverridesPermission_throwsSecurityException()
+            throws Throwable {
+        thrown.expect(SecurityException.class);
+        Set<Long> enabled = new HashSet<>();
+        Set<Long> disabled = new HashSet<>();
+        ChangeConfig changeConfig = new ChangeConfig(enabled, disabled);
+        CompatibilityChangeConfig compatibilityChangeConfig =
+                new CompatibilityChangeConfig(changeConfig);
+
+        mPlatformCompat.setOverridesForTest(compatibilityChangeConfig, "foo.bar");
+    }
+    @Test
+    public void setOverridesForTest_overridesPermission_noThrow()
+            throws Throwable {
+        mUiAutomation.adoptShellPermissionIdentity(OVERRIDE_COMPAT_CHANGE_CONFIG);
+        Set<Long> enabled = new HashSet<>();
+        Set<Long> disabled = new HashSet<>();
+        ChangeConfig changeConfig = new ChangeConfig(enabled, disabled);
+        CompatibilityChangeConfig compatibilityChangeConfig =
+                new CompatibilityChangeConfig(changeConfig);
+
+        mPlatformCompat.setOverridesForTest(compatibilityChangeConfig, "foo.bar");
+    }
+
+    @Test
+    public void clearOverrides_noOverridesPermission_throwsSecurityException()
+            throws Throwable {
+        thrown.expect(SecurityException.class);
+
+        mPlatformCompat.clearOverrides("foo.bar");
+    }
+    @Test
+    public void clearOverrides_overridesPermission_noThrow()
+            throws Throwable {
+        mUiAutomation.adoptShellPermissionIdentity(OVERRIDE_COMPAT_CHANGE_CONFIG);
+
+        mPlatformCompat.clearOverrides("foo.bar");
+    }
+
+    @Test
+    public void clearOverridesForTest_noOverridesPermission_throwsSecurityException()
+            throws Throwable {
+        thrown.expect(SecurityException.class);
+
+        mPlatformCompat.clearOverridesForTest("foo.bar");
+    }
+    @Test
+    public void clearOverridesForTest_overridesPermission_noThrow()
+            throws Throwable {
+        mUiAutomation.adoptShellPermissionIdentity(OVERRIDE_COMPAT_CHANGE_CONFIG);
+
+        mPlatformCompat.clearOverridesForTest("foo.bar");
+    }
+
+    @Test
+    public void clearOverride_noOverridesPermission_throwsSecurityException()
+            throws Throwable {
+        thrown.expect(SecurityException.class);
+
+        mPlatformCompat.clearOverride(1, "foo.bar");
+    }
+    @Test
+    public void clearOverride_overridesPermission_noThrow()
+            throws Throwable {
+        mUiAutomation.adoptShellPermissionIdentity(OVERRIDE_COMPAT_CHANGE_CONFIG);
+
+        mPlatformCompat.clearOverride(1, "foo.bar");
+    }
+
+    @Test
+    public void listAllChanges_noReadCompatConfigPermission_throwsSecurityException()
+            throws Throwable {
+        thrown.expect(SecurityException.class);
+
+        mPlatformCompat.listAllChanges();
+    }
+    @Test
+    public void listAllChanges_readCompatConfigPermission_noThrow()
+            throws Throwable {
+        mUiAutomation.adoptShellPermissionIdentity(READ_COMPAT_CHANGE_CONFIG);
+
+        mPlatformCompat.listAllChanges();
+    }
+}
diff --git a/tests/PlatformCompatGating/test-rules/Android.bp b/tests/PlatformCompatGating/test-rules/Android.bp
index 8211ef5..10fa2dc 100644
--- a/tests/PlatformCompatGating/test-rules/Android.bp
+++ b/tests/PlatformCompatGating/test-rules/Android.bp
@@ -19,7 +19,7 @@
     srcs: ["src/**/*.java"],
     static_libs: [
         "junit",
-        "android-support-test",
+        "androidx.test.core",
         "truth-prebuilt",
         "core-compat-test-rules"
     ],
diff --git a/tests/PlatformCompatGating/test-rules/src/android/compat/testing/PlatformCompatChangeRule.java b/tests/PlatformCompatGating/test-rules/src/android/compat/testing/PlatformCompatChangeRule.java
index 932ec64..d6846fa 100644
--- a/tests/PlatformCompatGating/test-rules/src/android/compat/testing/PlatformCompatChangeRule.java
+++ b/tests/PlatformCompatGating/test-rules/src/android/compat/testing/PlatformCompatChangeRule.java
@@ -16,13 +16,17 @@
 
 package android.compat.testing;
 
+import android.Manifest;
 import android.app.Instrumentation;
+import android.app.UiAutomation;
 import android.compat.Compatibility;
 import android.compat.Compatibility.ChangeConfig;
 import android.content.Context;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.support.test.InstrumentationRegistry;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
 
 import com.android.internal.compat.CompatibilityChangeConfig;
 import com.android.internal.compat.IPlatformCompat;
@@ -83,12 +87,17 @@
         @Override
         public void evaluate() throws Throwable {
             Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+            UiAutomation uiAutomation = instrumentation.getUiAutomation();
             String packageName = instrumentation.getTargetContext().getPackageName();
             IPlatformCompat platformCompat = IPlatformCompat.Stub
                     .asInterface(ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
             if (platformCompat == null) {
                 throw new IllegalStateException("Could not get IPlatformCompat service!");
             }
+            uiAutomation.adoptShellPermissionIdentity(
+                    Manifest.permission.LOG_COMPAT_CHANGE,
+                    Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG,
+                    Manifest.permission.READ_COMPAT_CHANGE_CONFIG);
             Compatibility.setOverrides(mConfig);
             try {
                 platformCompat.setOverridesForTest(new CompatibilityChangeConfig(mConfig),
@@ -101,6 +110,7 @@
             } catch (RemoteException e) {
                 throw new RuntimeException("Could not call IPlatformCompat binder method!", e);
             } finally {
+                uiAutomation.dropShellPermissionIdentity();
                 Compatibility.clearOverrides();
             }
         }
diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
index 82a524b..032f182 100644
--- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
+++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
@@ -23,6 +23,8 @@
 import static org.testng.Assert.assertThrows;
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.tradefed.device.LogcatReceiver;
+import com.android.tradefed.result.InputStreamSource;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
@@ -31,11 +33,18 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.BufferedReader;
 import java.io.File;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.TimeUnit;
 
 /**
  * Runs the staged rollback tests.
+ *
+ * TODO(gavincorkery): Support the verification of logging parents in Watchdog metrics.
  */
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class StagedRollbackTest extends BaseHostJUnit4Test {
@@ -54,6 +63,7 @@
     }
 
     private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test";
+    private static final String TESTAPP_A = "com.android.cts.install.lib.testapp.A";
 
     private static final String TEST_SUBDIR = "/subdir/";
 
@@ -66,8 +76,19 @@
     private static final String TEST_FILENAME_4 = "one_more.test";
     private static final String TEST_STRING_4 = "once more unto the test";
 
+    private static final String REASON_APP_CRASH = "REASON_APP_CRASH";
+    private static final String REASON_NATIVE_CRASH = "REASON_NATIVE_CRASH";
+    private static final String REASON_EXPLICIT_HEALTH_CHECK = "REASON_EXPLICIT_HEALTH_CHECK";
+
+    private static final String ROLLBACK_INITIATE = "ROLLBACK_INITIATE";
+    private static final String ROLLBACK_BOOT_TRIGGERED = "ROLLBACK_BOOT_TRIGGERED";
+
+    private LogcatReceiver mReceiver;
+
     @Before
     public void setUp() throws Exception {
+        mReceiver =  new LogcatReceiver(getDevice(), "logcat -s WatchdogRollbackLogger",
+                getDevice().getOptions().getMaxLogcatDataSize(), 0);
         if (!getDevice().isAdbRoot()) {
             getDevice().enableAdbRoot();
         }
@@ -77,10 +98,13 @@
                         + "/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex");
         getDevice().reboot();
         runPhase("testCleanUp");
+        mReceiver.start();
     }
 
     @After
     public void tearDown() throws Exception {
+        mReceiver.stop();
+        mReceiver.clear();
         runPhase("testCleanUp");
 
         if (!getDevice().isAdbRoot()) {
@@ -110,6 +134,16 @@
         getDevice().waitForDeviceAvailable();
 
         runPhase("testBadApkOnly_Phase4");
+        InputStreamSource logcatStream = mReceiver.getLogcatData();
+        try {
+            List<String> watchdogEvents = getWatchdogLoggingEvents(logcatStream);
+            assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_INITIATE, null,
+                    REASON_APP_CRASH, TESTAPP_A));
+            assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_BOOT_TRIGGERED, null,
+                    null, null));
+        } finally {
+            logcatStream.close();
+        }
     }
 
     @Test
@@ -137,6 +171,16 @@
 
         // verify rollback committed
         runPhase("testNativeWatchdogTriggersRollback_Phase3");
+        InputStreamSource logcatStream = mReceiver.getLogcatData();
+        try {
+            List<String> watchdogEvents = getWatchdogLoggingEvents(logcatStream);
+            assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_INITIATE, null,
+                            REASON_NATIVE_CRASH, null));
+            assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_BOOT_TRIGGERED, null,
+                    null, null));
+        } finally {
+            logcatStream.close();
+        }
     }
 
     @Test
@@ -171,6 +215,16 @@
 
         // verify all available rollbacks have been committed
         runPhase("testNativeWatchdogTriggersRollbackForAll_Phase4");
+        InputStreamSource logcatStream = mReceiver.getLogcatData();
+        try {
+            List<String> watchdogEvents = getWatchdogLoggingEvents(logcatStream);
+            assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_INITIATE, null,
+                            REASON_NATIVE_CRASH, null));
+            assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_BOOT_TRIGGERED, null,
+                    null, null));
+        } finally {
+            logcatStream.close();
+        }
     }
 
     /**
@@ -194,6 +248,16 @@
             getDevice().waitForDeviceAvailable();
             // Verify rollback was executed after health check deadline
             runPhase("testNetworkFailedRollback_Phase4");
+            InputStreamSource logcatStream = mReceiver.getLogcatData();
+            try {
+                List<String> watchdogEvents = getWatchdogLoggingEvents(logcatStream);
+                assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_INITIATE, null,
+                                REASON_EXPLICIT_HEALTH_CHECK, null));
+                assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_BOOT_TRIGGERED, null,
+                        null, null));
+            } finally {
+                logcatStream.close();
+            }
         } finally {
             // Reconnect internet again so we won't break tests which assume internet available
             getDevice().executeShellCommand("svc wifi enable");
@@ -223,6 +287,15 @@
 
         // Verify rollback was not executed after health check deadline
         runPhase("testNetworkPassedDoesNotRollback_Phase3");
+        InputStreamSource logcatStream = mReceiver.getLogcatData();
+        try {
+            List<String> watchdogEvents = getWatchdogLoggingEvents(logcatStream);
+            assertEquals(watchdogEventOccurred(watchdogEvents, null, null,
+                    REASON_EXPLICIT_HEALTH_CHECK, null), false);
+        } finally {
+            logcatStream.close();
+        }
+
     }
 
     /**
@@ -288,6 +361,16 @@
         getDevice().waitForDeviceAvailable();
         // Verify rollback occurred due to crash of apk-in-apex
         runPhase("testRollbackApexWithApkCrashing_Phase3");
+        InputStreamSource logcatStream = mReceiver.getLogcatData();
+        try {
+            List<String> watchdogEvents = getWatchdogLoggingEvents(logcatStream);
+            assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_INITIATE, null,
+                    REASON_APP_CRASH, TESTAPP_A));
+            assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_BOOT_TRIGGERED, null,
+                    null, null));
+        } finally {
+            logcatStream.close();
+        }
     }
 
     /**
@@ -457,4 +540,57 @@
             return false;
         }
     }
+
+    /**
+     * Returns a list of all Watchdog logging events which have occurred.
+     */
+    private List<String> getWatchdogLoggingEvents(InputStreamSource inputStreamSource)
+            throws Exception {
+        List<String> watchdogEvents = new ArrayList<>();
+        InputStream inputStream = inputStreamSource.createInputStream();
+        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
+        String line;
+        while ((line = reader.readLine()) != null) {
+            if (line.contains("Watchdog event occurred")) {
+                watchdogEvents.add(line);
+            }
+        }
+        return watchdogEvents;
+    }
+
+    /**
+     * Returns whether a Watchdog event has occurred that matches the given criteria.
+     *
+     * Check the value of all non-null parameters against the list of Watchdog events that have
+     * occurred, and return {@code true} if an event exists which matches all criteria.
+     */
+    private boolean watchdogEventOccurred(List<String> loggingEvents,
+            String type, String logPackage,
+            String rollbackReason, String failedPackageName) throws Exception {
+        List<String> eventCriteria = new ArrayList<>();
+        if (type != null) {
+            eventCriteria.add("type: " + type);
+        }
+        if (logPackage != null) {
+            eventCriteria.add("logPackage: " + logPackage);
+        }
+        if (rollbackReason != null) {
+            eventCriteria.add("rollbackReason: " + rollbackReason);
+        }
+        if (failedPackageName != null) {
+            eventCriteria.add("failedPackageName: " + failedPackageName);
+        }
+        for (String loggingEvent: loggingEvents) {
+            boolean matchesCriteria = true;
+            for (String criterion: eventCriteria) {
+                if (!loggingEvent.contains(criterion)) {
+                    matchesCriteria = false;
+                }
+            }
+            if (matchesCriteria) {
+                return true;
+            }
+        }
+        return false;
+    }
 }
diff --git a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java
index 8f3cb34..bdfaaa8 100644
--- a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java
+++ b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java
@@ -45,7 +45,7 @@
             final WindowContainerTransaction wct = new WindowContainerTransaction();
             wct.scheduleFinishEnterPip(ti.token, new Rect(0, 0, PIP_WIDTH, PIP_HEIGHT));
             try {
-                ActivityTaskManager.getTaskOrganizerController().applyContainerTransaction(wct);
+                ActivityTaskManager.getTaskOrganizerController().applyContainerTransaction(wct, null);
             } catch (Exception e) {
             }
         }
diff --git a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
index 3e4f3d8..efea91a 100644
--- a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
@@ -272,10 +272,24 @@
         netCap.setOwnerUid(123);
         assertParcelingIsLossless(netCap);
         netCap.setSSID(TEST_SSID);
-        assertParcelSane(netCap, 13);
+        assertParcelSane(netCap, 15);
     }
 
     @Test
+    public void testParcelNetworkCapabilitiesWithRequestorUidAndPackageName() {
+        final NetworkCapabilities netCap = new NetworkCapabilities()
+                .addCapability(NET_CAPABILITY_INTERNET)
+                .setRequestorUid(9304)
+                .setRequestorPackageName("com.android.test")
+                .addCapability(NET_CAPABILITY_EIMS)
+                .addCapability(NET_CAPABILITY_NOT_METERED);
+        assertParcelingIsLossless(netCap);
+        netCap.setSSID(TEST_SSID);
+        assertParcelSane(netCap, 15);
+    }
+
+
+    @Test
     public void testOemPaid() {
         NetworkCapabilities nc = new NetworkCapabilities();
         // By default OEM_PAID is neither in the unwanted or required lists and the network is not
diff --git a/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java b/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java
index 2d5df4f..0628691 100644
--- a/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java
+++ b/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java
@@ -38,6 +38,8 @@
 import android.content.Context;
 import android.os.PersistableBundle;
 
+import androidx.test.InstrumentationRegistry;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -58,21 +60,26 @@
 
     private static final Executor INLINE_EXECUTOR = x -> x.run();
 
-    @Mock private Context mContext;
     @Mock private IConnectivityManager mService;
     @Mock private ConnectivityDiagnosticsCallback mCb;
 
+    private Context mContext;
     private ConnectivityDiagnosticsBinder mBinder;
     private ConnectivityDiagnosticsManager mManager;
 
+    private String mPackageName;
+
     @Before
     public void setUp() {
-        mContext = mock(Context.class);
+        mContext = InstrumentationRegistry.getContext();
+
         mService = mock(IConnectivityManager.class);
         mCb = mock(ConnectivityDiagnosticsCallback.class);
 
         mBinder = new ConnectivityDiagnosticsBinder(mCb, INLINE_EXECUTOR);
         mManager = new ConnectivityDiagnosticsManager(mContext, mService);
+
+        mPackageName = mContext.getOpPackageName();
     }
 
     @After
@@ -271,7 +278,7 @@
         mManager.registerConnectivityDiagnosticsCallback(request, INLINE_EXECUTOR, mCb);
 
         verify(mService).registerConnectivityDiagnosticsCallback(
-                any(ConnectivityDiagnosticsBinder.class), eq(request));
+                any(ConnectivityDiagnosticsBinder.class), eq(request), eq(mPackageName));
         assertTrue(ConnectivityDiagnosticsManager.sCallbacks.containsKey(mCb));
     }
 
@@ -302,7 +309,7 @@
         // verify that re-registering is successful
         mManager.registerConnectivityDiagnosticsCallback(request, INLINE_EXECUTOR, mCb);
         verify(mService, times(2)).registerConnectivityDiagnosticsCallback(
-                any(ConnectivityDiagnosticsBinder.class), eq(request));
+                any(ConnectivityDiagnosticsBinder.class), eq(request), eq(mPackageName));
         assertTrue(ConnectivityDiagnosticsManager.sCallbacks.containsKey(mCb));
     }
 
diff --git a/tests/net/java/android/net/ConnectivityManagerTest.java b/tests/net/java/android/net/ConnectivityManagerTest.java
index 7ede144..d6bf334 100644
--- a/tests/net/java/android/net/ConnectivityManagerTest.java
+++ b/tests/net/java/android/net/ConnectivityManagerTest.java
@@ -212,7 +212,8 @@
         ArgumentCaptor<Messenger> captor = ArgumentCaptor.forClass(Messenger.class);
 
         // register callback
-        when(mService.requestNetwork(any(), captor.capture(), anyInt(), any(), anyInt()))
+        when(mService.requestNetwork(
+                any(), captor.capture(), anyInt(), any(), anyInt(), any()))
                 .thenReturn(request);
         manager.requestNetwork(request, callback, handler);
 
@@ -240,7 +241,8 @@
         ArgumentCaptor<Messenger> captor = ArgumentCaptor.forClass(Messenger.class);
 
         // register callback
-        when(mService.requestNetwork(any(), captor.capture(), anyInt(), any(), anyInt()))
+        when(mService.requestNetwork(
+                any(), captor.capture(), anyInt(), any(), anyInt(), any()))
                 .thenReturn(req1);
         manager.requestNetwork(req1, callback, handler);
 
@@ -258,7 +260,8 @@
         verify(callback, timeout(100).times(0)).onLosing(any(), anyInt());
 
         // callback can be registered again
-        when(mService.requestNetwork(any(), captor.capture(), anyInt(), any(), anyInt()))
+        when(mService.requestNetwork(
+                any(), captor.capture(), anyInt(), any(), anyInt(), any()))
                 .thenReturn(req2);
         manager.requestNetwork(req2, callback, handler);
 
@@ -282,7 +285,8 @@
         info.targetSdkVersion = VERSION_CODES.N_MR1 + 1;
 
         when(mCtx.getApplicationInfo()).thenReturn(info);
-        when(mService.requestNetwork(any(), any(), anyInt(), any(), anyInt())).thenReturn(request);
+        when(mService.requestNetwork(any(), any(), anyInt(), any(), anyInt(), any()))
+                .thenReturn(request);
 
         Handler handler = new Handler(Looper.getMainLooper());
         manager.requestNetwork(request, callback, handler);
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 2573bba..968f552 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -23,6 +23,8 @@
 import static android.content.pm.PackageManager.MATCH_ANY_USER;
 import static android.content.pm.PackageManager.PERMISSION_DENIED;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport;
+import static android.net.ConnectivityDiagnosticsManager.DataStallReport;
 import static android.net.ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_SUPL;
@@ -105,6 +107,7 @@
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -119,6 +122,7 @@
 import android.Manifest;
 import android.annotation.NonNull;
 import android.app.AlarmManager;
+import android.app.AppOpsManager;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
@@ -132,6 +136,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
+import android.location.LocationManager;
 import android.net.CaptivePortalData;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
@@ -177,6 +182,7 @@
 import android.net.util.MultinetworkPolicyTracker;
 import android.os.BadParcelableException;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.ConditionVariable;
 import android.os.Handler;
@@ -187,6 +193,7 @@
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
+import android.os.PersistableBundle;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -218,6 +225,7 @@
 import com.android.server.connectivity.IpConnectivityMetrics;
 import com.android.server.connectivity.MockableSystemProperties;
 import com.android.server.connectivity.Nat464Xlat;
+import com.android.server.connectivity.NetworkAgentInfo;
 import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
 import com.android.server.connectivity.ProxyTracker;
 import com.android.server.connectivity.Vpn;
@@ -292,10 +300,13 @@
 
     private static final int UNREASONABLY_LONG_ALARM_WAIT_MS = 1000;
 
+    private static final long TIMESTAMP = 1234L;
+
     private static final String CLAT_PREFIX = "v4-";
     private static final String MOBILE_IFNAME = "test_rmnet_data0";
     private static final String WIFI_IFNAME = "test_wlan0";
     private static final String WIFI_WOL_IFNAME = "test_wlan_wol";
+    private static final String TEST_PACKAGE_NAME = "com.android.test.package";
     private static final String[] EMPTY_STRING_ARRAY = new String[0];
 
     private MockContext mServiceContext;
@@ -327,6 +338,8 @@
     @Mock AlarmManager mAlarmManager;
     @Mock IConnectivityDiagnosticsCallback mConnectivityDiagnosticsCallback;
     @Mock IBinder mIBinder;
+    @Mock LocationManager mLocationManager;
+    @Mock AppOpsManager mAppOpsManager;
 
     private ArgumentCaptor<ResolverParamsParcel> mResolverParamsParcelCaptor =
             ArgumentCaptor.forClass(ResolverParamsParcel.class);
@@ -412,6 +425,8 @@
             if (Context.NETWORK_STACK_SERVICE.equals(name)) return mNetworkStack;
             if (Context.USER_SERVICE.equals(name)) return mUserManager;
             if (Context.ALARM_SERVICE.equals(name)) return mAlarmManager;
+            if (Context.LOCATION_SERVICE.equals(name)) return mLocationManager;
+            if (Context.APP_OPS_SERVICE.equals(name)) return mAppOpsManager;
             return super.getSystemService(name);
         }
 
@@ -558,12 +573,17 @@
                 | NETWORK_VALIDATION_RESULT_PARTIAL;
         private static final int VALIDATION_RESULT_INVALID = 0;
 
+        private static final long DATA_STALL_TIMESTAMP = 10L;
+        private static final int DATA_STALL_DETECTION_METHOD = 1;
+
         private INetworkMonitor mNetworkMonitor;
         private INetworkMonitorCallbacks mNmCallbacks;
         private int mNmValidationResult = VALIDATION_RESULT_BASE;
         private int mProbesCompleted;
         private int mProbesSucceeded;
         private String mNmValidationRedirectUrl = null;
+        private PersistableBundle mValidationExtras = PersistableBundle.EMPTY;
+        private PersistableBundle mDataStallExtras = PersistableBundle.EMPTY;
         private boolean mNmProvNotificationRequested = false;
 
         private final ConditionVariable mNetworkStatusReceived = new ConditionVariable();
@@ -631,12 +651,12 @@
             }
 
             mNmCallbacks.notifyProbeStatusChanged(mProbesCompleted, mProbesSucceeded);
-            mNmCallbacks.notifyNetworkTested(
-                    mNmValidationResult, mNmValidationRedirectUrl);
+            mNmCallbacks.notifyNetworkTestedWithExtras(
+                    mNmValidationResult, mNmValidationRedirectUrl, TIMESTAMP, mValidationExtras);
 
             if (mNmValidationRedirectUrl != null) {
                 mNmCallbacks.showProvisioningNotification(
-                        "test_provisioning_notif_action", "com.android.test.package");
+                        "test_provisioning_notif_action", TEST_PACKAGE_NAME);
                 mNmProvNotificationRequested = true;
             }
         }
@@ -791,6 +811,11 @@
         public void expectPreventReconnectReceived() {
             expectPreventReconnectReceived(TIMEOUT_MS);
         }
+
+        void notifyDataStallSuspected() throws Exception {
+            mNmCallbacks.notifyDataStallSuspected(
+                    DATA_STALL_TIMESTAMP, DATA_STALL_DETECTION_METHOD, mDataStallExtras);
+        }
     }
 
     /**
@@ -970,6 +995,8 @@
         // not inherit from NetworkAgent.
         private TestNetworkAgentWrapper mMockNetworkAgent;
 
+        private VpnInfo mVpnInfo;
+
         public MockVpn(int userId) {
             super(startHandlerThreadAndReturnLooper(), mServiceContext, mNetworkManagementService,
                     userId);
@@ -1041,6 +1068,17 @@
             mConnected = false;
             mConfig = null;
         }
+
+        @Override
+        public synchronized VpnInfo getVpnInfo() {
+            if (mVpnInfo != null) return mVpnInfo;
+
+            return super.getVpnInfo();
+        }
+
+        private void setVpnInfo(VpnInfo vpnInfo) {
+            mVpnInfo = vpnInfo;
+        }
     }
 
     private void mockVpn(int uid) {
@@ -2936,7 +2974,7 @@
             networkCapabilities.addTransportType(TRANSPORT_WIFI)
                     .setNetworkSpecifier(new MatchAllNetworkSpecifier());
             mService.requestNetwork(networkCapabilities, null, 0, null,
-                    ConnectivityManager.TYPE_WIFI);
+                    ConnectivityManager.TYPE_WIFI, TEST_PACKAGE_NAME);
         });
 
         class NonParcelableSpecifier extends NetworkSpecifier {
@@ -2975,31 +3013,12 @@
     }
 
     @Test
-    public void testNetworkSpecifierUidSpoofSecurityException() throws Exception {
-        class UidAwareNetworkSpecifier extends NetworkSpecifier implements Parcelable {
-            @Override
-            public boolean satisfiedBy(NetworkSpecifier other) {
-                return true;
-            }
-
-            @Override
-            public void assertValidFromUid(int requestorUid) {
-                throw new SecurityException("failure");
-            }
-
-            @Override
-            public int describeContents() { return 0; }
-            @Override
-            public void writeToParcel(Parcel dest, int flags) {}
-        }
-
+    public void testNetworkRequestUidSpoofSecurityException() throws Exception {
         mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(false);
-
-        UidAwareNetworkSpecifier networkSpecifier = new UidAwareNetworkSpecifier();
-        NetworkRequest networkRequest = newWifiRequestBuilder().setNetworkSpecifier(
-                networkSpecifier).build();
+        NetworkRequest networkRequest = newWifiRequestBuilder().build();
         TestNetworkCallback networkCallback = new TestNetworkCallback();
+        doThrow(new SecurityException()).when(mAppOpsManager).checkPackage(anyInt(), anyString());
         assertThrows(SecurityException.class, () -> {
             mCm.requestNetwork(networkRequest, networkCallback);
         });
@@ -6400,7 +6419,7 @@
                         new NetworkCapabilities(), TYPE_ETHERNET, 0, NetworkRequest.Type.NONE);
         try {
             mService.registerConnectivityDiagnosticsCallback(
-                    mConnectivityDiagnosticsCallback, request);
+                    mConnectivityDiagnosticsCallback, request, mContext.getPackageName());
             fail("registerConnectivityDiagnosticsCallback should throw on invalid NetworkRequest");
         } catch (IllegalArgumentException expected) {
         }
@@ -6410,14 +6429,16 @@
     public void testRegisterUnregisterConnectivityDiagnosticsCallback() throws Exception {
         final NetworkRequest wifiRequest =
                 new NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI).build();
-
         when(mConnectivityDiagnosticsCallback.asBinder()).thenReturn(mIBinder);
 
         mService.registerConnectivityDiagnosticsCallback(
-                mConnectivityDiagnosticsCallback, wifiRequest);
+                mConnectivityDiagnosticsCallback, wifiRequest, mContext.getPackageName());
 
-        verify(mIBinder, timeout(TIMEOUT_MS))
-                .linkToDeath(any(ConnectivityDiagnosticsCallbackInfo.class), anyInt());
+        // Block until all other events are done processing.
+        HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
+
+        verify(mIBinder).linkToDeath(any(ConnectivityDiagnosticsCallbackInfo.class), anyInt());
+        verify(mConnectivityDiagnosticsCallback).asBinder();
         assertTrue(
                 mService.mConnectivityDiagnosticsCallbacks.containsKey(
                         mConnectivityDiagnosticsCallback));
@@ -6438,10 +6459,12 @@
         when(mConnectivityDiagnosticsCallback.asBinder()).thenReturn(mIBinder);
 
         mService.registerConnectivityDiagnosticsCallback(
-                mConnectivityDiagnosticsCallback, wifiRequest);
+                mConnectivityDiagnosticsCallback, wifiRequest, mContext.getPackageName());
 
-        verify(mIBinder, timeout(TIMEOUT_MS))
-                .linkToDeath(any(ConnectivityDiagnosticsCallbackInfo.class), anyInt());
+        // Block until all other events are done processing.
+        HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
+
+        verify(mIBinder).linkToDeath(any(ConnectivityDiagnosticsCallbackInfo.class), anyInt());
         verify(mConnectivityDiagnosticsCallback).asBinder();
         assertTrue(
                 mService.mConnectivityDiagnosticsCallbacks.containsKey(
@@ -6449,7 +6472,7 @@
 
         // Register the same callback again
         mService.registerConnectivityDiagnosticsCallback(
-                mConnectivityDiagnosticsCallback, wifiRequest);
+                mConnectivityDiagnosticsCallback, wifiRequest, mContext.getPackageName());
 
         // Block until all other events are done processing.
         HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
@@ -6458,4 +6481,193 @@
                 mService.mConnectivityDiagnosticsCallbacks.containsKey(
                         mConnectivityDiagnosticsCallback));
     }
+
+    @Test
+    public void testCheckConnectivityDiagnosticsPermissionsNetworkStack() throws Exception {
+        final NetworkAgentInfo naiWithoutUid =
+                new NetworkAgentInfo(
+                        null, null, null, null, null, new NetworkCapabilities(), null,
+                        mServiceContext, null, null, mService, null, null, null, 0);
+
+        mServiceContext.setPermission(
+                android.Manifest.permission.NETWORK_STACK, PERMISSION_GRANTED);
+        assertTrue(
+                "NetworkStack permission not applied",
+                mService.checkConnectivityDiagnosticsPermissions(
+                        Process.myPid(), Process.myUid(), naiWithoutUid,
+                        mContext.getOpPackageName()));
+    }
+
+    @Test
+    public void testCheckConnectivityDiagnosticsPermissionsNoLocationPermission() throws Exception {
+        final NetworkAgentInfo naiWithoutUid =
+                new NetworkAgentInfo(
+                        null, null, null, null, null, new NetworkCapabilities(), null,
+                        mServiceContext, null, null, mService, null, null, null, 0);
+
+        mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED);
+
+        assertFalse(
+                "ACCESS_FINE_LOCATION permission necessary for Connectivity Diagnostics",
+                mService.checkConnectivityDiagnosticsPermissions(
+                        Process.myPid(), Process.myUid(), naiWithoutUid,
+                        mContext.getOpPackageName()));
+    }
+
+    @Test
+    public void testCheckConnectivityDiagnosticsPermissionsActiveVpn() throws Exception {
+        final NetworkAgentInfo naiWithoutUid =
+                new NetworkAgentInfo(
+                        null, null, null, null, null, new NetworkCapabilities(), null,
+                        mServiceContext, null, null, mService, null, null, null, 0);
+
+        setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_FINE_LOCATION,
+                Manifest.permission.ACCESS_FINE_LOCATION);
+        mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED);
+
+        // setUp() calls mockVpn() which adds a VPN with the Test Runner's uid. Configure it to be
+        // active
+        final VpnInfo info = new VpnInfo();
+        info.ownerUid = Process.myUid();
+        info.vpnIface = "interface";
+        mMockVpn.setVpnInfo(info);
+        assertTrue(
+                "Active VPN permission not applied",
+                mService.checkConnectivityDiagnosticsPermissions(
+                        Process.myPid(), Process.myUid(), naiWithoutUid,
+                        mContext.getOpPackageName()));
+    }
+
+    @Test
+    public void testCheckConnectivityDiagnosticsPermissionsNetworkAdministrator() throws Exception {
+        final NetworkCapabilities nc = new NetworkCapabilities();
+        nc.setAdministratorUids(Arrays.asList(Process.myUid()));
+        final NetworkAgentInfo naiWithUid =
+                new NetworkAgentInfo(
+                        null, null, null, null, null, nc, null, mServiceContext, null, null,
+                        mService, null, null, null, 0);
+
+        setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_FINE_LOCATION,
+                Manifest.permission.ACCESS_FINE_LOCATION);
+        mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED);
+
+        // Disconnect mock vpn so the uid check on NetworkAgentInfo is tested
+        mMockVpn.disconnect();
+        assertTrue(
+                "NetworkCapabilities administrator uid permission not applied",
+                mService.checkConnectivityDiagnosticsPermissions(
+                        Process.myPid(), Process.myUid(), naiWithUid, mContext.getOpPackageName()));
+    }
+
+    @Test
+    public void testCheckConnectivityDiagnosticsPermissionsFails() throws Exception {
+        final NetworkCapabilities nc = new NetworkCapabilities();
+        nc.setOwnerUid(Process.myUid());
+        nc.setAdministratorUids(Arrays.asList(Process.myUid()));
+        final NetworkAgentInfo naiWithUid =
+                new NetworkAgentInfo(
+                        null, null, null, null, null, nc, null, mServiceContext, null, null,
+                        mService, null, null, null, 0);
+
+        setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_FINE_LOCATION,
+                Manifest.permission.ACCESS_FINE_LOCATION);
+        mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED);
+
+        // Use wrong pid and uid
+        assertFalse(
+                "Permissions allowed when they shouldn't be granted",
+                mService.checkConnectivityDiagnosticsPermissions(
+                        Process.myPid() + 1, Process.myUid() + 1, naiWithUid,
+                        mContext.getOpPackageName()));
+    }
+
+    private void setupLocationPermissions(
+            int targetSdk, boolean locationToggle, String op, String perm) throws Exception {
+        final ApplicationInfo applicationInfo = new ApplicationInfo();
+        applicationInfo.targetSdkVersion = targetSdk;
+        when(mPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), any()))
+                .thenReturn(applicationInfo);
+
+        when(mLocationManager.isLocationEnabledForUser(any())).thenReturn(locationToggle);
+
+        when(mAppOpsManager.noteOp(eq(op), eq(Process.myUid()), eq(mContext.getPackageName())))
+                .thenReturn(AppOpsManager.MODE_ALLOWED);
+
+        mServiceContext.setPermission(perm, PERMISSION_GRANTED);
+    }
+
+    private void setUpConnectivityDiagnosticsCallback() throws Exception {
+        final NetworkRequest request = new NetworkRequest.Builder().build();
+        when(mConnectivityDiagnosticsCallback.asBinder()).thenReturn(mIBinder);
+
+        mServiceContext.setPermission(
+                android.Manifest.permission.NETWORK_STACK, PERMISSION_GRANTED);
+
+        mService.registerConnectivityDiagnosticsCallback(
+                mConnectivityDiagnosticsCallback, request, mContext.getPackageName());
+
+        // Block until all other events are done processing.
+        HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
+
+        // Connect the cell agent verify that it notifies TestNetworkCallback that it is available
+        final TestNetworkCallback callback = new TestNetworkCallback();
+        mCm.registerDefaultNetworkCallback(callback);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellNetworkAgent.connect(true);
+        callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        callback.assertNoCallback();
+    }
+
+    @Test
+    public void testConnectivityDiagnosticsCallbackOnConnectivityReport() throws Exception {
+        setUpConnectivityDiagnosticsCallback();
+
+        // Block until all other events are done processing.
+        HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
+
+        // Verify onConnectivityReport fired
+        verify(mConnectivityDiagnosticsCallback)
+                .onConnectivityReport(any(ConnectivityReport.class));
+    }
+
+    @Test
+    public void testConnectivityDiagnosticsCallbackOnDataStallSuspected() throws Exception {
+        setUpConnectivityDiagnosticsCallback();
+
+        // Trigger notifyDataStallSuspected() on the INetworkMonitorCallbacks instance in the
+        // cellular network agent
+        mCellNetworkAgent.notifyDataStallSuspected();
+
+        // Block until all other events are done processing.
+        HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
+
+        // Verify onDataStallSuspected fired
+        verify(mConnectivityDiagnosticsCallback).onDataStallSuspected(any(DataStallReport.class));
+    }
+
+    @Test
+    public void testConnectivityDiagnosticsCallbackOnConnectivityReported() throws Exception {
+        setUpConnectivityDiagnosticsCallback();
+
+        final Network n = mCellNetworkAgent.getNetwork();
+        final boolean hasConnectivity = true;
+        mService.reportNetworkConnectivity(n, hasConnectivity);
+
+        // Block until all other events are done processing.
+        HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
+
+        // Verify onNetworkConnectivityReported fired
+        verify(mConnectivityDiagnosticsCallback)
+                .onNetworkConnectivityReported(eq(n), eq(hasConnectivity));
+
+        final boolean noConnectivity = false;
+        mService.reportNetworkConnectivity(n, noConnectivity);
+
+        // Block until all other events are done processing.
+        HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
+
+        // Wait for onNetworkConnectivityReported to fire
+        verify(mConnectivityDiagnosticsCallback)
+                .onNetworkConnectivityReported(eq(n), eq(noConnectivity));
+    }
 }
diff --git a/tools/stats_log_api_gen/Android.bp b/tools/stats_log_api_gen/Android.bp
index 0e32aee..a251c05 100644
--- a/tools/stats_log_api_gen/Android.bp
+++ b/tools/stats_log_api_gen/Android.bp
@@ -30,6 +30,7 @@
         "utils.cpp",
     ],
     cflags: [
+        //"-DSTATS_SCHEMA_LEGACY",
         "-Wall",
         "-Werror",
     ],
diff --git a/tools/stats_log_api_gen/java_writer_q.cpp b/tools/stats_log_api_gen/java_writer_q.cpp
index a68c3a2..12c050d 100644
--- a/tools/stats_log_api_gen/java_writer_q.cpp
+++ b/tools/stats_log_api_gen/java_writer_q.cpp
@@ -175,9 +175,7 @@
                         indent.c_str());
                 fprintf(out,
                         "%s    android.util.SparseArray<Float> floatMap = null;\n", indent.c_str());
-                fprintf(out,
-                        "%s    int keyValuePairSize = LIST_TYPE_OVERHEAD * 5;\n",
-                        indent.c_str());
+                fprintf(out, "%s    int keyValuePairSize = LIST_TYPE_OVERHEAD;\n", indent.c_str());
                 fprintf(out,
                         "%s    for (int i = 0; i < count; i++) {\n", indent.c_str());
                 fprintf(out,
@@ -360,8 +358,9 @@
                 requiredHelpers |= JAVA_MODULE_REQUIRES_FLOAT;
                 requiredHelpers |= JAVA_MODULE_REQUIRES_KEY_VALUE_PAIRS;
                 fprintf(out,
-                        "%s    writeKeyValuePairs(buff, pos, intMap, longMap, stringMap, "
-                        "floatMap);\n", indent.c_str());
+                        "%s    writeKeyValuePairs(buff, pos, (byte) count, intMap, longMap, "
+                        "stringMap, floatMap);\n",
+                        indent.c_str());
                 fprintf(out, "%s    pos += keyValuePairSize;\n", indent.c_str());
                 break;
             default:
@@ -472,7 +471,8 @@
     }
 
     if (requiredHelpers & JAVA_MODULE_REQUIRES_KEY_VALUE_PAIRS) {
-        fprintf(out, "%sprivate static void writeKeyValuePairs(byte[] buff, int pos,\n",
+        fprintf(out,
+                "%sprivate static void writeKeyValuePairs(byte[] buff, int pos, byte numPairs,\n",
                 indent.c_str());
         fprintf(out, "%s        final android.util.SparseIntArray intMap,\n", indent.c_str());
         fprintf(out, "%s        final android.util.SparseLongArray longMap,\n", indent.c_str());
@@ -483,15 +483,12 @@
 
         // Start list of lists.
         fprintf(out, "%s    buff[pos] = LIST_TYPE;\n", indent.c_str());
-        fprintf(out, "%s    buff[pos + 1] = (byte) 4;\n", indent.c_str());
+        fprintf(out, "%s    buff[pos + 1] = (byte) numPairs;\n", indent.c_str());
         fprintf(out, "%s    pos += LIST_TYPE_OVERHEAD;\n", indent.c_str());
 
         // Write integers.
         fprintf(out, "%s    final int intMapSize = null == intMap ? 0 : intMap.size();\n",
                 indent.c_str());
-        fprintf(out, "%s    buff[pos] = LIST_TYPE;\n", indent.c_str());
-        fprintf(out, "%s    buff[pos + 1] = (byte) intMapSize;\n", indent.c_str());
-        fprintf(out, "%s    pos += LIST_TYPE_OVERHEAD;\n", indent.c_str());
         fprintf(out, "%s    for (int i = 0; i < intMapSize; i++) {\n", indent.c_str());
         fprintf(out, "%s        buff[pos] = LIST_TYPE;\n", indent.c_str());
         fprintf(out, "%s        buff[pos + 1] = (byte) 2;\n", indent.c_str());
@@ -509,9 +506,6 @@
         // Write longs.
         fprintf(out, "%s    final int longMapSize = null == longMap ? 0 : longMap.size();\n",
                 indent.c_str());
-        fprintf(out, "%s    buff[pos] = LIST_TYPE;\n", indent.c_str());
-        fprintf(out, "%s    buff[pos + 1] = (byte) longMapSize;\n", indent.c_str());
-        fprintf(out, "%s    pos += LIST_TYPE_OVERHEAD;\n", indent.c_str());
         fprintf(out, "%s    for (int i = 0; i < longMapSize; i++) {\n", indent.c_str());
         fprintf(out, "%s        buff[pos] = LIST_TYPE;\n", indent.c_str());
         fprintf(out, "%s        buff[pos + 1] = (byte) 2;\n", indent.c_str());
@@ -529,9 +523,6 @@
         // Write Strings.
         fprintf(out, "%s    final int stringMapSize = null == stringMap ? 0 : stringMap.size();\n",
                 indent.c_str());
-        fprintf(out, "%s    buff[pos] = LIST_TYPE;\n", indent.c_str());
-        fprintf(out, "%s    buff[pos + 1] = (byte) stringMapSize;\n", indent.c_str());
-        fprintf(out, "%s    pos += LIST_TYPE_OVERHEAD;\n", indent.c_str());
         fprintf(out, "%s    for (int i = 0; i < stringMapSize; i++) {\n", indent.c_str());
         fprintf(out, "%s        buff[pos] = LIST_TYPE;\n", indent.c_str());
         fprintf(out, "%s        buff[pos + 1] = (byte) 2;\n", indent.c_str());
@@ -556,9 +547,6 @@
         // Write floats.
         fprintf(out, "%s    final int floatMapSize = null == floatMap ? 0 : floatMap.size();\n",
                 indent.c_str());
-        fprintf(out, "%s    buff[pos] = LIST_TYPE;\n", indent.c_str());
-        fprintf(out, "%s    buff[pos + 1] = (byte) floatMapSize;\n", indent.c_str());
-        fprintf(out, "%s    pos += LIST_TYPE_OVERHEAD;\n", indent.c_str());
         fprintf(out, "%s    for (int i = 0; i < floatMapSize; i++) {\n", indent.c_str());
         fprintf(out, "%s        buff[pos] = LIST_TYPE;\n", indent.c_str());
         fprintf(out, "%s        buff[pos + 1] = (byte) 2;\n", indent.c_str());
diff --git a/tools/stats_log_api_gen/native_writer.cpp b/tools/stats_log_api_gen/native_writer.cpp
index c7a34fe..285514d 100644
--- a/tools/stats_log_api_gen/native_writer.cpp
+++ b/tools/stats_log_api_gen/native_writer.cpp
@@ -22,15 +22,6 @@
 namespace stats_log_api_gen {
 
 #if !defined(STATS_SCHEMA_LEGACY)
-static void write_native_key_value_pairs_for_type(FILE* out, const int argIndex,
-        const int typeIndex, const string& type, const string& valueFieldName) {
-    fprintf(out, "    for (const auto& it : arg%d_%d) {\n", argIndex, typeIndex);
-    fprintf(out, "        pairs.push_back("
-            "{ .key = it.first, .valueType = %s, .%s = it.second });\n",
-            type.c_str(), valueFieldName.c_str());
-    fprintf(out, "    }\n");
-
-}
 
 static int write_native_stats_write_methods(FILE* out, const Atoms& atoms,
         const AtomDecl& attributionDecl, const string& moduleName, const bool supportQ) {
@@ -41,7 +32,10 @@
             continue;
         }
         vector<java_type_t> signature = signature_to_modules_it->first;
-
+        // Key value pairs not supported in native.
+        if (find(signature.begin(), signature.end(), JAVA_TYPE_KEY_VALUE_PAIR) != signature.end()) {
+            continue;
+        }
         write_native_method_signature(out, "int stats_write", signature,
                 attributionDecl, " {");
 
@@ -59,11 +53,6 @@
                                 uidName, uidName, tagName);
                         break;
                     }
-                    case JAVA_TYPE_KEY_VALUE_PAIR:
-                        fprintf(out, "    event.writeKeyValuePairs("
-                                "arg%d_1, arg%d_2, arg%d_3, arg%d_4);\n",
-                                argIndex, argIndex, argIndex, argIndex);
-                        break;
                     case JAVA_TYPE_BYTE_ARRAY:
                         fprintf(out, "    event.writeByteArray(arg%d.arg, arg%d.arg_length);\n",
                                 argIndex, argIndex);
@@ -85,7 +74,7 @@
                         fprintf(out, "    event.writeString(arg%d);\n", argIndex);
                         break;
                     default:
-                        // Unsupported types: OBJECT, DOUBLE.
+                        // Unsupported types: OBJECT, DOUBLE, KEY_VALUE_PAIRS.
                         fprintf(stderr, "Encountered unsupported type.");
                         return 1;
                 }
@@ -93,8 +82,8 @@
             }
             fprintf(out, "    return event.writeToSocket();\n");
         } else {
-            fprintf(out, "    struct stats_event* event = stats_event_obtain();\n");
-            fprintf(out, "    stats_event_set_atom_id(event, code);\n");
+            fprintf(out, "    AStatsEvent* event = AStatsEvent_obtain();\n");
+            fprintf(out, "    AStatsEvent_setAtomId(event, code);\n");
             for (vector<java_type_t>::const_iterator arg = signature.begin();
                     arg != signature.end(); arg++) {
                 switch (*arg) {
@@ -102,57 +91,43 @@
                         const char* uidName = attributionDecl.fields.front().name.c_str();
                         const char* tagName = attributionDecl.fields.back().name.c_str();
                         fprintf(out,
-                                "    stats_event_write_attribution_chain(event, "
+                                "    AStatsEvent_writeAttributionChain(event, "
                                 "reinterpret_cast<const uint32_t*>(%s), %s.data(), "
                                 "static_cast<uint8_t>(%s_length));\n",
                                 uidName, tagName, uidName);
                         break;
                     }
-                    case JAVA_TYPE_KEY_VALUE_PAIR:
-                        fprintf(out, "    std::vector<key_value_pair> pairs;\n");
-                        write_native_key_value_pairs_for_type(
-                                out, argIndex, 1, "INT32_TYPE", "int32Value");
-                        write_native_key_value_pairs_for_type(
-                                out, argIndex, 2, "INT64_TYPE", "int64Value");
-                        write_native_key_value_pairs_for_type(
-                                out, argIndex, 3, "STRING_TYPE", "stringValue");
-                        write_native_key_value_pairs_for_type(
-                                out, argIndex, 4, "FLOAT_TYPE", "floatValue");
-                        fprintf(out,
-                                "    stats_event_write_key_value_pairs(event, pairs.data(), "
-                                "static_cast<uint8_t>(pairs.size()));\n");
-                        break;
                     case JAVA_TYPE_BYTE_ARRAY:
                         fprintf(out,
-                                "    stats_event_write_byte_array(event, "
+                                "    AStatsEvent_writeByteArray(event, "
                                 "reinterpret_cast<const uint8_t*>(arg%d.arg), arg%d.arg_length);\n",
                                 argIndex, argIndex);
                         break;
                     case JAVA_TYPE_BOOLEAN:
-                        fprintf(out, "    stats_event_write_bool(event, arg%d);\n", argIndex);
+                        fprintf(out, "    AStatsEvent_writeBool(event, arg%d);\n", argIndex);
                         break;
                     case JAVA_TYPE_INT: // Fall through.
                     case JAVA_TYPE_ENUM:
-                        fprintf(out, "    stats_event_write_int32(event, arg%d);\n", argIndex);
+                        fprintf(out, "    AStatsEvent_writeInt32(event, arg%d);\n", argIndex);
                         break;
                     case JAVA_TYPE_FLOAT:
-                        fprintf(out, "    stats_event_write_float(event, arg%d);\n", argIndex);
+                        fprintf(out, "    AStatsEvent_writeFloat(event, arg%d);\n", argIndex);
                         break;
                     case JAVA_TYPE_LONG:
-                        fprintf(out, "    stats_event_write_int64(event, arg%d);\n", argIndex);
+                        fprintf(out, "    AStatsEvent_writeInt64(event, arg%d);\n", argIndex);
                         break;
                     case JAVA_TYPE_STRING:
-                        fprintf(out, "    stats_event_write_string8(event, arg%d);\n", argIndex);
+                        fprintf(out, "    AStatsEvent_writeString(event, arg%d);\n", argIndex);
                         break;
                     default:
-                        // Unsupported types: OBJECT, DOUBLE.
+                        // Unsupported types: OBJECT, DOUBLE, KEY_VALUE_PAIRS
                         fprintf(stderr, "Encountered unsupported type.");
                         return 1;
                 }
                 argIndex++;
             }
-            fprintf(out, "    const int ret = stats_event_write(event);\n");
-            fprintf(out, "    stats_event_release(event);\n");
+            fprintf(out, "    const int ret = AStatsEvent_write(event);\n");
+            fprintf(out, "    AStatsEvent_release(event);\n");
             fprintf(out, "    return ret;\n");
         }
         fprintf(out, "}\n\n");
@@ -169,6 +144,10 @@
             continue;
         }
         vector<java_type_t> signature = signature_it->first;
+        // Key value pairs not supported in native.
+        if (find(signature.begin(), signature.end(), JAVA_TYPE_KEY_VALUE_PAIR) != signature.end()) {
+            continue;
+        }
 
         write_native_method_signature(out, "int stats_write_non_chained", signature,
                 attributionDecl, " {");
@@ -210,8 +189,14 @@
         if (!signature_needed_for_module(signature_to_modules_it->second, moduleName)) {
             continue;
         }
-
         vector<java_type_t> signature = signature_to_modules_it->first;
+
+#if !defined(STATS_SCHEMA_LEGACY)
+        // Key value pairs not supported in native.
+        if (find(signature.begin(), signature.end(), JAVA_TYPE_KEY_VALUE_PAIR) != signature.end()) {
+            continue;
+        }
+#endif
         write_native_method_signature(out, methodName, signature, attributionDecl, ";");
     }
 }
diff --git a/tools/streaming_proto/cpp/main.cpp b/tools/streaming_proto/cpp/main.cpp
index d6b9d81..fe9a438 100644
--- a/tools/streaming_proto/cpp/main.cpp
+++ b/tools/streaming_proto/cpp/main.cpp
@@ -33,13 +33,13 @@
     if (GENERATE_MAPPING) {
         string name = make_constant_name(enu.name());
         string prefix = name + "_";
-        text << indent << "const int _ENUM_" << name << "_COUNT = " << N << ";" << endl;
-        text << indent << "const char* _ENUM_" << name << "_NAMES[" << N << "] = {" << endl;
+        text << indent << "static const int _ENUM_" << name << "_COUNT = " << N << ";" << endl;
+        text << indent << "static const char* _ENUM_" << name << "_NAMES[" << N << "] = {" << endl;
         for (int i=0; i<N; i++) {
             text << indent << INDENT << "\"" << stripPrefix(enu.value(i).name(), prefix) << "\"," << endl;
         }
         text << indent << "};" << endl;
-        text << indent << "const int _ENUM_" << name << "_VALUES[" << N << "] = {" << endl;
+        text << indent << "static const int _ENUM_" << name << "_VALUES[" << N << "] = {" << endl;
         for (int i=0; i<N; i++) {
             text << indent << INDENT << make_constant_name(enu.value(i).name()) << "," << endl;
         }
@@ -102,13 +102,13 @@
 
     if (GENERATE_MAPPING) {
         N = message.field_size();
-        text << indented << "const int _FIELD_COUNT = " << N << ";" << endl;
-        text << indented << "const char* _FIELD_NAMES[" << N << "] = {" << endl;
+        text << indented << "static const int _FIELD_COUNT = " << N << ";" << endl;
+        text << indented << "static const char* _FIELD_NAMES[" << N << "] = {" << endl;
         for (int i=0; i<N; i++) {
             text << indented << INDENT << "\"" << message.field(i).name() << "\"," << endl;
         }
         text << indented << "};" << endl;
-        text << indented << "const uint64_t _FIELD_IDS[" << N << "] = {" << endl;
+        text << indented << "static const uint64_t _FIELD_IDS[" << N << "] = {" << endl;
         for (int i=0; i<N; i++) {
             text << indented << INDENT << make_constant_name(message.field(i).name()) << "," << endl;
         }
@@ -152,7 +152,7 @@
         write_message(text, file_descriptor.message_type(i), "");
     }
 
-    for (vector<string>::iterator it = namespaces.begin(); it != namespaces.end(); it++) {
+    for (vector<string>::reverse_iterator it = namespaces.rbegin(); it != namespaces.rend(); it++) {
         text << "} // " << *it << endl;
     }
 
diff --git a/wifi/Android.bp b/wifi/Android.bp
index dae04c6..6a29b1c 100644
--- a/wifi/Android.bp
+++ b/wifi/Android.bp
@@ -63,7 +63,6 @@
     "//frameworks/base/wifi/tests",
     "//frameworks/opt/net/wifi/tests/wifitests:__subpackages__",
 
-    "//frameworks/opt/net/wifi/libs/WifiTrackerLib/tests",
     "//external/robolectric-shadows:__subpackages__",
     "//frameworks/base/packages/SettingsLib/tests/integ",
     "//external/sl4a:__subpackages__",
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index 91b7df3..7c3d0b9 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -568,14 +568,12 @@
      * 2GHz band.
      * @hide
      */
-    @SystemApi
     public static final int AP_BAND_2GHZ = 0;
 
     /**
      * 5GHz band.
      * @hide
      */
-    @SystemApi
     public static final int AP_BAND_5GHZ = 1;
 
     /**
@@ -583,7 +581,6 @@
      * operating country code and current radio conditions.
      * @hide
      */
-    @SystemApi
     public static final int AP_BAND_ANY = -1;
 
     /**
@@ -593,7 +590,7 @@
      *
      * @hide
      */
-    @SystemApi
+    @UnsupportedAppUsage
     @ApBand
     public int apBand = AP_BAND_2GHZ;
 
@@ -1304,10 +1301,34 @@
         public static final int DISABLED_BY_WRONG_PASSWORD = 8;
         /** This network is disabled because service is not subscribed. */
         public static final int DISABLED_AUTHENTICATION_NO_SUBSCRIPTION = 9;
-        /** All other disable reasons should be strictly less than this value. */
+        /**
+         * All other disable reasons should be strictly less than this value.
+         * @hide
+         */
         public static final int NETWORK_SELECTION_DISABLED_MAX = 10;
 
         /**
+         * Get an integer that is equal to the maximum integer value of all the
+         * DISABLED_* reasons
+         * e.g. {@link #DISABLED_NONE}, {@link #DISABLED_ASSOCIATION_REJECTION}, etc.
+         *
+         * All DISABLED_* constants will be contiguous in the range
+         * 0, 1, 2, 3, ..., getMaxNetworkSelectionDisableReasons()
+         *
+         * <br />
+         * For example, this can be used to iterate through all the network selection
+         * disable reasons like so:
+         * <pre>{@code
+         * for (int reason = 0; reason <= getMaxNetworkSelectionDisableReasons(); reason++) {
+         *     ...
+         * }
+         * }</pre>
+         */
+        public static int getMaxNetworkSelectionDisableReason() {
+            return NETWORK_SELECTION_DISABLED_MAX - 1;
+        }
+
+        /**
          * Contains info about disable reasons.
          * @hide
          */
@@ -1709,7 +1730,10 @@
             return mStatus;
         }
 
-        /** True if the current network is enabled to join network selection, false otherwise. */
+        /**
+         * True if the current network is enabled to join network selection, false otherwise.
+         * @hide
+         */
         public boolean isNetworkEnabled() {
             return mStatus == NETWORK_SELECTION_ENABLED;
         }
@@ -1722,7 +1746,10 @@
             return mStatus == NETWORK_SELECTION_TEMPORARY_DISABLED;
         }
 
-        /** True if the current network is permanently disabled, false otherwise. */
+        /**
+         * True if the current network is permanently disabled, false otherwise.
+         * @hide
+         */
         public boolean isNetworkPermanentlyDisabled() {
             return mStatus == NETWORK_SELECTION_PERMANENTLY_DISABLED;
         }
diff --git a/wifi/java/android/net/wifi/WifiFrameworkInitializer.java b/wifi/java/android/net/wifi/WifiFrameworkInitializer.java
index 002820b..1507199 100644
--- a/wifi/java/android/net/wifi/WifiFrameworkInitializer.java
+++ b/wifi/java/android/net/wifi/WifiFrameworkInitializer.java
@@ -102,15 +102,6 @@
                 }
         );
         SystemServiceRegistry.registerContextAwareService(
-                Context.WIFI_RTT_SERVICE,
-                RttManager.class,
-                (context, serviceBinder) -> {
-                    IWifiRttManager service = IWifiRttManager.Stub.asInterface(serviceBinder);
-                    WifiRttManager wifiRttManager = new WifiRttManager(context, service);
-                    return new RttManager(context, wifiRttManager);
-                }
-        );
-        SystemServiceRegistry.registerContextAwareService(
                 Context.WIFI_RTT_RANGING_SERVICE,
                 WifiRttManager.class,
                 (context, serviceBinder) -> {
@@ -118,5 +109,13 @@
                     return new WifiRttManager(context, service);
                 }
         );
+        SystemServiceRegistry.registerContextAwareService(
+                Context.WIFI_RTT_SERVICE,
+                RttManager.class,
+                context -> {
+                    WifiRttManager wifiRttManager = context.getSystemService(WifiRttManager.class);
+                    return new RttManager(context, wifiRttManager);
+                }
+        );
     }
 }
diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java
index 7c031ea..24b2a8e 100644
--- a/wifi/java/android/net/wifi/WifiInfo.java
+++ b/wifi/java/android/net/wifi/WifiInfo.java
@@ -449,9 +449,8 @@
      * <p>
      * If the SSID can be decoded as UTF-8, it will be returned surrounded by double
      * quotation marks. Otherwise, it is returned as a string of hex digits.
-     * The SSID may be
-     * <lt>&lt;unknown ssid&gt;, if there is no network currently connected or if the caller has
-     * insufficient permissions to access the SSID.<lt>
+     * The SSID may be {@link WifiManager#UNKNOWN_SSID}, if there is no network currently connected
+     * or if the caller has insufficient permissions to access the SSID.
      * </p>
      * <p>
      * Prior to {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}, this method
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 1dc4a06..a3cce7c 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -2625,8 +2625,8 @@
     public void getWifiActivityEnergyInfoAsync(
             @NonNull @CallbackExecutor Executor executor,
             @NonNull OnWifiActivityEnergyInfoListener listener) {
-        if (executor == null) throw new IllegalArgumentException("executor cannot be null");
-        if (listener == null) throw new IllegalArgumentException("listener cannot be null");
+        Objects.requireNonNull(executor, "executor cannot be null");
+        Objects.requireNonNull(listener, "listener cannot be null");
         try {
             mService.getWifiActivityEnergyInfoAsync(
                     new OnWifiActivityEnergyInfoProxy(executor, listener));
diff --git a/wifi/java/android/net/wifi/WifiNetworkAgentSpecifier.java b/wifi/java/android/net/wifi/WifiNetworkAgentSpecifier.java
index 04d2e1a..6632c16 100644
--- a/wifi/java/android/net/wifi/WifiNetworkAgentSpecifier.java
+++ b/wifi/java/android/net/wifi/WifiNetworkAgentSpecifier.java
@@ -27,7 +27,6 @@
 import android.net.NetworkSpecifier;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.text.TextUtils;
 
 import java.util.Objects;
 
@@ -41,33 +40,10 @@
      */
     private final WifiConfiguration mWifiConfiguration;
 
-    /**
-     * The UID of the app that requested a specific wifi network using {@link WifiNetworkSpecifier}.
-     *
-     * Will only be filled when the device connects to a wifi network as a result of a
-     * {@link NetworkRequest} with {@link WifiNetworkSpecifier}. Will be set to -1 if the device
-     * auto-connected to a wifi network.
-     */
-    private final int mOriginalRequestorUid;
-
-    /**
-     * The package name of the app that requested a specific wifi network using
-     * {@link WifiNetworkSpecifier}.
-     *
-     * Will only be filled when the device connects to a wifi network as a result of a
-     * {@link NetworkRequest} with {@link WifiNetworkSpecifier}. Will be set to null if the device
-     * auto-connected to a wifi network.
-     */
-    private final String mOriginalRequestorPackageName;
-
-    public WifiNetworkAgentSpecifier(@NonNull WifiConfiguration wifiConfiguration,
-                                     int originalRequestorUid,
-                                     @Nullable String originalRequestorPackageName) {
+    public WifiNetworkAgentSpecifier(@NonNull WifiConfiguration wifiConfiguration) {
         checkNotNull(wifiConfiguration);
 
         mWifiConfiguration = wifiConfiguration;
-        mOriginalRequestorUid = originalRequestorUid;
-        mOriginalRequestorPackageName = originalRequestorPackageName;
     }
 
     /**
@@ -78,10 +54,7 @@
                 @Override
                 public WifiNetworkAgentSpecifier createFromParcel(@NonNull Parcel in) {
                     WifiConfiguration wifiConfiguration = in.readParcelable(null);
-                    int originalRequestorUid = in.readInt();
-                    String originalRequestorPackageName = in.readString();
-                    return new WifiNetworkAgentSpecifier(
-                            wifiConfiguration, originalRequestorUid, originalRequestorPackageName);
+                    return new WifiNetworkAgentSpecifier(wifiConfiguration);
                 }
 
                 @Override
@@ -98,8 +71,6 @@
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeParcelable(mWifiConfiguration, flags);
-        dest.writeInt(mOriginalRequestorUid);
-        dest.writeString(mOriginalRequestorPackageName);
     }
 
     @Override
@@ -149,12 +120,6 @@
                 this.mWifiConfiguration.allowedKeyManagement)) {
             return false;
         }
-        if (ns.requestorUid != this.mOriginalRequestorUid) {
-            return false;
-        }
-        if (!TextUtils.equals(ns.requestorPackageName, this.mOriginalRequestorPackageName)) {
-            return false;
-        }
         return true;
     }
 
@@ -163,9 +128,7 @@
         return Objects.hash(
                 mWifiConfiguration.SSID,
                 mWifiConfiguration.BSSID,
-                mWifiConfiguration.allowedKeyManagement,
-                mOriginalRequestorUid,
-                mOriginalRequestorPackageName);
+                mWifiConfiguration.allowedKeyManagement);
     }
 
     @Override
@@ -180,10 +143,7 @@
         return Objects.equals(this.mWifiConfiguration.SSID, lhs.mWifiConfiguration.SSID)
                 && Objects.equals(this.mWifiConfiguration.BSSID, lhs.mWifiConfiguration.BSSID)
                 && Objects.equals(this.mWifiConfiguration.allowedKeyManagement,
-                    lhs.mWifiConfiguration.allowedKeyManagement)
-                && mOriginalRequestorUid == lhs.mOriginalRequestorUid
-                && TextUtils.equals(mOriginalRequestorPackageName,
-                lhs.mOriginalRequestorPackageName);
+                    lhs.mWifiConfiguration.allowedKeyManagement);
     }
 
     @Override
@@ -192,19 +152,11 @@
         sb.append("WifiConfiguration=")
                 .append(", SSID=").append(mWifiConfiguration.SSID)
                 .append(", BSSID=").append(mWifiConfiguration.BSSID)
-                .append(", mOriginalRequestorUid=").append(mOriginalRequestorUid)
-                .append(", mOriginalRequestorPackageName=").append(mOriginalRequestorPackageName)
                 .append("]");
         return sb.toString();
     }
 
     @Override
-    public void assertValidFromUid(int requestorUid) {
-        throw new IllegalStateException("WifiNetworkAgentSpecifier should never be used "
-                + "for requests.");
-    }
-
-    @Override
     public NetworkSpecifier redact() {
         return null;
     }
diff --git a/wifi/java/android/net/wifi/WifiNetworkSpecifier.java b/wifi/java/android/net/wifi/WifiNetworkSpecifier.java
index 444e1ef..3d946c9 100644
--- a/wifi/java/android/net/wifi/WifiNetworkSpecifier.java
+++ b/wifi/java/android/net/wifi/WifiNetworkSpecifier.java
@@ -20,7 +20,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.Application;
 import android.net.MacAddress;
 import android.net.MatchAllNetworkSpecifier;
 import android.net.NetworkRequest;
@@ -28,13 +27,9 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.PatternMatcher;
-import android.os.Process;
 import android.text.TextUtils;
-import android.util.Log;
 import android.util.Pair;
 
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
 import java.nio.charset.CharsetEncoder;
 import java.nio.charset.StandardCharsets;
 import java.util.Objects;
@@ -438,24 +433,7 @@
             return new WifiNetworkSpecifier(
                     mSsidPatternMatcher,
                     mBssidPatternMatcher,
-                    buildWifiConfiguration(),
-                    Process.myUid(),
-                    getCurrentApplicationReflectively().getApplicationContext().getOpPackageName());
-        }
-
-        // TODO(b/144102365): Remove once refactor is complete
-        private static Application getCurrentApplicationReflectively() {
-            try {
-                // reflection for static method android.app.ActivityThread#currentApplication()
-                Class<?> klass = Class.forName("android.app.ActivityThread");
-                Method currentApplicationMethod = klass.getDeclaredMethod("currentApplication");
-                Object result = currentApplicationMethod.invoke(null);
-                return (Application) result;
-            } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException
-                    | InvocationTargetException e) {
-                Log.e(TAG, "Failed to call ActivityThread#currentApplication() reflectively!", e);
-                throw new RuntimeException(e);
-            }
+                    buildWifiConfiguration());
         }
     }
 
@@ -483,20 +461,6 @@
      */
     public final WifiConfiguration wifiConfiguration;
 
-    /**
-     * The UID of the process initializing this network specifier. Validated by receiver using
-     * checkUidIfNecessary() and is used by satisfiedBy() to determine whether the specifier
-     * matches the offered network.
-     * @hide
-     */
-    public final int requestorUid;
-
-    /**
-     * The package name of the app initializing this network specifier.
-     * @hide
-     */
-    public final String requestorPackageName;
-
     /** @hide */
     public WifiNetworkSpecifier() throws IllegalAccessException {
         throw new IllegalAccessException("Use the builder to create an instance");
@@ -505,18 +469,14 @@
     /** @hide */
     public WifiNetworkSpecifier(@NonNull PatternMatcher ssidPatternMatcher,
                                 @NonNull Pair<MacAddress, MacAddress> bssidPatternMatcher,
-                                @NonNull WifiConfiguration wifiConfiguration,
-                                int requestorUid, @NonNull String requestorPackageName) {
+                                @NonNull WifiConfiguration wifiConfiguration) {
         checkNotNull(ssidPatternMatcher);
         checkNotNull(bssidPatternMatcher);
         checkNotNull(wifiConfiguration);
-        checkNotNull(requestorPackageName);
 
         this.ssidPatternMatcher = ssidPatternMatcher;
         this.bssidPatternMatcher = bssidPatternMatcher;
         this.wifiConfiguration = wifiConfiguration;
-        this.requestorUid = requestorUid;
-        this.requestorPackageName = requestorPackageName;
     }
 
     public static final @NonNull Creator<WifiNetworkSpecifier> CREATOR =
@@ -529,10 +489,8 @@
                     Pair<MacAddress, MacAddress> bssidPatternMatcher =
                             Pair.create(baseAddress, mask);
                     WifiConfiguration wifiConfiguration = in.readParcelable(null);
-                    int requestorUid = in.readInt();
-                    String requestorPackageName = in.readString();
                     return new WifiNetworkSpecifier(ssidPatternMatcher, bssidPatternMatcher,
-                            wifiConfiguration, requestorUid, requestorPackageName);
+                            wifiConfiguration);
                 }
 
                 @Override
@@ -552,18 +510,13 @@
         dest.writeParcelable(bssidPatternMatcher.first, flags);
         dest.writeParcelable(bssidPatternMatcher.second, flags);
         dest.writeParcelable(wifiConfiguration, flags);
-        dest.writeInt(requestorUid);
-        dest.writeString(requestorPackageName);
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(
-                ssidPatternMatcher.getPath(),
-                ssidPatternMatcher.getType(),
-                bssidPatternMatcher,
-                wifiConfiguration.allowedKeyManagement,
-                requestorUid, requestorPackageName);
+                ssidPatternMatcher.getPath(), ssidPatternMatcher.getType(), bssidPatternMatcher,
+                wifiConfiguration.allowedKeyManagement);
     }
 
     @Override
@@ -582,9 +535,7 @@
                 && Objects.equals(this.bssidPatternMatcher,
                     lhs.bssidPatternMatcher)
                 && Objects.equals(this.wifiConfiguration.allowedKeyManagement,
-                    lhs.wifiConfiguration.allowedKeyManagement)
-                && requestorUid == lhs.requestorUid
-                && TextUtils.equals(requestorPackageName, lhs.requestorPackageName);
+                    lhs.wifiConfiguration.allowedKeyManagement);
     }
 
     @Override
@@ -595,8 +546,6 @@
                 .append(", BSSID Match pattern=").append(bssidPatternMatcher)
                 .append(", SSID=").append(wifiConfiguration.SSID)
                 .append(", BSSID=").append(wifiConfiguration.BSSID)
-                .append(", requestorUid=").append(requestorUid)
-                .append(", requestorPackageName=").append(requestorPackageName)
                 .append("]")
                 .toString();
     }
@@ -618,12 +567,4 @@
         // not make much sense!
         return equals(other);
     }
-
-    /** @hide */
-    @Override
-    public void assertValidFromUid(int requestorUid) {
-        if (this.requestorUid != requestorUid) {
-            throw new SecurityException("mismatched UIDs");
-        }
-    }
 }
diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java
index a85f40b..b4eb30b 100644
--- a/wifi/java/android/net/wifi/WifiScanner.java
+++ b/wifi/java/android/net/wifi/WifiScanner.java
@@ -293,26 +293,34 @@
         public final List<HiddenNetwork> hiddenNetworks = new ArrayList<>();
         /**
          * period of background scan; in millisecond, 0 => single shot scan
-         * @deprecated Background scan support is removed.
+         * @deprecated Background scan support has always been hardware vendor dependent. This
+         * support may not be present on newer devices. Use {@link #startScan(ScanSettings,
+         * ScanListener)} instead for single scans.
          */
         @Deprecated
         public int periodInMs;
         /**
          * must have a valid REPORT_EVENT value
-         * @deprecated Background scan support is removed.
+         * @deprecated Background scan support has always been hardware vendor dependent. This
+         * support may not be present on newer devices. Use {@link #startScan(ScanSettings,
+         * ScanListener)} instead for single scans.
          */
         @Deprecated
         public int reportEvents;
         /**
          * defines number of bssids to cache from each scan
-         * @deprecated Background scan support is removed.
+         * @deprecated Background scan support has always been hardware vendor dependent. This
+         * support may not be present on newer devices. Use {@link #startScan(ScanSettings,
+         * ScanListener)} instead for single scans.
          */
         @Deprecated
         public int numBssidsPerScan;
         /**
          * defines number of scans to cache; use it with REPORT_EVENT_AFTER_BUFFER_FULL
          * to wake up at fixed interval
-         * @deprecated Background scan support is removed.
+         * @deprecated Background scan support has always been hardware vendor dependent. This
+         * support may not be present on newer devices. Use {@link #startScan(ScanSettings,
+         * ScanListener)} instead for single scans.
          */
         @Deprecated
         public int maxScansToCache;
@@ -321,14 +329,18 @@
          * a truncated binary exponential backoff bucket and the scan period will grow
          * exponentially as per formula: actual_period(N) = period * (2 ^ (N/stepCount))
          * to maxPeriodInMs
-         * @deprecated Background scan support is removed.
+         * @deprecated Background scan support has always been hardware vendor dependent. This
+         * support may not be present on newer devices. Use {@link #startScan(ScanSettings,
+         * ScanListener)} instead for single scans.
          */
         @Deprecated
         public int maxPeriodInMs;
         /**
          * for truncated binary exponential back off bucket, number of scans to perform
          * for a given period
-         * @deprecated Background scan support is removed.
+         * @deprecated Background scan support has always been hardware vendor dependent. This
+         * support may not be present on newer devices. Use {@link #startScan(ScanSettings,
+         * ScanListener)} instead for single scans.
          */
         @Deprecated
         public int stepCount;
@@ -806,7 +818,9 @@
         /**
          * Framework co-ordinates scans across multiple apps; so it may not give exactly the
          * same period requested. If period of a scan is changed; it is reported by this event.
-         * @deprecated Background scan support is removed.
+         * @deprecated Background scan support has always been hardware vendor dependent. This
+         * support may not be present on newer devices. Use {@link #startScan(ScanSettings,
+         * ScanListener)} instead for single scans.
          */
         @Deprecated
         public void onPeriodChanged(int periodInMs);
@@ -913,7 +927,9 @@
      * @param listener specifies the object to report events to. This object is also treated as a
      *                 key for this scan, and must also be specified to cancel the scan. Multiple
      *                 scans should also not share this object.
-     * @deprecated Background scan support is removed.
+     * @deprecated Background scan support has always been hardware vendor dependent. This support
+     * may not be present on newer devices. Use {@link #startScan(ScanSettings, ScanListener)}
+     * instead for single scans.
      */
     @Deprecated
     @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
@@ -935,7 +951,9 @@
      * stop an ongoing wifi scan
      * @param listener specifies which scan to cancel; must be same object as passed in {@link
      *  #startBackgroundScan}
-     * @deprecated Background scan support is removed.
+     * @deprecated Background scan support has always been hardware vendor dependent. This support
+     * may not be present on newer devices. Use {@link #startScan(ScanSettings, ScanListener)}
+     * instead for single scans.
      */
     @Deprecated
     @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
@@ -953,7 +971,9 @@
     /**
      * reports currently available scan results on appropriate listeners
      * @return true if all scan results were reported correctly
-     * @deprecated Background scan support is removed.
+     * @deprecated Background scan support has always been hardware vendor dependent. This support
+     * may not be present on newer devices. Use {@link #startScan(ScanSettings, ScanListener)}
+     * instead for single scans.
      */
     @Deprecated
     @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareAgentNetworkSpecifier.java b/wifi/java/android/net/wifi/aware/WifiAwareAgentNetworkSpecifier.java
index c667334..a4b3e86 100644
--- a/wifi/java/android/net/wifi/aware/WifiAwareAgentNetworkSpecifier.java
+++ b/wifi/java/android/net/wifi/aware/WifiAwareAgentNetworkSpecifier.java
@@ -143,12 +143,6 @@
     }
 
     @Override
-    public void assertValidFromUid(int requestorUid) {
-        throw new SecurityException(
-                "WifiAwareAgentNetworkSpecifier should not be used in network requests");
-    }
-
-    @Override
     public NetworkSpecifier redact() {
         return null;
     }
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareManager.java b/wifi/java/android/net/wifi/aware/WifiAwareManager.java
index 81bf81e..2ebaa18 100644
--- a/wifi/java/android/net/wifi/aware/WifiAwareManager.java
+++ b/wifi/java/android/net/wifi/aware/WifiAwareManager.java
@@ -34,7 +34,6 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
-import android.os.Process;
 import android.os.RemoteException;
 import android.util.Log;
 
@@ -447,8 +446,7 @@
                 pmk,
                 passphrase,
                 0, // no port info for deprecated IB APIs
-                -1, // no transport info for deprecated IB APIs
-                Process.myUid());
+                -1); // no transport info for deprecated IB APIs
     }
 
     /** @hide */
@@ -488,8 +486,7 @@
                 pmk,
                 passphrase,
                 0, // no port info for OOB APIs
-                -1, // no transport protocol info for OOB APIs
-                Process.myUid());
+                -1); // no transport protocol info for OOB APIs
     }
 
     private static class WifiAwareEventCallbackProxy extends IWifiAwareEventCallback.Stub {
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareNetworkSpecifier.java b/wifi/java/android/net/wifi/aware/WifiAwareNetworkSpecifier.java
index 5a4ed3c..65ac1ab 100644
--- a/wifi/java/android/net/wifi/aware/WifiAwareNetworkSpecifier.java
+++ b/wifi/java/android/net/wifi/aware/WifiAwareNetworkSpecifier.java
@@ -23,7 +23,6 @@
 import android.net.NetworkSpecifier;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.os.Process;
 import android.text.TextUtils;
 
 import java.util.Arrays;
@@ -144,19 +143,9 @@
      */
     public final int transportProtocol;
 
-    /**
-     * The UID of the process initializing this network specifier. Validated by receiver using
-     * checkUidIfNecessary() and is used by satisfiedBy() to determine whether matches the
-     * offered network.
-     *
-     * @hide
-     */
-    public final int requestorUid;
-
     /** @hide */
     public WifiAwareNetworkSpecifier(int type, int role, int clientId, int sessionId, int peerId,
-            byte[] peerMac, byte[] pmk, String passphrase, int port, int transportProtocol,
-            int requestorUid) {
+            byte[] peerMac, byte[] pmk, String passphrase, int port, int transportProtocol) {
         this.type = type;
         this.role = role;
         this.clientId = clientId;
@@ -167,7 +156,6 @@
         this.passphrase = passphrase;
         this.port = port;
         this.transportProtocol = transportProtocol;
-        this.requestorUid = requestorUid;
     }
 
     public static final @android.annotation.NonNull Creator<WifiAwareNetworkSpecifier> CREATOR =
@@ -184,8 +172,7 @@
                         in.createByteArray(), // pmk
                         in.readString(), // passphrase
                         in.readInt(), // port
-                        in.readInt(), // transportProtocol
-                        in.readInt()); // requestorUid
+                        in.readInt()); // transportProtocol
                 }
 
                 @Override
@@ -221,7 +208,6 @@
         dest.writeString(passphrase);
         dest.writeInt(port);
         dest.writeInt(transportProtocol);
-        dest.writeInt(requestorUid);
     }
 
     /** @hide */
@@ -238,7 +224,7 @@
     @Override
     public int hashCode() {
         return Objects.hash(type, role, clientId, sessionId, peerId, Arrays.hashCode(peerMac),
-                Arrays.hashCode(pmk), passphrase, port, transportProtocol, requestorUid);
+                Arrays.hashCode(pmk), passphrase, port, transportProtocol);
     }
 
     /** @hide */
@@ -263,8 +249,7 @@
                 && Arrays.equals(pmk, lhs.pmk)
                 && Objects.equals(passphrase, lhs.passphrase)
                 && port == lhs.port
-                && transportProtocol == lhs.transportProtocol
-                && requestorUid == lhs.requestorUid;
+                && transportProtocol == lhs.transportProtocol;
     }
 
     /** @hide */
@@ -283,19 +268,11 @@
                 // masking PII
                 .append(", passphrase=").append((passphrase == null) ? "<null>" : "<non-null>")
                 .append(", port=").append(port).append(", transportProtocol=")
-                .append(transportProtocol).append(", requestorUid=").append(requestorUid)
+                .append(transportProtocol)
                 .append("]");
         return sb.toString();
     }
 
-    /** @hide */
-    @Override
-    public void assertValidFromUid(int requestorUid) {
-        if (this.requestorUid != requestorUid) {
-            throw new SecurityException("mismatched UIDs");
-        }
-    }
-
     /**
      * A builder class for a Wi-Fi Aware network specifier to set up an Aware connection with a
      * peer.
@@ -463,7 +440,7 @@
             return new WifiAwareNetworkSpecifier(
                     WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB, role,
                     mDiscoverySession.mClientId, mDiscoverySession.mSessionId, mPeerHandle.peerId,
-                    null, mPmk, mPskPassphrase, mPort, mTransportProtocol, Process.myUid());
+                    null, mPmk, mPskPassphrase, mPort, mTransportProtocol);
         }
     }
 }
diff --git a/wifi/java/android/net/wifi/wificond/NativeScanResult.java b/wifi/java/android/net/wifi/wificond/NativeScanResult.java
index 85251e8b..7cc617d 100644
--- a/wifi/java/android/net/wifi/wificond/NativeScanResult.java
+++ b/wifi/java/android/net/wifi/wificond/NativeScanResult.java
@@ -24,7 +24,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
-import java.util.BitSet;
 import java.util.List;
 
 /**
@@ -34,8 +33,6 @@
  */
 @SystemApi
 public final class NativeScanResult implements Parcelable {
-    private static final int CAPABILITY_SIZE = 16;
-
     /** @hide */
     @VisibleForTesting
     public byte[] ssid;
@@ -56,7 +53,7 @@
     public long tsf;
     /** @hide */
     @VisibleForTesting
-    public BitSet capability;
+    public int capability;
     /** @hide */
     @VisibleForTesting
     public boolean associated;
@@ -134,7 +131,7 @@
      *  Returns the capabilities of the AP repseresented by this scan result as advertised in the
      *  received probe response or beacon.
      *
-     *  This is a bit mask describing the capabilities of a BSS. See IEEE Std 802.11: 8.4.1.4:
+     *  This is a bit mask describing the capabilities of a BSS. See IEEE Std 802.11: 9.4.1.4:
      *    Bit 0 - ESS
      *    Bit 1 - IBSS
      *    Bit 2 - CF Pollable
@@ -143,7 +140,7 @@
      *    Bit 5 - Short Preamble
      *    Bit 6 - PBCC
      *    Bit 7 - Channel Agility
-     *    Bit 8 - Spectrum Mgmt
+     *    Bit 8 - Spectrum Management
      *    Bit 9 - QoS
      *    Bit 10 - Short Slot Time
      *    Bit 11 - APSD
@@ -154,7 +151,7 @@
      *
      * @return a bit mask of capabilities.
      */
-    @NonNull public BitSet getCapabilities() {
+    @NonNull public int getCapabilities() {
         return capability;
     }
 
@@ -188,13 +185,7 @@
         out.writeInt(frequency);
         out.writeInt(signalMbm);
         out.writeLong(tsf);
-        int capabilityInt = 0;
-        for (int i = 0; i < CAPABILITY_SIZE; i++) {
-            if (capability.get(i)) {
-                capabilityInt |= 1 << i;
-            }
-        }
-        out.writeInt(capabilityInt);
+        out.writeInt(capability);
         out.writeInt(associated ? 1 : 0);
         out.writeTypedList(radioChainInfos);
     }
@@ -220,13 +211,7 @@
             result.frequency = in.readInt();
             result.signalMbm = in.readInt();
             result.tsf = in.readLong();
-            int capabilityInt = in.readInt();
-            result.capability = new BitSet(CAPABILITY_SIZE);
-            for (int i = 0; i < CAPABILITY_SIZE; i++) {
-                if ((capabilityInt & (1 << i)) != 0) {
-                    result.capability.set(i);
-                }
-            }
+            result.capability = in.readInt();
             result.associated = (in.readInt() != 0);
             result.radioChainInfos = new ArrayList<>();
             in.readTypedList(result.radioChainInfos, RadioChainInfo.CREATOR);
diff --git a/wifi/java/android/net/wifi/wificond/PnoSettings.java b/wifi/java/android/net/wifi/wificond/PnoSettings.java
index 57c9ca5..533d37d 100644
--- a/wifi/java/android/net/wifi/wificond/PnoSettings.java
+++ b/wifi/java/android/net/wifi/wificond/PnoSettings.java
@@ -16,6 +16,7 @@
 
 package android.net.wifi.wificond;
 
+import android.annotation.DurationMillisLong;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcel;
@@ -33,7 +34,7 @@
  */
 @SystemApi
 public final class PnoSettings implements Parcelable {
-    private int mIntervalMs;
+    private long mIntervalMs;
     private int mMin2gRssi;
     private int mMin5gRssi;
     private int mMin6gRssi;
@@ -47,17 +48,17 @@
      *
      * @return An interval in milliseconds.
      */
-    public int getIntervalMillis() {
+    public @DurationMillisLong long getIntervalMillis() {
         return mIntervalMs;
     }
 
     /**
      * Set the requested PNO scan interval in milliseconds.
      *
-     * @param intervalMs An interval in milliseconds.
+     * @param intervalMillis An interval in milliseconds.
      */
-    public void setIntervalMillis(int intervalMs) {
-        this.mIntervalMs = intervalMs;
+    public void setIntervalMillis(@DurationMillisLong long intervalMillis) {
+        this.mIntervalMs = intervalMillis;
     }
 
     /**
@@ -176,7 +177,7 @@
      **/
     @Override
     public void writeToParcel(@NonNull Parcel out, int flags) {
-        out.writeInt(mIntervalMs);
+        out.writeLong(mIntervalMs);
         out.writeInt(mMin2gRssi);
         out.writeInt(mMin5gRssi);
         out.writeInt(mMin6gRssi);
@@ -189,7 +190,7 @@
         @Override
         public PnoSettings createFromParcel(Parcel in) {
             PnoSettings result = new PnoSettings();
-            result.mIntervalMs = in.readInt();
+            result.mIntervalMs = in.readLong();
             result.mMin2gRssi = in.readInt();
             result.mMin5gRssi = in.readInt();
             result.mMin6gRssi = in.readInt();
diff --git a/wifi/java/android/net/wifi/wificond/WifiCondManager.java b/wifi/java/android/net/wifi/wificond/WifiCondManager.java
index 43aa1b6..7a31a5a 100644
--- a/wifi/java/android/net/wifi/wificond/WifiCondManager.java
+++ b/wifi/java/android/net/wifi/wificond/WifiCondManager.java
@@ -496,22 +496,17 @@
     }
 
     /**
-     * Initializes WifiCondManager & registers a death notification for the WifiCondManager which
-     * acts as a proxy for the wificond daemon (i.e. the death listener will be called when and if
-     * the wificond daemon dies).
-     *
-     * Note: This method clears any existing state in wificond daemon.
+     * Register a death notification for the WifiCondManager which acts as a proxy for the
+     * wificond daemon (i.e. the death listener will be called when and if the wificond daemon
+     * dies).
      *
      * @param deathEventHandler A {@link Runnable} to be called whenever the wificond daemon dies.
-     * @return Returns true on success.
      */
-    public boolean initialize(@NonNull Runnable deathEventHandler) {
+    public void setOnServiceDeadCallback(@NonNull Runnable deathEventHandler) {
         if (mDeathEventHandler != null) {
             Log.e(TAG, "Death handler already present");
         }
         mDeathEventHandler = deathEventHandler;
-        tearDownInterfaces();
-        return true;
     }
 
     /**
@@ -603,11 +598,12 @@
     }
 
     /**
-     * Tear down a specific client (STA) interface, initially configured using
+     * Tear down a specific client (STA) interface configured using
      * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}.
      *
      * @param ifaceName Name of the interface to tear down.
-     * @return Returns true on success.
+     * @return Returns true on success, false on failure (e.g. when called before an interface was
+     * set up).
      */
     public boolean tearDownClientInterface(@NonNull String ifaceName) {
         if (getClientInterface(ifaceName) == null) {
@@ -681,11 +677,12 @@
     }
 
     /**
-     * Tear down a Soft AP interface initially configured using
+     * Tear down a Soft AP interface configured using
      * {@link #setupInterfaceForSoftApMode(String)}.
      *
      * @param ifaceName Name of the interface to tear down.
-     * @return Returns true on success.
+     * @return Returns true on success, false on failure (e.g. when called before an interface was
+     * set up).
      */
     public boolean tearDownSoftApInterface(@NonNull String ifaceName) {
         if (getApInterface(ifaceName) == null) {
@@ -750,9 +747,13 @@
     /**
      * Request signal polling.
      *
-     * @param ifaceName Name of the interface on which to poll.
+     * @param ifaceName Name of the interface on which to poll. The interface must have been
+     *                  already set up using
+     *{@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}
+     *                  or {@link #setupInterfaceForSoftApMode(String)}.
+     *
      * @return A {@link SignalPollResult} object containing interface statistics, or a null on
-     * error.
+     * error (e.g. the interface hasn't been set up yet).
      */
     @Nullable public SignalPollResult signalPoll(@NonNull String ifaceName) {
         IClientInterface iface = getClientInterface(ifaceName);
@@ -776,10 +777,14 @@
     }
 
     /**
-     * Get current transmit (Tx) packet counters of the specified interface.
+     * Get current transmit (Tx) packet counters of the specified interface. The interface must
+     * have been already set up using
+     * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}
+     * or {@link #setupInterfaceForSoftApMode(String)}.
      *
      * @param ifaceName Name of the interface.
-     * @return {@link TxPacketCounters} of the current interface or null on error.
+     * @return {@link TxPacketCounters} of the current interface or null on error (e.g. when
+     * called before the interface has been set up).
      */
     @Nullable public TxPacketCounters getTxPacketCounters(@NonNull String ifaceName) {
         IClientInterface iface = getClientInterface(ifaceName);
@@ -813,10 +818,15 @@
      * be done using {@link #startScan(String, int, Set, List)} or
      * {@link #startPnoScan(String, PnoSettings, Executor, PnoScanRequestCallback)}.
      *
+     * Note: The interface must have been already set up using
+     * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}
+     * or {@link #setupInterfaceForSoftApMode(String)}.
+     *
      * @param ifaceName Name of the interface.
      * @param scanType The type of scan result to be returned, can be
      * {@link #SCAN_TYPE_SINGLE_SCAN} or {@link #SCAN_TYPE_PNO_SCAN}.
-     * @return Returns an array of {@link NativeScanResult} or an empty array on failure.
+     * @return Returns an array of {@link NativeScanResult} or an empty array on failure (e.g. when
+     * called before the interface has been set up).
      */
     @NonNull public List<NativeScanResult> getScanResults(@NonNull String ifaceName,
             @ScanResultType int scanType) {
@@ -869,13 +879,19 @@
      * The latest scans can be obtained using {@link #getScanResults(String, int)} and using a
      * {@link #SCAN_TYPE_SINGLE_SCAN} for the {@code scanType}.
      *
+     * Note: The interface must have been already set up using
+     * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}
+     * or {@link #setupInterfaceForSoftApMode(String)}.
+     *
      * @param ifaceName Name of the interface on which to initiate the scan.
      * @param scanType Type of scan to perform, can be any of
      * {@link WifiScanner#SCAN_TYPE_HIGH_ACCURACY}, {@link WifiScanner#SCAN_TYPE_LOW_POWER}, or
      * {@link WifiScanner#SCAN_TYPE_LOW_LATENCY}.
      * @param freqs list of frequencies to scan for, if null scan all supported channels.
-     * @param hiddenNetworkSSIDs List of hidden networks to be scanned for.
-     * @return Returns true on success.
+     * @param hiddenNetworkSSIDs List of hidden networks to be scanned for, a null indicates that
+     *                           no hidden frequencies will be scanned for.
+     * @return Returns true on success, false on failure (e.g. when called before the interface
+     * has been set up).
      */
     public boolean startScan(@NonNull String ifaceName, @WifiAnnotations.ScanType int scanType,
             @Nullable Set<Integer> freqs, @Nullable List<byte[]> hiddenNetworkSSIDs) {
@@ -931,11 +947,16 @@
      * The latest PNO scans can be obtained using {@link #getScanResults(String, int)} with the
      * {@code scanType} set to {@link #SCAN_TYPE_PNO_SCAN}.
      *
+     * Note: The interface must have been already set up using
+     * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}
+     * or {@link #setupInterfaceForSoftApMode(String)}.
+     *
      * @param ifaceName Name of the interface on which to request a PNO.
      * @param pnoSettings PNO scan configuration.
      * @param executor The Executor on which to execute the callback.
      * @param callback Callback for the results of the offload request.
-     * @return true on success.
+     * @return true on success, false on failure (e.g. when called before the interface has been set
+     * up).
      */
     public boolean startPnoScan(@NonNull String ifaceName, @NonNull PnoSettings pnoSettings,
             @NonNull @CallbackExecutor Executor executor,
@@ -969,8 +990,13 @@
      * Stop PNO scan configured with
      * {@link #startPnoScan(String, PnoSettings, Executor, PnoScanRequestCallback)}.
      *
+     * Note: The interface must have been already set up using
+     * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}
+     * or {@link #setupInterfaceForSoftApMode(String)}.
+     *
      * @param ifaceName Name of the interface on which the PNO scan was configured.
-     * @return true on success.
+     * @return true on success, false on failure (e.g. when called before the interface has been
+     * set up).
      */
     public boolean stopPnoScan(@NonNull String ifaceName) {
         IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName);
@@ -987,7 +1013,13 @@
     }
 
     /**
-     * Abort ongoing single scan started with {@link #startScan(String, int, Set, List)}.
+     * Abort ongoing single scan started with {@link #startScan(String, int, Set, List)}. No failure
+     * callback, e.g. {@link ScanEventCallback#onScanFailed()}, is triggered by this operation.
+     *
+     * Note: The interface must have been already set up using
+     * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}
+     * or {@link #setupInterfaceForSoftApMode(String)}. If the interface has not been set up then
+     * this method has no impact.
      *
      * @param ifaceName Name of the interface on which the scan was started.
      */
@@ -1055,7 +1087,14 @@
     }
 
     /**
-     * Get the device phy capabilities for a given interface
+     * Get the device phy capabilities for a given interface.
+     *
+     * Note: The interface must have been already set up using
+     * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}
+     * or {@link #setupInterfaceForSoftApMode(String)}.
+     *
+     * @return DeviceWiphyCapabilities or null on error (e.g. when called on an interface which has
+     * not been set up).
      */
     @Nullable public DeviceWiphyCapabilities getDeviceWiphyCapabilities(@NonNull String ifaceName) {
         if (mWificond == null) {
@@ -1071,13 +1110,19 @@
     }
 
     /**
-     * Register the provided callback handler for SoftAp events. Note that the Soft AP itself is
-     * configured using {@link #setupInterfaceForSoftApMode(String)}.
+     * Register the provided callback handler for SoftAp events. The interface must first be created
+     * using {@link #setupInterfaceForSoftApMode(String)}. The callback registration is valid until
+     * the interface is deleted using {@link #tearDownSoftApInterface(String)} (no deregistration
+     * method is provided).
+     * <p>
+     * Note that only one callback can be registered at a time - any registration overrides previous
+     * registrations.
      *
      * @param ifaceName Name of the interface on which to register the callback.
      * @param executor The Executor on which to execute the callbacks.
      * @param callback Callback for AP events.
-     * @return true on success, false otherwise.
+     * @return true on success, false on failure (e.g. when called on an interface which has not
+     * been set up).
      */
     public boolean registerApCallback(@NonNull String ifaceName,
             @NonNull @CallbackExecutor Executor executor,
@@ -1113,6 +1158,10 @@
      * Send a management frame on the specified interface at the specified rate. Useful for probing
      * the link with arbitrary frames.
      *
+     * Note: The interface must have been already set up using
+     * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}
+     * or {@link #setupInterfaceForSoftApMode(String)}.
+     *
      * @param ifaceName The interface on which to send the frame.
      * @param frame The raw byte array of the management frame to tramit.
      * @param mcs The MCS (modulation and coding scheme), i.e. rate, at which to transmit the
diff --git a/wifi/java/com/android/server/wifi/BaseWifiService.java b/wifi/java/com/android/server/wifi/BaseWifiService.java
deleted file mode 100644
index 060c85c..0000000
--- a/wifi/java/com/android/server/wifi/BaseWifiService.java
+++ /dev/null
@@ -1,641 +0,0 @@
-/**
- * Copyright (c) 2018, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License") {
- *  throw new UnsupportedOperationException();
- }
- * 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.wifi;
-
-import android.content.pm.ParceledListSlice;
-import android.net.DhcpInfo;
-import android.net.Network;
-import android.net.wifi.IActionListener;
-import android.net.wifi.IDppCallback;
-import android.net.wifi.ILocalOnlyHotspotCallback;
-import android.net.wifi.INetworkRequestMatchCallback;
-import android.net.wifi.IOnWifiActivityEnergyInfoListener;
-import android.net.wifi.IOnWifiUsabilityStatsListener;
-import android.net.wifi.IScanResultsCallback;
-import android.net.wifi.ISoftApCallback;
-import android.net.wifi.ISuggestionConnectionStatusListener;
-import android.net.wifi.ITrafficStateCallback;
-import android.net.wifi.ITxPacketCountListener;
-import android.net.wifi.IWifiConnectedNetworkScorer;
-import android.net.wifi.IWifiManager;
-import android.net.wifi.ScanResult;
-import android.net.wifi.SoftApConfiguration;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiInfo;
-import android.net.wifi.WifiManager;
-import android.net.wifi.WifiNetworkSuggestion;
-import android.net.wifi.hotspot2.IProvisioningCallback;
-import android.net.wifi.hotspot2.OsuProvider;
-import android.net.wifi.hotspot2.PasspointConfiguration;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.ResultReceiver;
-import android.os.WorkSource;
-import android.os.connectivity.WifiActivityEnergyInfo;
-
-import java.util.List;
-import java.util.Map;
-
-/**
- * Empty concrete class implementing IWifiManager with stub methods throwing runtime exceptions.
- *
- * This class is meant to be extended by real implementations of IWifiManager in order to facilitate
- * cross-repo changes to WiFi internal APIs, including the introduction of new APIs, the removal of
- * deprecated APIs, or the migration of existing API signatures.
- *
- * When an existing API is scheduled for removal, it can be removed from IWifiManager.aidl
- * immediately and marked as @Deprecated first in this class. Children inheriting this class are
- * then given a short grace period to update themselves before the @Deprecated stub is removed for
- * good. If the API scheduled for removal has a replacement or an overload (signature change),
- * these should be introduced before the stub is removed to allow children to migrate.
- *
- * When a new API is added to IWifiManager.aidl, a stub should be added in BaseWifiService as
- * well otherwise compilation will fail.
- */
-public class BaseWifiService extends IWifiManager.Stub {
-
-    private static final String TAG = BaseWifiService.class.getSimpleName();
-
-    @Override
-    public long getSupportedFeatures() {
-        throw new UnsupportedOperationException();
-    }
-
-    /** @deprecated use {@link #getWifiActivityEnergyInfoAsync} instead */
-    @Deprecated
-    public WifiActivityEnergyInfo reportActivityInfo() {
-        throw new UnsupportedOperationException();
-    }
-
-    /** @deprecated use {@link #getWifiActivityEnergyInfoAsync} instead */
-    @Deprecated
-    public void requestActivityInfo(ResultReceiver result) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void getWifiActivityEnergyInfoAsync(IOnWifiActivityEnergyInfoListener listener) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public ParceledListSlice getConfiguredNetworks(String packageName, String featureId) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public ParceledListSlice getPrivilegedConfiguredNetworks(String packageName, String featureId) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public Map<String, Map<Integer, List<ScanResult>>> getAllMatchingFqdnsForScanResults(
-            List<ScanResult> scanResults) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public Map<OsuProvider, List<ScanResult>> getMatchingOsuProviders(
-            List<ScanResult> scanResults) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public Map<OsuProvider, PasspointConfiguration> getMatchingPasspointConfigsForOsuProviders(
-            List<OsuProvider> osuProviders) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public int addOrUpdateNetwork(WifiConfiguration config, String packageName) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public boolean addOrUpdatePasspointConfiguration(
-            PasspointConfiguration config, String packageName) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public boolean removePasspointConfiguration(String fqdn, String packageName) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public List<PasspointConfiguration> getPasspointConfigurations(String packageName) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public List<WifiConfiguration> getWifiConfigsForPasspointProfiles(List<String> fqdnList) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void queryPasspointIcon(long bssid, String fileName) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public int matchProviderWithCurrentNetwork(String fqdn) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void deauthenticateNetwork(long holdoff, boolean ess) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public boolean removeNetwork(int netId, String packageName) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public boolean enableNetwork(int netId, boolean disableOthers, String packageName) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public boolean disableNetwork(int netId, String packageName) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void allowAutojoinGlobal(boolean choice) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void allowAutojoin(int netId, boolean choice) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void allowAutojoinPasspoint(String fqdn, boolean enableAutoJoin) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void setMacRandomizationSettingPasspointEnabled(String fqdn, boolean enable) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void setMeteredOverridePasspoint(String fqdn, int meteredOverride) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public boolean startScan(String packageName, String featureId) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public List<ScanResult> getScanResults(String callingPackage, String callingFeatureId) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public boolean disconnect(String packageName) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public boolean reconnect(String packageName) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public boolean reassociate(String packageName) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public WifiInfo getConnectionInfo(String callingPackage, String callingFeatureId) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public boolean setWifiEnabled(String packageName, boolean enable) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public int getWifiEnabledState() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public String getCountryCode() {
-        throw new UnsupportedOperationException();
-    }
-
-    /** @deprecated use {@link #is5GHzBandSupported} instead */
-    @Deprecated
-    public boolean isDualBandSupported() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public boolean is5GHzBandSupported() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public boolean is6GHzBandSupported() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public boolean isWifiStandardSupported(int standard) {
-        throw new UnsupportedOperationException();
-    }
-
-    /** @deprecated use {@link WifiManager#isStaApConcurrencySupported()} */
-    @Deprecated
-    public boolean needs5GHzToAnyApBandConversion() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public DhcpInfo getDhcpInfo() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public boolean isScanAlwaysAvailable() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public boolean acquireWifiLock(IBinder lock, int lockType, String tag, WorkSource ws) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void updateWifiLockWorkSource(IBinder lock, WorkSource ws) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public boolean releaseWifiLock(IBinder lock) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void initializeMulticastFiltering() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public boolean isMulticastEnabled() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void acquireMulticastLock(IBinder binder, String tag) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void releaseMulticastLock(String tag) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void updateInterfaceIpState(String ifaceName, int mode) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public boolean startSoftAp(WifiConfiguration wifiConfig) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public boolean startTetheredHotspot(SoftApConfiguration softApConfig) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public boolean stopSoftAp() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public int startLocalOnlyHotspot(ILocalOnlyHotspotCallback callback, String packageName,
-            String featureId, SoftApConfiguration customConfig) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void stopLocalOnlyHotspot() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void startWatchLocalOnlyHotspot(ILocalOnlyHotspotCallback callback) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void stopWatchLocalOnlyHotspot() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public int getWifiApEnabledState() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public WifiConfiguration getWifiApConfiguration() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public SoftApConfiguration getSoftApConfiguration() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public boolean setWifiApConfiguration(WifiConfiguration wifiConfig, String packageName) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public boolean setSoftApConfiguration(SoftApConfiguration softApConfig, String packageName) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void notifyUserOfApBandConversion(String packageName) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void enableTdls(String remoteIPAddress, boolean enable) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void enableTdlsWithMacAddress(String remoteMacAddress, boolean enable) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public String getCurrentNetworkWpsNfcConfigurationToken() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void enableVerboseLogging(int verbose) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public int getVerboseLoggingLevel() {
-        throw new UnsupportedOperationException();
-    }
-
-    /** @deprecated use {@link #allowAutojoinGlobal(boolean)} instead */
-    @Deprecated
-    public void enableWifiConnectivityManager(boolean enabled) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void disableEphemeralNetwork(String SSID, String packageName) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void factoryReset(String packageName) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public Network getCurrentNetwork() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public byte[] retrieveBackupData() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void restoreBackupData(byte[] data) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public byte[] retrieveSoftApBackupData() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public SoftApConfiguration restoreSoftApBackupData(byte[] data) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void restoreSupplicantBackupData(byte[] supplicantData, byte[] ipConfigData) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void startSubscriptionProvisioning(
-            OsuProvider provider, IProvisioningCallback callback) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void registerSoftApCallback(
-            IBinder binder, ISoftApCallback callback, int callbackIdentifier) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void unregisterSoftApCallback(int callbackIdentifier) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void registerTrafficStateCallback(
-            IBinder binder, ITrafficStateCallback callback, int callbackIdentifier) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void unregisterTrafficStateCallback(int callbackIdentifier) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void registerNetworkRequestMatchCallback(
-            IBinder binder, INetworkRequestMatchCallback callback, int callbackIdentifier) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void unregisterNetworkRequestMatchCallback(int callbackIdentifier) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public int addNetworkSuggestions(
-            List<WifiNetworkSuggestion> networkSuggestions, String callingPackageName,
-            String callingFeatureId) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public int removeNetworkSuggestions(
-            List<WifiNetworkSuggestion> networkSuggestions, String callingPackageName) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public List<WifiNetworkSuggestion> getNetworkSuggestions(String packageName) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public String[] getFactoryMacAddresses() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void setDeviceMobilityState(int state) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void startDppAsConfiguratorInitiator(IBinder binder, String enrolleeUri,
-            int selectedNetworkId, int netRole, IDppCallback callback) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void startDppAsEnrolleeInitiator(IBinder binder, String configuratorUri,
-            IDppCallback callback) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void stopDppSession() throws RemoteException {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void addOnWifiUsabilityStatsListener(
-            IBinder binder, IOnWifiUsabilityStatsListener listener, int listenerIdentifier) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void removeOnWifiUsabilityStatsListener(int listenerIdentifier) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void updateWifiUsabilityScore(int seqNum, int score, int predictionHorizonSec) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void connect(WifiConfiguration config, int netId, IBinder binder,
-            IActionListener callback, int callbackIdentifier) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void save(WifiConfiguration config, IBinder binder, IActionListener callback,
-            int callbackIdentifier) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void forget(int netId, IBinder binder, IActionListener callback,
-            int callbackIdentifier) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void getTxPacketCount(String packageName, IBinder binder,
-            ITxPacketCountListener callback, int callbackIdentifier) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void registerScanResultsCallback(IScanResultsCallback callback) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void unregisterScanResultsCallback(IScanResultsCallback callback) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void registerSuggestionConnectionStatusListener(IBinder binder,
-            ISuggestionConnectionStatusListener listener,
-            int listenerIdentifier, String packageName, String featureId) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void unregisterSuggestionConnectionStatusListener(int listenerIdentifier,
-            String packageName) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public int calculateSignalLevel(int rssi) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public List<WifiConfiguration> getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(
-            List<ScanResult> scanResults) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public boolean setWifiConnectedNetworkScorer(IBinder binder,
-            IWifiConnectedNetworkScorer scorer) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void clearWifiConnectedNetworkScorer() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public Map<WifiNetworkSuggestion, List<ScanResult>> getMatchingScanResults(
-            List<WifiNetworkSuggestion> networkSuggestions,
-            List<ScanResult> scanResults,
-            String callingPackage, String callingFeatureId) {
-        throw new UnsupportedOperationException();
-    }
-}
diff --git a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
index 8023160..05a3dce 100644
--- a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
@@ -336,6 +336,20 @@
     }
 
     /**
+     * Ensure that {@link NetworkSelectionStatus#getMaxNetworkSelectionDisableReason()} returns
+     * the maximum disable reason.
+     */
+    @Test
+    public void testNetworkSelectionGetMaxNetworkSelectionDisableReason() {
+        int maxReason = Integer.MIN_VALUE;
+        for (int i = 0; i < NetworkSelectionStatus.DISABLE_REASON_INFOS.size(); i++) {
+            int reason = NetworkSelectionStatus.DISABLE_REASON_INFOS.keyAt(i);
+            maxReason = Math.max(maxReason, reason);
+        }
+        assertEquals(maxReason, NetworkSelectionStatus.getMaxNetworkSelectionDisableReason());
+    }
+
+    /**
      * Ensure that {@link WifiConfiguration#setSecurityParams(int)} sets up the
      * {@link WifiConfiguration} object correctly for SAE security type.
      * @throws Exception
diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
index a189d50..6320f85 100644
--- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
@@ -1867,7 +1867,7 @@
      * Tests that passing a null Executor to {@link WifiManager#getWifiActivityEnergyInfoAsync}
      * throws an exception.
      */
-    @Test(expected = IllegalArgumentException.class)
+    @Test(expected = NullPointerException.class)
     public void testGetWifiActivityInfoNullExecutor() throws Exception {
         mWifiManager.getWifiActivityEnergyInfoAsync(null, mOnWifiActivityEnergyInfoListener);
     }
@@ -1876,7 +1876,7 @@
      * Tests that passing a null listener to {@link WifiManager#getWifiActivityEnergyInfoAsync}
      * throws an exception.
      */
-    @Test(expected = IllegalArgumentException.class)
+    @Test(expected = NullPointerException.class)
     public void testGetWifiActivityInfoNullListener() throws Exception {
         mWifiManager.getWifiActivityEnergyInfoAsync(mExecutor, null);
     }
diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkAgentSpecifierTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkAgentSpecifierTest.java
index adc41f0..0233ee2 100644
--- a/wifi/tests/src/android/net/wifi/WifiNetworkAgentSpecifierTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiNetworkAgentSpecifierTest.java
@@ -22,7 +22,6 @@
 
 import android.net.MacAddress;
 import android.net.MatchAllNetworkSpecifier;
-import android.net.NetworkRequest;
 import android.os.Parcel;
 import android.os.PatternMatcher;
 import android.util.Pair;
@@ -36,10 +35,6 @@
  */
 @SmallTest
 public class WifiNetworkAgentSpecifierTest {
-    private static final int TEST_UID = 5;
-    private static final int TEST_UID_1 = 8;
-    private static final String TEST_PACKAGE = "com.test";
-    private static final String TEST_PACKAGE_1 = "com.test.1";
     private static final String TEST_SSID = "Test123";
     private static final String TEST_SSID_PATTERN = "Test";
     private static final String TEST_SSID_1 = "456test";
@@ -71,16 +66,6 @@
     }
 
     /**
-     * Validate that the NetworkAgentSpecifier cannot be used in a {@link NetworkRequest} by apps.
-     */
-    @Test(expected = IllegalStateException.class)
-    public void testWifiNetworkAgentSpecifierNotUsedInNetworkRequest() {
-        WifiNetworkAgentSpecifier specifier = createDefaultNetworkAgentSpecifier();
-
-        specifier.assertValidFromUid(TEST_UID);
-    }
-
-    /**
      * Validate NetworkAgentSpecifier equals with itself.
      * a) Create network agent specifier 1 for WPA_PSK network
      * b) Create network agent specifier 2 with the same params as specifier 1.
@@ -105,15 +90,13 @@
         WifiConfiguration wifiConfiguration1 = createDefaultWifiConfiguration();
         WifiNetworkAgentSpecifier specifier1 =
                 new WifiNetworkAgentSpecifier(
-                        wifiConfiguration1,
-                        TEST_UID, TEST_PACKAGE);
+                        wifiConfiguration1);
 
         WifiConfiguration wifiConfiguration2 = new WifiConfiguration(wifiConfiguration1);
         wifiConfiguration2.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
         WifiNetworkAgentSpecifier specifier2 =
                 new WifiNetworkAgentSpecifier(
-                        wifiConfiguration2,
-                        TEST_UID, TEST_PACKAGE);
+                        wifiConfiguration2);
 
         assertFalse(specifier2.equals(specifier1));
     }
@@ -129,15 +112,13 @@
         WifiConfiguration wifiConfiguration1 = createDefaultWifiConfiguration();
         WifiNetworkAgentSpecifier specifier1 =
                 new WifiNetworkAgentSpecifier(
-                        wifiConfiguration1,
-                        TEST_UID, TEST_PACKAGE);
+                        wifiConfiguration1);
 
         WifiConfiguration wifiConfiguration2 = new WifiConfiguration(wifiConfiguration1);
         wifiConfiguration2.SSID = TEST_SSID_1;
         WifiNetworkAgentSpecifier specifier2 =
                 new WifiNetworkAgentSpecifier(
-                        wifiConfiguration2,
-                        TEST_UID, TEST_PACKAGE);
+                        wifiConfiguration2);
 
         assertFalse(specifier2.equals(specifier1));
     }
@@ -153,15 +134,13 @@
         WifiConfiguration wifiConfiguration1 = createDefaultWifiConfiguration();
         WifiNetworkAgentSpecifier specifier1 =
                 new WifiNetworkAgentSpecifier(
-                        wifiConfiguration1,
-                        TEST_UID, TEST_PACKAGE);
+                        wifiConfiguration1);
 
         WifiConfiguration wifiConfiguration2 = new WifiConfiguration(wifiConfiguration1);
         wifiConfiguration2.BSSID = TEST_BSSID_1;
         WifiNetworkAgentSpecifier specifier2 =
                 new WifiNetworkAgentSpecifier(
-                        wifiConfiguration2,
-                        TEST_UID, TEST_PACKAGE);
+                        wifiConfiguration2);
 
         assertFalse(specifier2.equals(specifier1));
     }
@@ -215,8 +194,7 @@
         WifiNetworkSpecifier wifiNetworkSpecifier = new WifiNetworkSpecifier(
                 ssidPattern,
                 bssidPattern,
-                wificonfigurationNetworkSpecifier,
-                TEST_UID, TEST_PACKAGE);
+                wificonfigurationNetworkSpecifier);
 
         assertTrue(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier));
         assertTrue(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier));
@@ -244,8 +222,7 @@
         WifiNetworkSpecifier wifiNetworkSpecifier = new WifiNetworkSpecifier(
                 ssidPattern,
                 bssidPattern,
-                wificonfigurationNetworkSpecifier,
-                TEST_UID, TEST_PACKAGE);
+                wificonfigurationNetworkSpecifier);
 
         assertTrue(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier));
         assertTrue(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier));
@@ -273,8 +250,7 @@
         WifiNetworkSpecifier wifiNetworkSpecifier = new WifiNetworkSpecifier(
                 ssidPattern,
                 bssidPattern,
-                wificonfigurationNetworkSpecifier,
-                TEST_UID, TEST_PACKAGE);
+                wificonfigurationNetworkSpecifier);
 
         assertTrue(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier));
         assertTrue(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier));
@@ -293,8 +269,7 @@
         wifiConfigurationNetworkAgent.SSID = "\"" + TEST_SSID_1 + "\"";
         WifiNetworkAgentSpecifier wifiNetworkAgentSpecifier =
                 new WifiNetworkAgentSpecifier(
-                        wifiConfigurationNetworkAgent,
-                        TEST_UID, TEST_PACKAGE);
+                        wifiConfigurationNetworkAgent);
 
         PatternMatcher ssidPattern =
                 new PatternMatcher(TEST_SSID_PATTERN, PatternMatcher.PATTERN_PREFIX);
@@ -306,8 +281,7 @@
         WifiNetworkSpecifier wifiNetworkSpecifier = new WifiNetworkSpecifier(
                 ssidPattern,
                 bssidPattern,
-                wificonfigurationNetworkSpecifier,
-                TEST_UID, TEST_PACKAGE);
+                wificonfigurationNetworkSpecifier);
 
         assertFalse(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier));
         assertFalse(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier));
@@ -326,8 +300,7 @@
         wifiConfigurationNetworkAgent.BSSID = TEST_BSSID_1;
         WifiNetworkAgentSpecifier wifiNetworkAgentSpecifier =
                 new WifiNetworkAgentSpecifier(
-                        wifiConfigurationNetworkAgent,
-                        TEST_UID, TEST_PACKAGE);
+                        wifiConfigurationNetworkAgent);
 
         PatternMatcher ssidPattern =
                 new PatternMatcher(".*", PatternMatcher.PATTERN_SIMPLE_GLOB);
@@ -340,8 +313,7 @@
         WifiNetworkSpecifier wifiNetworkSpecifier = new WifiNetworkSpecifier(
                 ssidPattern,
                 bssidPattern,
-                wificonfigurationNetworkSpecifier,
-                TEST_UID, TEST_PACKAGE);
+                wificonfigurationNetworkSpecifier);
 
         assertFalse(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier));
         assertFalse(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier));
@@ -360,8 +332,7 @@
         wifiConfigurationNetworkAgent.BSSID = TEST_BSSID_1;
         WifiNetworkAgentSpecifier wifiNetworkAgentSpecifier =
                 new WifiNetworkAgentSpecifier(
-                        wifiConfigurationNetworkAgent,
-                        TEST_UID, TEST_PACKAGE);
+                        wifiConfigurationNetworkAgent);
 
         PatternMatcher ssidPattern =
                 new PatternMatcher(TEST_SSID_PATTERN, PatternMatcher.PATTERN_PREFIX);
@@ -374,8 +345,7 @@
         WifiNetworkSpecifier wifiNetworkSpecifier = new WifiNetworkSpecifier(
                 ssidPattern,
                 bssidPattern,
-                wificonfigurationNetworkSpecifier,
-                TEST_UID, TEST_PACKAGE);
+                wificonfigurationNetworkSpecifier);
 
         assertFalse(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier));
         assertFalse(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier));
@@ -402,41 +372,12 @@
         WifiNetworkSpecifier wifiNetworkSpecifier = new WifiNetworkSpecifier(
                 ssidPattern,
                 bssidPattern,
-                wificonfigurationNetworkSpecifier,
-                TEST_UID, TEST_PACKAGE);
+                wificonfigurationNetworkSpecifier);
 
         assertFalse(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier));
         assertFalse(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier));
     }
 
-    /**
-     * Validate {@link WifiNetworkAgentSpecifier} with {@link WifiNetworkSpecifier} matching.
-     * a) Create network agent specifier for WPA_PSK network
-     * b) Create network specifier with matching SSID and BSSID pattern, but different UID.
-     * c) Ensure that the agent specifier is not satisfied by specifier.
-     */
-    @Test
-    public void
-            testWifiNetworkAgentSpecifierDoesNotSatisfyNetworkSpecifierWithDifferentUid() {
-        WifiNetworkAgentSpecifier wifiNetworkAgentSpecifier = createDefaultNetworkAgentSpecifier();
-
-        PatternMatcher ssidPattern =
-                new PatternMatcher(TEST_SSID_PATTERN, PatternMatcher.PATTERN_PREFIX);
-        Pair<MacAddress, MacAddress> bssidPattern =
-                Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
-                        MacAddress.fromString(TEST_BSSID_OUI_MASK));
-        WifiConfiguration wificonfigurationNetworkSpecifier = new WifiConfiguration();
-        wificonfigurationNetworkSpecifier.allowedKeyManagement
-                .set(WifiConfiguration.KeyMgmt.WPA_PSK);
-        WifiNetworkSpecifier wifiNetworkSpecifier = new WifiNetworkSpecifier(
-                ssidPattern,
-                bssidPattern,
-                wificonfigurationNetworkSpecifier,
-                TEST_UID_1, TEST_PACKAGE_1);
-
-        assertFalse(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier));
-        assertFalse(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier));
-    }
 
     private WifiConfiguration createDefaultWifiConfiguration() {
         WifiConfiguration wifiConfiguration = new WifiConfiguration();
@@ -448,8 +389,7 @@
     }
 
     private WifiNetworkAgentSpecifier createDefaultNetworkAgentSpecifier() {
-        return new WifiNetworkAgentSpecifier(createDefaultWifiConfiguration(), TEST_UID,
-                TEST_PACKAGE);
+        return new WifiNetworkAgentSpecifier(createDefaultWifiConfiguration());
     }
 
 }
diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkSpecifierTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkSpecifierTest.java
index 1619744..3b67236 100644
--- a/wifi/tests/src/android/net/wifi/WifiNetworkSpecifierTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiNetworkSpecifierTest.java
@@ -29,7 +29,6 @@
 import android.net.NetworkSpecifier;
 import android.os.Parcel;
 import android.os.PatternMatcher;
-import android.os.Process;
 import android.util.Pair;
 
 import androidx.test.filters.SmallTest;
@@ -41,8 +40,6 @@
  */
 @SmallTest
 public class WifiNetworkSpecifierTest {
-    private static final int TEST_UID = 5;
-    private static final String TEST_PACKAGE_NAME = "com.test";
     private static final String TEST_SSID = "Test123";
     private static final String TEST_BSSID_OUI_BASE_ADDRESS = "12:12:12:00:00:00";
     private static final String TEST_BSSID_OUI_MASK = "ff:ff:ff:00:00:00";
@@ -62,7 +59,6 @@
         assertTrue(specifier instanceof WifiNetworkSpecifier);
         WifiNetworkSpecifier wifiNetworkSpecifier = (WifiNetworkSpecifier) specifier;
 
-        assertEquals(Process.myUid(), wifiNetworkSpecifier.requestorUid);
         assertEquals(TEST_SSID, wifiNetworkSpecifier.ssidPatternMatcher.getPath());
         assertEquals(PATTERN_PREFIX, wifiNetworkSpecifier.ssidPatternMatcher.getType());
         assertEquals(WifiManager.ALL_ZEROS_MAC_ADDRESS,
@@ -367,8 +363,7 @@
                 new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL),
                         Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
                                 MacAddress.fromString(TEST_BSSID_OUI_MASK)),
-                        wifiConfiguration,
-                        TEST_UID, TEST_PACKAGE_NAME);
+                        wifiConfiguration);
 
         Parcel parcelW = Parcel.obtain();
         specifier.writeToParcel(parcelW, 0);
@@ -399,8 +394,7 @@
                 new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL),
                         Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
                                 MacAddress.fromString(TEST_BSSID_OUI_MASK)),
-                        wifiConfiguration,
-                        TEST_UID, TEST_PACKAGE_NAME);
+                        wifiConfiguration);
 
         assertTrue(specifier.satisfiedBy(null));
         assertTrue(specifier.satisfiedBy(new MatchAllNetworkSpecifier()));
@@ -422,15 +416,13 @@
                 new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL),
                         Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
                                 MacAddress.fromString(TEST_BSSID_OUI_MASK)),
-                        wifiConfiguration,
-                        TEST_UID, TEST_PACKAGE_NAME);
+                        wifiConfiguration);
 
         WifiNetworkSpecifier specifier2 =
                 new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL),
                         Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
                                 MacAddress.fromString(TEST_BSSID_OUI_MASK)),
-                        wifiConfiguration,
-                        TEST_UID, TEST_PACKAGE_NAME);
+                        wifiConfiguration);
 
         assertTrue(specifier2.satisfiedBy(specifier1));
     }
@@ -451,8 +443,7 @@
                 new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL),
                         Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
                                 MacAddress.fromString(TEST_BSSID_OUI_MASK)),
-                        wifiConfiguration1,
-                        TEST_UID, TEST_PACKAGE_NAME);
+                        wifiConfiguration1);
 
         WifiConfiguration wifiConfiguration2 = new WifiConfiguration();
         wifiConfiguration2.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
@@ -460,8 +451,7 @@
                 new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL),
                         Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
                                 MacAddress.fromString(TEST_BSSID_OUI_MASK)),
-                        wifiConfiguration2,
-                        TEST_UID, TEST_PACKAGE_NAME);
+                        wifiConfiguration2);
 
         assertFalse(specifier2.satisfiedBy(specifier1));
     }
@@ -482,15 +472,13 @@
                 new WifiNetworkSpecifier(new PatternMatcher("", PATTERN_LITERAL),
                         Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
                                 MacAddress.fromString(TEST_BSSID_OUI_MASK)),
-                        wifiConfiguration,
-                        TEST_UID, TEST_PACKAGE_NAME);
+                        wifiConfiguration);
 
         WifiNetworkSpecifier specifier2 =
                 new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL),
                         Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
                                 MacAddress.fromString(TEST_BSSID_OUI_MASK)),
-                        wifiConfiguration,
-                        TEST_UID, TEST_PACKAGE_NAME);
+                        wifiConfiguration);
 
         assertFalse(specifier2.satisfiedBy(specifier1));
     }
@@ -511,44 +499,13 @@
                 new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL),
                         Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
                                 MacAddress.fromString(TEST_BSSID_OUI_MASK)),
-                        wifiConfiguration,
-                        TEST_UID, TEST_PACKAGE_NAME);
+                        wifiConfiguration);
 
         WifiNetworkSpecifier specifier2 =
                 new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL),
                         Pair.create(WifiManager.ALL_ZEROS_MAC_ADDRESS,
                                 WifiManager.ALL_ZEROS_MAC_ADDRESS),
-                        wifiConfiguration,
-                        TEST_UID, TEST_PACKAGE_NAME);
-
-        assertFalse(specifier2.satisfiedBy(specifier1));
-    }
-
-    /**
-     * Validate NetworkSpecifier matching.
-     * a) Create network specifier 1 for WPA_PSK network
-     * b) Create network specifier 2 with different package name .
-     * c) Ensure that the specifier 2 is not satisfied by specifier 1.
-     */
-    @Test
-    public void testWifiNetworkSpecifierDoesNotSatisfyWhenPackageNameDifferent() {
-        WifiConfiguration wifiConfiguration = new WifiConfiguration();
-        wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
-        wifiConfiguration.preSharedKey = TEST_PRESHARED_KEY;
-
-        WifiNetworkSpecifier specifier1 =
-                new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL),
-                        Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
-                                MacAddress.fromString(TEST_BSSID_OUI_MASK)),
-                        wifiConfiguration,
-                        TEST_UID, TEST_PACKAGE_NAME);
-
-        WifiNetworkSpecifier specifier2 =
-                new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL),
-                        Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
-                                MacAddress.fromString(TEST_BSSID_OUI_MASK)),
-                        wifiConfiguration,
-                        TEST_UID, TEST_PACKAGE_NAME + "blah");
+                        wifiConfiguration);
 
         assertFalse(specifier2.satisfiedBy(specifier1));
     }
diff --git a/wifi/tests/src/android/net/wifi/aware/WifiAwareAgentNetworkSpecifierTest.java b/wifi/tests/src/android/net/wifi/aware/WifiAwareAgentNetworkSpecifierTest.java
index c3b6285..81b02fa 100644
--- a/wifi/tests/src/android/net/wifi/aware/WifiAwareAgentNetworkSpecifierTest.java
+++ b/wifi/tests/src/android/net/wifi/aware/WifiAwareAgentNetworkSpecifierTest.java
@@ -162,17 +162,6 @@
         collector.checkThat("Match unexpected", oldNs.satisfiedBy(newNs), equalTo(false));
     }
 
-    /**
-     * Validate that agent network specifier cannot be used as in network requests - i.e. that
-     * throws an exception when queried for UID validity.
-     */
-    @Test(expected = SecurityException.class)
-    public void testNoUsageInRequest() {
-        WifiAwareAgentNetworkSpecifier dut = new WifiAwareAgentNetworkSpecifier();
-
-        dut.assertValidFromUid(0);
-    }
-
     // utilities
 
     /**
@@ -182,6 +171,6 @@
     WifiAwareNetworkSpecifier getDummyNetworkSpecifier(int clientId) {
         return new WifiAwareNetworkSpecifier(WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_OOB,
                 WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR, clientId, 0, 0, new byte[6],
-                null, null, 10, 5, 0);
+                null, null, 10, 5);
     }
 }
diff --git a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
index 65fbf5b..c5f9804 100644
--- a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
@@ -1564,7 +1564,7 @@
         WifiAwareNetworkSpecifier ns = new WifiAwareNetworkSpecifier(NETWORK_SPECIFIER_TYPE_IB,
                 WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER, 5, 568, 334,
                 HexEncoding.decode("000102030405".toCharArray(), false),
-                "01234567890123456789012345678901".getBytes(), "blah blah", 666, 4, 10001);
+                "01234567890123456789012345678901".getBytes(), "blah blah", 666, 4);
 
         Parcel parcelW = Parcel.obtain();
         ns.writeToParcel(parcelW, 0);
diff --git a/wifi/tests/src/android/net/wifi/wificond/NativeScanResultTest.java b/wifi/tests/src/android/net/wifi/wificond/NativeScanResultTest.java
index 06f12f7..0df170f 100644
--- a/wifi/tests/src/android/net/wifi/wificond/NativeScanResultTest.java
+++ b/wifi/tests/src/android/net/wifi/wificond/NativeScanResultTest.java
@@ -28,7 +28,6 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.BitSet;
 
 /**
  * Unit tests for {@link android.net.wifi.wificond.NativeScanResult}.
@@ -46,7 +45,7 @@
     private static final int TEST_FREQUENCY = 2456;
     private static final int TEST_SIGNAL_MBM = -45;
     private static final long TEST_TSF = 34455441;
-    private static final BitSet TEST_CAPABILITY = new BitSet(16) {{ set(2); set(5); }};
+    private static final int TEST_CAPABILITY = (0x1 << 2) | (0x1 << 5);
     private static final boolean TEST_ASSOCIATED = true;
     private static final int[] RADIO_CHAIN_IDS = { 0, 1 };
     private static final int[] RADIO_CHAIN_LEVELS = { -56, -65 };
diff --git a/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java b/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java
index 5ba02a7..32105be 100644
--- a/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java
@@ -720,8 +720,7 @@
     @Test
     public void testRegisterDeathHandler() throws Exception {
         Runnable deathHandler = mock(Runnable.class);
-        assertTrue(mWificondControl.initialize(deathHandler));
-        verify(mWificond).tearDownInterfaces();
+        mWificondControl.setOnServiceDeadCallback(deathHandler);
         mWificondControl.binderDied();
         mLooper.dispatchAll();
         verify(deathHandler).run();
@@ -734,7 +733,7 @@
     @Test
     public void testDeathHandling() throws Exception {
         Runnable deathHandler = mock(Runnable.class);
-        assertTrue(mWificondControl.initialize(deathHandler));
+        mWificondControl.setOnServiceDeadCallback(deathHandler);
 
         testSetupInterfaceForClientMode();